001/* 002 * nimbus-jose-jwt 003 * 004 * Copyright 2012-2016, Connect2id Ltd. 005 * 006 * Licensed under the Apache License, Version 2.0 (the "License"); you may not use 007 * this file except in compliance with the License. You may obtain a copy of the 008 * License at 009 * 010 * http://www.apache.org/licenses/LICENSE-2.0 011 * 012 * Unless required by applicable law or agreed to in writing, software distributed 013 * under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR 014 * CONDITIONS OF ANY KIND, either express or implied. See the License for the 015 * specific language governing permissions and limitations under the License. 016 */ 017 018package com.nimbusds.jwt; 019 020 021import java.io.Serializable; 022import java.net.URI; 023import java.net.URISyntaxException; 024import java.text.ParseException; 025import java.util.*; 026 027import com.nimbusds.jose.util.JSONObjectUtils; 028import com.nimbusds.jose.util.DateUtils; 029import net.jcip.annotations.Immutable; 030import net.minidev.json.JSONArray; 031import net.minidev.json.JSONObject; 032 033 034/** 035 * JSON Web Token (JWT) claims set. This class is immutable. 036 * 037 * <p>Supports all {@link #getRegisteredNames()} registered claims} of the JWT 038 * specification: 039 * 040 * <ul> 041 * <li>iss - Issuer 042 * <li>sub - Subject 043 * <li>aud - Audience 044 * <li>exp - Expiration Time 045 * <li>nbf - Not Before 046 * <li>iat - Issued At 047 * <li>jti - JWT ID 048 * </ul> 049 * 050 * <p>The set may also contain custom claims; these will be serialised and 051 * parsed along the registered ones. 052 * 053 * <p>Example JWT claims set: 054 * 055 * <pre> 056 * { 057 * "sub" : "joe", 058 * "exp" : 1300819380, 059 * "http://example.com/is_root" : true 060 * } 061 * </pre> 062 * 063 * <p>Example usage: 064 * 065 * <pre> 066 * JWTClaimsSet claimsSet = new JWTClaimsSet.Builder() 067 * .subject("joe") 068 * .expirationDate(new Date(1300819380 * 1000l) 069 * .claim("http://example.com/is_root", true) 070 * .build(); 071 * </pre> 072 * 073 * @author Vladimir Dzhuvinov 074 * @author Justin Richer 075 * @version 2017-05-29 076 */ 077@Immutable 078public final class JWTClaimsSet implements Serializable { 079 080 081 private static final long serialVersionUID = 1L; 082 083 084 private static final String ISSUER_CLAIM = "iss"; 085 private static final String SUBJECT_CLAIM = "sub"; 086 private static final String AUDIENCE_CLAIM = "aud"; 087 private static final String EXPIRATION_TIME_CLAIM = "exp"; 088 private static final String NOT_BEFORE_CLAIM = "nbf"; 089 private static final String ISSUED_AT_CLAIM = "iat"; 090 private static final String JWT_ID_CLAIM = "jti"; 091 092 093 /** 094 * The registered claim names. 095 */ 096 private static final Set<String> REGISTERED_CLAIM_NAMES; 097 098 099 /** 100 * Initialises the registered claim name set. 101 */ 102 static { 103 Set<String> n = new HashSet<>(); 104 105 n.add(ISSUER_CLAIM); 106 n.add(SUBJECT_CLAIM); 107 n.add(AUDIENCE_CLAIM); 108 n.add(EXPIRATION_TIME_CLAIM); 109 n.add(NOT_BEFORE_CLAIM); 110 n.add(ISSUED_AT_CLAIM); 111 n.add(JWT_ID_CLAIM); 112 113 REGISTERED_CLAIM_NAMES = Collections.unmodifiableSet(n); 114 } 115 116 117 /** 118 * Builder for constructing JSON Web Token (JWT) claims sets. 119 * 120 * <p>Example usage: 121 * 122 * <pre> 123 * JWTClaimsSet claimsSet = new JWTClaimsSet.Builder() 124 * .subject("joe") 125 * .expirationDate(new Date(1300819380 * 1000l) 126 * .claim("http://example.com/is_root", true) 127 * .build(); 128 * </pre> 129 */ 130 public static class Builder { 131 132 133 /** 134 * The claims. 135 */ 136 private final Map<String,Object> claims = new LinkedHashMap<>(); 137 138 139 /** 140 * Creates a new builder. 141 */ 142 public Builder() { 143 144 // Nothing to do 145 } 146 147 148 /** 149 * Creates a new builder with the claims from the specified 150 * set. 151 * 152 * @param jwtClaimsSet The JWT claims set to use. Must not be 153 * {@code null}. 154 */ 155 public Builder(final JWTClaimsSet jwtClaimsSet) { 156 157 claims.putAll(jwtClaimsSet.claims); 158 } 159 160 161 /** 162 * Sets the issuer ({@code iss}) claim. 163 * 164 * @param iss The issuer claim, {@code null} if not specified. 165 * 166 * @return This builder. 167 */ 168 public Builder issuer(final String iss) { 169 170 claims.put(ISSUER_CLAIM, iss); 171 return this; 172 } 173 174 175 /** 176 * Sets the subject ({@code sub}) claim. 177 * 178 * @param sub The subject claim, {@code null} if not specified. 179 * 180 * @return This builder. 181 */ 182 public Builder subject(final String sub) { 183 184 claims.put(SUBJECT_CLAIM, sub); 185 return this; 186 } 187 188 189 /** 190 * Sets the audience ({@code aud}) claim. 191 * 192 * @param aud The audience claim, {@code null} if not 193 * specified. 194 * 195 * @return This builder. 196 */ 197 public Builder audience(final List<String> aud) { 198 199 claims.put(AUDIENCE_CLAIM, aud); 200 return this; 201 } 202 203 204 /** 205 * Sets a single-valued audience ({@code aud}) claim. 206 * 207 * @param aud The audience claim, {@code null} if not 208 * specified. 209 * 210 * @return This builder. 211 */ 212 public Builder audience(final String aud) { 213 214 if (aud == null) { 215 claims.put(AUDIENCE_CLAIM, null); 216 } else { 217 claims.put(AUDIENCE_CLAIM, Collections.singletonList(aud)); 218 } 219 return this; 220 } 221 222 223 /** 224 * Sets the expiration time ({@code exp}) claim. 225 * 226 * @param exp The expiration time, {@code null} if not 227 * specified. 228 * 229 * @return This builder. 230 */ 231 public Builder expirationTime(final Date exp) { 232 233 claims.put(EXPIRATION_TIME_CLAIM, exp); 234 return this; 235 } 236 237 238 /** 239 * Sets the not-before ({@code nbf}) claim. 240 * 241 * @param nbf The not-before claim, {@code null} if not 242 * specified. 243 * 244 * @return This builder. 245 */ 246 public Builder notBeforeTime(final Date nbf) { 247 248 claims.put(NOT_BEFORE_CLAIM, nbf); 249 return this; 250 } 251 252 253 /** 254 * Sets the issued-at ({@code iat}) claim. 255 * 256 * @param iat The issued-at claim, {@code null} if not 257 * specified. 258 * 259 * @return This builder. 260 */ 261 public Builder issueTime(final Date iat) { 262 263 claims.put(ISSUED_AT_CLAIM, iat); 264 return this; 265 } 266 267 268 /** 269 * Sets the JWT ID ({@code jti}) claim. 270 * 271 * @param jti The JWT ID claim, {@code null} if not specified. 272 * 273 * @return This builder. 274 */ 275 public Builder jwtID(final String jti) { 276 277 claims.put(JWT_ID_CLAIM, jti); 278 return this; 279 } 280 281 282 /** 283 * Sets the specified claim (registered or custom). 284 * 285 * @param name The name of the claim to set. Must not be 286 * {@code null}. 287 * @param value The value of the claim to set, {@code null} if 288 * not specified. Should map to a JSON entity. 289 * 290 * @return This builder. 291 */ 292 public Builder claim(final String name, final Object value) { 293 294 claims.put(name, value); 295 return this; 296 } 297 298 299 /** 300 * Builds a new JWT claims set. 301 * 302 * @return The JWT claims set. 303 */ 304 public JWTClaimsSet build() { 305 306 return new JWTClaimsSet(claims); 307 } 308 } 309 310 311 /** 312 * The claims map. 313 */ 314 private final Map<String,Object> claims = new LinkedHashMap<>(); 315 316 317 /** 318 * Creates a new JWT claims set. 319 * 320 * @param claims The JWT claims set as a map. Must not be {@code null}. 321 */ 322 private JWTClaimsSet(final Map<String,Object> claims) { 323 324 this.claims.putAll(claims); 325 } 326 327 328 /** 329 * Gets the registered JWT claim names. 330 * 331 * @return The registered claim names, as a unmodifiable set. 332 */ 333 public static Set<String> getRegisteredNames() { 334 335 return REGISTERED_CLAIM_NAMES; 336 } 337 338 339 /** 340 * Gets the issuer ({@code iss}) claim. 341 * 342 * @return The issuer claim, {@code null} if not specified. 343 */ 344 public String getIssuer() { 345 346 try { 347 return getStringClaim(ISSUER_CLAIM); 348 } catch (ParseException e) { 349 return null; 350 } 351 } 352 353 354 /** 355 * Gets the subject ({@code sub}) claim. 356 * 357 * @return The subject claim, {@code null} if not specified. 358 */ 359 public String getSubject() { 360 361 try { 362 return getStringClaim(SUBJECT_CLAIM); 363 } catch (ParseException e) { 364 return null; 365 } 366 } 367 368 369 /** 370 * Gets the audience ({@code aud}) clam. 371 * 372 * @return The audience claim, empty list if not specified. 373 */ 374 public List<String> getAudience() { 375 376 List<String> aud; 377 try { 378 aud = getStringListClaim(AUDIENCE_CLAIM); 379 } catch (ParseException e) { 380 return Collections.emptyList(); 381 } 382 return aud != null ? Collections.unmodifiableList(aud) : Collections.<String>emptyList(); 383 } 384 385 386 /** 387 * Gets the expiration time ({@code exp}) claim. 388 * 389 * @return The expiration time, {@code null} if not specified. 390 */ 391 public Date getExpirationTime() { 392 393 try { 394 return getDateClaim(EXPIRATION_TIME_CLAIM); 395 } catch (ParseException e) { 396 return null; 397 } 398 } 399 400 401 /** 402 * Gets the not-before ({@code nbf}) claim. 403 * 404 * @return The not-before claim, {@code null} if not specified. 405 */ 406 public Date getNotBeforeTime() { 407 408 try { 409 return getDateClaim(NOT_BEFORE_CLAIM); 410 } catch (ParseException e) { 411 return null; 412 } 413 } 414 415 416 /** 417 * Gets the issued-at ({@code iat}) claim. 418 * 419 * @return The issued-at claim, {@code null} if not specified. 420 */ 421 public Date getIssueTime() { 422 423 try { 424 return getDateClaim(ISSUED_AT_CLAIM); 425 } catch (ParseException e) { 426 return null; 427 } 428 } 429 430 431 /** 432 * Gets the JWT ID ({@code jti}) claim. 433 * 434 * @return The JWT ID claim, {@code null} if not specified. 435 */ 436 public String getJWTID() { 437 438 try { 439 return getStringClaim(JWT_ID_CLAIM); 440 } catch (ParseException e) { 441 return null; 442 } 443 } 444 445 446 /** 447 * Gets the specified claim (registered or custom). 448 * 449 * @param name The name of the claim. Must not be {@code null}. 450 * 451 * @return The value of the claim, {@code null} if not specified. 452 */ 453 public Object getClaim(final String name) { 454 455 return claims.get(name); 456 } 457 458 459 /** 460 * Gets the specified claim (registered or custom) as 461 * {@link java.lang.String}. 462 * 463 * @param name The name of the claim. Must not be {@code null}. 464 * 465 * @return The value of the claim, {@code null} if not specified. 466 * 467 * @throws ParseException If the claim value is not of the required 468 * type. 469 */ 470 public String getStringClaim(final String name) 471 throws ParseException { 472 473 Object value = getClaim(name); 474 475 if (value == null || value instanceof String) { 476 return (String)value; 477 } else { 478 throw new ParseException("The \"" + name + "\" claim is not a String", 0); 479 } 480 } 481 482 483 /** 484 * Gets the specified claims (registered or custom) as a 485 * {@link java.lang.String} array. 486 * 487 * @param name The name of the claim. Must not be {@code null}. 488 * 489 * @return The value of the claim, {@code null} if not specified. 490 * 491 * @throws ParseException If the claim value is not of the required 492 * type. 493 */ 494 public String[] getStringArrayClaim(final String name) 495 throws ParseException { 496 497 Object value = getClaim(name); 498 499 if (value == null) { 500 return null; 501 } 502 503 List<?> list; 504 505 try { 506 list = (List<?>)getClaim(name); 507 508 } catch (ClassCastException e) { 509 throw new ParseException("The \"" + name + "\" claim is not a list / JSON array", 0); 510 } 511 512 String[] stringArray = new String[list.size()]; 513 514 for (int i=0; i < stringArray.length; i++) { 515 516 try { 517 stringArray[i] = (String)list.get(i); 518 } catch (ClassCastException e) { 519 throw new ParseException("The \"" + name + "\" claim is not a list / JSON array of strings", 0); 520 } 521 } 522 523 return stringArray; 524 } 525 526 527 /** 528 * Gets the specified claims (registered or custom) as a 529 * {@link java.util.List} list of strings. 530 * 531 * @param name The name of the claim. Must not be {@code null}. 532 * 533 * @return The value of the claim, {@code null} if not specified. 534 * 535 * @throws ParseException If the claim value is not of the required 536 * type. 537 */ 538 public List<String> getStringListClaim(final String name) 539 throws ParseException { 540 541 String[] stringArray = getStringArrayClaim(name); 542 543 if (stringArray == null) { 544 return null; 545 } 546 547 return Collections.unmodifiableList(Arrays.asList(stringArray)); 548 } 549 550 551 /** 552 * Gets the specified claim (registered or custom) as a 553 * {@link java.net.URI}. 554 * 555 * @param name The name of the claim. Must not be {@code null}. 556 * 557 * @return The value of the claim, {@code null} if not specified. 558 * 559 * @throws ParseException If the claim couldn't be parsed to a URI. 560 */ 561 public URI getURIClaim(final String name) 562 throws ParseException { 563 564 String uriString = getStringClaim(name); 565 566 if (uriString == null) { 567 return null; 568 } 569 570 try { 571 return new URI(uriString); 572 } catch (URISyntaxException e) { 573 throw new ParseException("The \"" + name + "\" claim is not a URI: " + e.getMessage(), 0); 574 } 575 } 576 577 578 /** 579 * Gets the specified claim (registered or custom) as 580 * {@link java.lang.Boolean}. 581 * 582 * @param name The name of the claim. Must not be {@code null}. 583 * 584 * @return The value of the claim, {@code null} if not specified. 585 * 586 * @throws ParseException If the claim value is not of the required 587 * type. 588 */ 589 public Boolean getBooleanClaim(final String name) 590 throws ParseException { 591 592 Object value = getClaim(name); 593 594 if (value == null || value instanceof Boolean) { 595 return (Boolean)value; 596 } else { 597 throw new ParseException("The \"" + name + "\" claim is not a Boolean", 0); 598 } 599 } 600 601 602 /** 603 * Gets the specified claim (registered or custom) as 604 * {@link java.lang.Integer}. 605 * 606 * @param name The name of the claim. Must not be {@code null}. 607 * 608 * @return The value of the claim, {@code null} if not specified. 609 * 610 * @throws ParseException If the claim value is not of the required 611 * type. 612 */ 613 public Integer getIntegerClaim(final String name) 614 throws ParseException { 615 616 Object value = getClaim(name); 617 618 if (value == null) { 619 return null; 620 } else if (value instanceof Number) { 621 return ((Number)value).intValue(); 622 } else { 623 throw new ParseException("The \"" + name + "\" claim is not an Integer", 0); 624 } 625 } 626 627 628 /** 629 * Gets the specified claim (registered or custom) as 630 * {@link java.lang.Long}. 631 * 632 * @param name The name of the claim. Must not be {@code null}. 633 * 634 * @return The value of the claim, {@code null} if not specified. 635 * 636 * @throws ParseException If the claim value is not of the required 637 * type. 638 */ 639 public Long getLongClaim(final String name) 640 throws ParseException { 641 642 Object value = getClaim(name); 643 644 if (value == null) { 645 return null; 646 } else if (value instanceof Number) { 647 return ((Number)value).longValue(); 648 } else { 649 throw new ParseException("The \"" + name + "\" claim is not a Number", 0); 650 } 651 } 652 653 654 /** 655 * Gets the specified claim (registered or custom) as 656 * {@link java.util.Date}. The claim may be represented by a Date 657 * object or a number of a seconds since the Unix epoch. 658 * 659 * @param name The name of the claim. Must not be {@code null}. 660 * 661 * @return The value of the claim, {@code null} if not specified. 662 * 663 * @throws ParseException If the claim value is not of the required 664 * type. 665 */ 666 public Date getDateClaim(final String name) 667 throws ParseException { 668 669 Object value = getClaim(name); 670 671 if (value == null) { 672 return null; 673 } else if (value instanceof Date) { 674 return (Date)value; 675 } else if (value instanceof Number) { 676 return DateUtils.fromSecondsSinceEpoch(((Number)value).longValue()); 677 } else { 678 throw new ParseException("The \"" + name + "\" claim is not a Date", 0); 679 } 680 } 681 682 683 /** 684 * Gets the specified claim (registered or custom) as 685 * {@link java.lang.Float}. 686 * 687 * @param name The name of the claim. Must not be {@code null}. 688 * 689 * @return The value of the claim, {@code null} if not specified. 690 * 691 * @throws ParseException If the claim value is not of the required 692 * type. 693 */ 694 public Float getFloatClaim(final String name) 695 throws ParseException { 696 697 Object value = getClaim(name); 698 699 if (value == null) { 700 return null; 701 } else if (value instanceof Number) { 702 return ((Number)value).floatValue(); 703 } else { 704 throw new ParseException("The \"" + name + "\" claim is not a Float", 0); 705 } 706 } 707 708 709 /** 710 * Gets the specified claim (registered or custom) as 711 * {@link java.lang.Double}. 712 * 713 * @param name The name of the claim. Must not be {@code null}. 714 * 715 * @return The value of the claim, {@code null} if not specified. 716 * 717 * @throws ParseException If the claim value is not of the required 718 * type. 719 */ 720 public Double getDoubleClaim(final String name) 721 throws ParseException { 722 723 Object value = getClaim(name); 724 725 if (value == null) { 726 return null; 727 } else if (value instanceof Number) { 728 return ((Number)value).doubleValue(); 729 } else { 730 throw new ParseException("The \"" + name + "\" claim is not a Double", 0); 731 } 732 } 733 734 735 /** 736 * Gets the specified claim (registered or custom) as a 737 * {@link net.minidev.json.JSONObject}. 738 * 739 * @param name The name of the claim. Must not be {@code null}. 740 * 741 * @return The value of the claim, {@code null} if not specified. 742 * 743 * @throws ParseException If the claim value is not of the required 744 * type. 745 */ 746 public JSONObject getJSONObjectClaim(final String name) 747 throws ParseException { 748 749 Object value = getClaim(name); 750 751 if (value == null) { 752 return null; 753 } else if (value instanceof JSONObject) { 754 return (JSONObject)value; 755 } else if (value instanceof Map) { 756 JSONObject jsonObject = new JSONObject(); 757 Map<?,?> map = (Map<?,?>)value; 758 for (Map.Entry<?,?> entry: map.entrySet()) { 759 if (entry.getKey() instanceof String) { 760 jsonObject.put((String)entry.getKey(), entry.getValue()); 761 } 762 } 763 return jsonObject; 764 } else { 765 throw new ParseException("The \"" + name + "\" claim is not a JSON object or Map", 0); 766 } 767 } 768 769 770 /** 771 * Gets the claims (registered and custom). 772 * 773 * <p>Note that the registered claims Expiration-Time ({@code exp}), 774 * Not-Before-Time ({@code nbf}) and Issued-At ({@code iat}) will be 775 * returned as {@code java.util.Date} instances. 776 * 777 * @return The claims, as an unmodifiable map, empty map if none. 778 */ 779 public Map<String,Object> getClaims() { 780 781 return Collections.unmodifiableMap(claims); 782 } 783 784 785 /** 786 * Returns the JSON object representation of the claims set. The claims 787 * are serialised according to their insertion order. 788 * 789 * @return The JSON object representation. 790 */ 791 public JSONObject toJSONObject() { 792 793 JSONObject o = new JSONObject(); 794 795 for (Map.Entry<String,Object> claim: claims.entrySet()) { 796 797 if (claim.getValue() instanceof Date) { 798 799 // Transform dates to Unix timestamps 800 Date dateValue = (Date) claim.getValue(); 801 o.put(claim.getKey(), DateUtils.toSecondsSinceEpoch(dateValue)); 802 803 } else if (AUDIENCE_CLAIM.equals(claim.getKey())) { 804 805 // Serialise single audience list and string 806 List<String> audList = getAudience(); 807 808 if (audList != null && ! audList.isEmpty()) { 809 if (audList.size() == 1) { 810 o.put(AUDIENCE_CLAIM, audList.get(0)); 811 } else { 812 JSONArray audArray = new JSONArray(); 813 audArray.addAll(audList); 814 o.put(AUDIENCE_CLAIM, audArray); 815 } 816 } 817 818 } else if (claim.getValue() != null) { 819 // Do not output claims with null values! 820 o.put(claim.getKey(), claim.getValue()); 821 } 822 } 823 824 return o; 825 } 826 827 828 @Override 829 public String toString() { 830 831 return toJSONObject().toJSONString(); 832 } 833 834 835 /** 836 * Returns a transformation of this JWT claims set. 837 * 838 * @param <T> Type of the result. 839 * @param transformer The JWT claims set transformer. Must not be 840 * {@code null}. 841 * 842 * @return The transformed JWT claims set. 843 */ 844 public <T> T toType(final JWTClaimsSetTransformer<T> transformer) { 845 846 return transformer.transform(this); 847 } 848 849 850 /** 851 * Parses a JSON Web Token (JWT) claims set from the specified JSON 852 * object representation. 853 * 854 * @param json The JSON object to parse. Must not be {@code null}. 855 * 856 * @return The JWT claims set. 857 * 858 * @throws ParseException If the specified JSON object doesn't 859 * represent a valid JWT claims set. 860 */ 861 public static JWTClaimsSet parse(final JSONObject json) 862 throws ParseException { 863 864 JWTClaimsSet.Builder builder = new JWTClaimsSet.Builder(); 865 866 // Parse registered + custom params 867 for (final String name: json.keySet()) { 868 869 if (name.equals(ISSUER_CLAIM)) { 870 871 builder.issuer(JSONObjectUtils.getString(json, ISSUER_CLAIM)); 872 873 } else if (name.equals(SUBJECT_CLAIM)) { 874 875 builder.subject(JSONObjectUtils.getString(json, SUBJECT_CLAIM)); 876 877 } else if (name.equals(AUDIENCE_CLAIM)) { 878 879 Object audValue = json.get(AUDIENCE_CLAIM); 880 881 if (audValue instanceof String) { 882 List<String> singleAud = new ArrayList<>(); 883 singleAud.add(JSONObjectUtils.getString(json, AUDIENCE_CLAIM)); 884 builder.audience(singleAud); 885 } else if (audValue instanceof List) { 886 builder.audience(JSONObjectUtils.getStringList(json, AUDIENCE_CLAIM)); 887 } 888 889 } else if (name.equals(EXPIRATION_TIME_CLAIM)) { 890 891 builder.expirationTime(new Date(JSONObjectUtils.getLong(json, EXPIRATION_TIME_CLAIM) * 1000)); 892 893 } else if (name.equals(NOT_BEFORE_CLAIM)) { 894 895 builder.notBeforeTime(new Date(JSONObjectUtils.getLong(json, NOT_BEFORE_CLAIM) * 1000)); 896 897 } else if (name.equals(ISSUED_AT_CLAIM)) { 898 899 builder.issueTime(new Date(JSONObjectUtils.getLong(json, ISSUED_AT_CLAIM) * 1000)); 900 901 } else if (name.equals(JWT_ID_CLAIM)) { 902 903 builder.jwtID(JSONObjectUtils.getString(json, JWT_ID_CLAIM)); 904 905 } else { 906 builder.claim(name, json.get(name)); 907 } 908 } 909 910 return builder.build(); 911 } 912 913 914 /** 915 * Parses a JSON Web Token (JWT) claims set from the specified JSON 916 * object string representation. 917 * 918 * @param s The JSON object string to parse. Must not be {@code null}. 919 * 920 * @return The JWT claims set. 921 * 922 * @throws ParseException If the specified JSON object string doesn't 923 * represent a valid JWT claims set. 924 */ 925 public static JWTClaimsSet parse(final String s) 926 throws ParseException { 927 928 return parse(JSONObjectUtils.parse(s)); 929 } 930}