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.DateUtils; 028import com.nimbusds.jose.util.JSONObjectUtils; 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 2018-08-23 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}) claim. 371 * 372 * @return The audience claim, empty list if not specified. 373 */ 374 public List<String> getAudience() { 375 376 Object audValue = getClaim(AUDIENCE_CLAIM); 377 378 if (audValue instanceof String) { 379 // Special case 380 return Collections.singletonList((String)audValue); 381 } 382 383 List<String> aud; 384 try { 385 aud = getStringListClaim(AUDIENCE_CLAIM); 386 } catch (ParseException e) { 387 return Collections.emptyList(); 388 } 389 return aud != null ? Collections.unmodifiableList(aud) : Collections.<String>emptyList(); 390 } 391 392 393 /** 394 * Gets the expiration time ({@code exp}) claim. 395 * 396 * @return The expiration time, {@code null} if not specified. 397 */ 398 public Date getExpirationTime() { 399 400 try { 401 return getDateClaim(EXPIRATION_TIME_CLAIM); 402 } catch (ParseException e) { 403 return null; 404 } 405 } 406 407 408 /** 409 * Gets the not-before ({@code nbf}) claim. 410 * 411 * @return The not-before claim, {@code null} if not specified. 412 */ 413 public Date getNotBeforeTime() { 414 415 try { 416 return getDateClaim(NOT_BEFORE_CLAIM); 417 } catch (ParseException e) { 418 return null; 419 } 420 } 421 422 423 /** 424 * Gets the issued-at ({@code iat}) claim. 425 * 426 * @return The issued-at claim, {@code null} if not specified. 427 */ 428 public Date getIssueTime() { 429 430 try { 431 return getDateClaim(ISSUED_AT_CLAIM); 432 } catch (ParseException e) { 433 return null; 434 } 435 } 436 437 438 /** 439 * Gets the JWT ID ({@code jti}) claim. 440 * 441 * @return The JWT ID claim, {@code null} if not specified. 442 */ 443 public String getJWTID() { 444 445 try { 446 return getStringClaim(JWT_ID_CLAIM); 447 } catch (ParseException e) { 448 return null; 449 } 450 } 451 452 453 /** 454 * Gets the specified claim (registered or custom). 455 * 456 * @param name The name of the claim. Must not be {@code null}. 457 * 458 * @return The value of the claim, {@code null} if not specified. 459 */ 460 public Object getClaim(final String name) { 461 462 return claims.get(name); 463 } 464 465 466 /** 467 * Gets the specified claim (registered or custom) as 468 * {@link java.lang.String}. 469 * 470 * @param name The name of the claim. Must not be {@code null}. 471 * 472 * @return The value of the claim, {@code null} if not specified. 473 * 474 * @throws ParseException If the claim value is not of the required 475 * type. 476 */ 477 public String getStringClaim(final String name) 478 throws ParseException { 479 480 Object value = getClaim(name); 481 482 if (value == null || value instanceof String) { 483 return (String)value; 484 } else { 485 throw new ParseException("The \"" + name + "\" claim is not a String", 0); 486 } 487 } 488 489 490 /** 491 * Gets the specified claims (registered or custom) as a 492 * {@link java.lang.String} array. 493 * 494 * @param name The name of the claim. Must not be {@code null}. 495 * 496 * @return The value of the claim, {@code null} if not specified. 497 * 498 * @throws ParseException If the claim value is not of the required 499 * type. 500 */ 501 public String[] getStringArrayClaim(final String name) 502 throws ParseException { 503 504 Object value = getClaim(name); 505 506 if (value == null) { 507 return null; 508 } 509 510 List<?> list; 511 512 try { 513 list = (List<?>)getClaim(name); 514 515 } catch (ClassCastException e) { 516 throw new ParseException("The \"" + name + "\" claim is not a list / JSON array", 0); 517 } 518 519 String[] stringArray = new String[list.size()]; 520 521 for (int i=0; i < stringArray.length; i++) { 522 523 try { 524 stringArray[i] = (String)list.get(i); 525 } catch (ClassCastException e) { 526 throw new ParseException("The \"" + name + "\" claim is not a list / JSON array of strings", 0); 527 } 528 } 529 530 return stringArray; 531 } 532 533 534 /** 535 * Gets the specified claims (registered or custom) as a 536 * {@link java.util.List} list of strings. 537 * 538 * @param name The name of the claim. Must not be {@code null}. 539 * 540 * @return The value of the claim, {@code null} if not specified. 541 * 542 * @throws ParseException If the claim value is not of the required 543 * type. 544 */ 545 public List<String> getStringListClaim(final String name) 546 throws ParseException { 547 548 String[] stringArray = getStringArrayClaim(name); 549 550 if (stringArray == null) { 551 return null; 552 } 553 554 return Collections.unmodifiableList(Arrays.asList(stringArray)); 555 } 556 557 558 /** 559 * Gets the specified claim (registered or custom) as a 560 * {@link java.net.URI}. 561 * 562 * @param name The name of the claim. Must not be {@code null}. 563 * 564 * @return The value of the claim, {@code null} if not specified. 565 * 566 * @throws ParseException If the claim couldn't be parsed to a URI. 567 */ 568 public URI getURIClaim(final String name) 569 throws ParseException { 570 571 String uriString = getStringClaim(name); 572 573 if (uriString == null) { 574 return null; 575 } 576 577 try { 578 return new URI(uriString); 579 } catch (URISyntaxException e) { 580 throw new ParseException("The \"" + name + "\" claim is not a URI: " + e.getMessage(), 0); 581 } 582 } 583 584 585 /** 586 * Gets the specified claim (registered or custom) as 587 * {@link java.lang.Boolean}. 588 * 589 * @param name The name of the claim. Must not be {@code null}. 590 * 591 * @return The value of the claim, {@code null} if not specified. 592 * 593 * @throws ParseException If the claim value is not of the required 594 * type. 595 */ 596 public Boolean getBooleanClaim(final String name) 597 throws ParseException { 598 599 Object value = getClaim(name); 600 601 if (value == null || value instanceof Boolean) { 602 return (Boolean)value; 603 } else { 604 throw new ParseException("The \"" + name + "\" claim is not a Boolean", 0); 605 } 606 } 607 608 609 /** 610 * Gets the specified claim (registered or custom) as 611 * {@link java.lang.Integer}. 612 * 613 * @param name The name of the claim. Must not be {@code null}. 614 * 615 * @return The value of the claim, {@code null} if not specified. 616 * 617 * @throws ParseException If the claim value is not of the required 618 * type. 619 */ 620 public Integer getIntegerClaim(final String name) 621 throws ParseException { 622 623 Object value = getClaim(name); 624 625 if (value == null) { 626 return null; 627 } else if (value instanceof Number) { 628 return ((Number)value).intValue(); 629 } else { 630 throw new ParseException("The \"" + name + "\" claim is not an Integer", 0); 631 } 632 } 633 634 635 /** 636 * Gets the specified claim (registered or custom) as 637 * {@link java.lang.Long}. 638 * 639 * @param name The name of the claim. Must not be {@code null}. 640 * 641 * @return The value of the claim, {@code null} if not specified. 642 * 643 * @throws ParseException If the claim value is not of the required 644 * type. 645 */ 646 public Long getLongClaim(final String name) 647 throws ParseException { 648 649 Object value = getClaim(name); 650 651 if (value == null) { 652 return null; 653 } else if (value instanceof Number) { 654 return ((Number)value).longValue(); 655 } else { 656 throw new ParseException("The \"" + name + "\" claim is not a Number", 0); 657 } 658 } 659 660 661 /** 662 * Gets the specified claim (registered or custom) as 663 * {@link java.util.Date}. The claim may be represented by a Date 664 * object or a number of a seconds since the Unix epoch. 665 * 666 * @param name The name of the claim. Must not be {@code null}. 667 * 668 * @return The value of the claim, {@code null} if not specified. 669 * 670 * @throws ParseException If the claim value is not of the required 671 * type. 672 */ 673 public Date getDateClaim(final String name) 674 throws ParseException { 675 676 Object value = getClaim(name); 677 678 if (value == null) { 679 return null; 680 } else if (value instanceof Date) { 681 return (Date)value; 682 } else if (value instanceof Number) { 683 return DateUtils.fromSecondsSinceEpoch(((Number)value).longValue()); 684 } else { 685 throw new ParseException("The \"" + name + "\" claim is not a Date", 0); 686 } 687 } 688 689 690 /** 691 * Gets the specified claim (registered or custom) as 692 * {@link java.lang.Float}. 693 * 694 * @param name The name of the claim. Must not be {@code null}. 695 * 696 * @return The value of the claim, {@code null} if not specified. 697 * 698 * @throws ParseException If the claim value is not of the required 699 * type. 700 */ 701 public Float getFloatClaim(final String name) 702 throws ParseException { 703 704 Object value = getClaim(name); 705 706 if (value == null) { 707 return null; 708 } else if (value instanceof Number) { 709 return ((Number)value).floatValue(); 710 } else { 711 throw new ParseException("The \"" + name + "\" claim is not a Float", 0); 712 } 713 } 714 715 716 /** 717 * Gets the specified claim (registered or custom) as 718 * {@link java.lang.Double}. 719 * 720 * @param name The name of the claim. Must not be {@code null}. 721 * 722 * @return The value of the claim, {@code null} if not specified. 723 * 724 * @throws ParseException If the claim value is not of the required 725 * type. 726 */ 727 public Double getDoubleClaim(final String name) 728 throws ParseException { 729 730 Object value = getClaim(name); 731 732 if (value == null) { 733 return null; 734 } else if (value instanceof Number) { 735 return ((Number)value).doubleValue(); 736 } else { 737 throw new ParseException("The \"" + name + "\" claim is not a Double", 0); 738 } 739 } 740 741 742 /** 743 * Gets the specified claim (registered or custom) as a 744 * {@link net.minidev.json.JSONObject}. 745 * 746 * @param name The name of the claim. Must not be {@code null}. 747 * 748 * @return The value of the claim, {@code null} if not specified. 749 * 750 * @throws ParseException If the claim value is not of the required 751 * type. 752 */ 753 public JSONObject getJSONObjectClaim(final String name) 754 throws ParseException { 755 756 Object value = getClaim(name); 757 758 if (value == null) { 759 return null; 760 } else if (value instanceof JSONObject) { 761 return (JSONObject)value; 762 } else if (value instanceof Map) { 763 JSONObject jsonObject = new JSONObject(); 764 Map<?,?> map = (Map<?,?>)value; 765 for (Map.Entry<?,?> entry: map.entrySet()) { 766 if (entry.getKey() instanceof String) { 767 jsonObject.put((String)entry.getKey(), entry.getValue()); 768 } 769 } 770 return jsonObject; 771 } else { 772 throw new ParseException("The \"" + name + "\" claim is not a JSON object or Map", 0); 773 } 774 } 775 776 777 /** 778 * Gets the claims (registered and custom). 779 * 780 * <p>Note that the registered claims Expiration-Time ({@code exp}), 781 * Not-Before-Time ({@code nbf}) and Issued-At ({@code iat}) will be 782 * returned as {@code java.util.Date} instances. 783 * 784 * @return The claims, as an unmodifiable map, empty map if none. 785 */ 786 public Map<String,Object> getClaims() { 787 788 return Collections.unmodifiableMap(claims); 789 } 790 791 792 /** 793 * Returns the JSON object representation of the claims set. The claims 794 * are serialised according to their insertion order. 795 * 796 * @return The JSON object representation. 797 */ 798 public JSONObject toJSONObject() { 799 800 JSONObject o = new JSONObject(); 801 802 for (Map.Entry<String,Object> claim: claims.entrySet()) { 803 804 if (claim.getValue() instanceof Date) { 805 806 // Transform dates to Unix timestamps 807 Date dateValue = (Date) claim.getValue(); 808 o.put(claim.getKey(), DateUtils.toSecondsSinceEpoch(dateValue)); 809 810 } else if (AUDIENCE_CLAIM.equals(claim.getKey())) { 811 812 // Serialise single audience list and string 813 List<String> audList = getAudience(); 814 815 if (audList != null && ! audList.isEmpty()) { 816 if (audList.size() == 1) { 817 o.put(AUDIENCE_CLAIM, audList.get(0)); 818 } else { 819 JSONArray audArray = new JSONArray(); 820 audArray.addAll(audList); 821 o.put(AUDIENCE_CLAIM, audArray); 822 } 823 } 824 825 } else if (claim.getValue() != null) { 826 // Do not output claims with null values! 827 o.put(claim.getKey(), claim.getValue()); 828 } 829 } 830 831 return o; 832 } 833 834 835 @Override 836 public String toString() { 837 838 return toJSONObject().toJSONString(); 839 } 840 841 842 /** 843 * Returns a transformation of this JWT claims set. 844 * 845 * @param <T> Type of the result. 846 * @param transformer The JWT claims set transformer. Must not be 847 * {@code null}. 848 * 849 * @return The transformed JWT claims set. 850 */ 851 public <T> T toType(final JWTClaimsSetTransformer<T> transformer) { 852 853 return transformer.transform(this); 854 } 855 856 857 /** 858 * Parses a JSON Web Token (JWT) claims set from the specified JSON 859 * object representation. 860 * 861 * @param json The JSON object to parse. Must not be {@code null}. 862 * 863 * @return The JWT claims set. 864 * 865 * @throws ParseException If the specified JSON object doesn't 866 * represent a valid JWT claims set. 867 */ 868 public static JWTClaimsSet parse(final JSONObject json) 869 throws ParseException { 870 871 JWTClaimsSet.Builder builder = new JWTClaimsSet.Builder(); 872 873 // Parse registered + custom params 874 for (final String name: json.keySet()) { 875 876 if (name.equals(ISSUER_CLAIM)) { 877 878 builder.issuer(JSONObjectUtils.getString(json, ISSUER_CLAIM)); 879 880 } else if (name.equals(SUBJECT_CLAIM)) { 881 882 builder.subject(JSONObjectUtils.getString(json, SUBJECT_CLAIM)); 883 884 } else if (name.equals(AUDIENCE_CLAIM)) { 885 886 Object audValue = json.get(AUDIENCE_CLAIM); 887 888 if (audValue instanceof String) { 889 List<String> singleAud = new ArrayList<>(); 890 singleAud.add(JSONObjectUtils.getString(json, AUDIENCE_CLAIM)); 891 builder.audience(singleAud); 892 } else if (audValue instanceof List) { 893 builder.audience(JSONObjectUtils.getStringList(json, AUDIENCE_CLAIM)); 894 } 895 896 } else if (name.equals(EXPIRATION_TIME_CLAIM)) { 897 898 builder.expirationTime(new Date(JSONObjectUtils.getLong(json, EXPIRATION_TIME_CLAIM) * 1000)); 899 900 } else if (name.equals(NOT_BEFORE_CLAIM)) { 901 902 builder.notBeforeTime(new Date(JSONObjectUtils.getLong(json, NOT_BEFORE_CLAIM) * 1000)); 903 904 } else if (name.equals(ISSUED_AT_CLAIM)) { 905 906 builder.issueTime(new Date(JSONObjectUtils.getLong(json, ISSUED_AT_CLAIM) * 1000)); 907 908 } else if (name.equals(JWT_ID_CLAIM)) { 909 910 builder.jwtID(JSONObjectUtils.getString(json, JWT_ID_CLAIM)); 911 912 } else { 913 builder.claim(name, json.get(name)); 914 } 915 } 916 917 return builder.build(); 918 } 919 920 921 /** 922 * Parses a JSON Web Token (JWT) claims set from the specified JSON 923 * object string representation. 924 * 925 * @param s The JSON object string to parse. Must not be {@code null}. 926 * 927 * @return The JWT claims set. 928 * 929 * @throws ParseException If the specified JSON object string doesn't 930 * represent a valid JWT claims set. 931 */ 932 public static JWTClaimsSet parse(final String s) 933 throws ParseException { 934 935 return parse(JSONObjectUtils.parse(s)); 936 } 937}