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