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.jose.util.JSONArrayUtils; 031import com.nimbusds.jose.util.JSONObjectUtils; 032import com.nimbusds.jwt.util.DateUtils; 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 2023-10-16 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.util.List} list of objects. 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 List<Object> getListClaim(final String name) 511 throws ParseException { 512 513 Object value = getClaim(name); 514 515 if (value == null) { 516 return null; 517 } 518 519 try { 520 return (List<Object>)getClaim(name); 521 522 } catch (ClassCastException e) { 523 throw new ParseException("The " + name + " claim is not a list / JSON array", 0); 524 } 525 } 526 527 528 /** 529 * Gets the specified claims (registered or custom) as a 530 * {@link java.lang.String} array. 531 * 532 * @param name The name of the claim. Must not be {@code null}. 533 * 534 * @return The value of the claim, {@code null} if not specified. 535 * 536 * @throws ParseException If the claim value is not of the required 537 * type. 538 */ 539 public String[] getStringArrayClaim(final String name) 540 throws ParseException { 541 542 List<?> list = getListClaim(name); 543 544 if (list == null) { 545 return null; 546 } 547 548 String[] stringArray = new String[list.size()]; 549 550 for (int i=0; i < stringArray.length; i++) { 551 552 try { 553 stringArray[i] = (String)list.get(i); 554 } catch (ClassCastException e) { 555 throw new ParseException("The " + name + " claim is not a list / JSON array of strings", 0); 556 } 557 } 558 559 return stringArray; 560 } 561 562 563 /** 564 * Gets the specified claims (registered or custom) as a 565 * {@link java.util.List} list of strings. 566 * 567 * @param name The name of the claim. Must not be {@code null}. 568 * 569 * @return The value of the claim, {@code null} if not specified. 570 * 571 * @throws ParseException If the claim value is not of the required 572 * type. 573 */ 574 public List<String> getStringListClaim(final String name) 575 throws ParseException { 576 577 String[] stringArray = getStringArrayClaim(name); 578 579 if (stringArray == null) { 580 return null; 581 } 582 583 return Collections.unmodifiableList(Arrays.asList(stringArray)); 584 } 585 586 587 /** 588 * Gets the specified claim (registered or custom) as a 589 * {@link java.net.URI}. 590 * 591 * @param name The name of the claim. Must not be {@code null}. 592 * 593 * @return The value of the claim, {@code null} if not specified. 594 * 595 * @throws ParseException If the claim couldn't be parsed to a URI. 596 */ 597 public URI getURIClaim(final String name) 598 throws ParseException { 599 600 String uriString = getStringClaim(name); 601 602 if (uriString == null) { 603 return null; 604 } 605 606 try { 607 return new URI(uriString); 608 } catch (URISyntaxException e) { 609 throw new ParseException("The \"" + name + "\" claim is not a URI: " + e.getMessage(), 0); 610 } 611 } 612 613 614 /** 615 * Gets the specified claim (registered or custom) as 616 * {@link java.lang.Boolean}. 617 * 618 * @param name The name of the claim. Must not be {@code null}. 619 * 620 * @return The value of the claim, {@code null} if not specified. 621 * 622 * @throws ParseException If the claim value is not of the required 623 * type. 624 */ 625 public Boolean getBooleanClaim(final String name) 626 throws ParseException { 627 628 Object value = getClaim(name); 629 630 if (value == null || value instanceof Boolean) { 631 return (Boolean)value; 632 } else { 633 throw new ParseException("The \"" + name + "\" claim is not a Boolean", 0); 634 } 635 } 636 637 638 /** 639 * Gets the specified claim (registered or custom) as 640 * {@link java.lang.Integer}. 641 * 642 * @param name The name of the claim. Must not be {@code null}. 643 * 644 * @return The value of the claim, {@code null} if not specified. 645 * 646 * @throws ParseException If the claim value is not of the required 647 * type. 648 */ 649 public Integer getIntegerClaim(final String name) 650 throws ParseException { 651 652 Object value = getClaim(name); 653 654 if (value == null) { 655 return null; 656 } else if (value instanceof Number) { 657 return ((Number)value).intValue(); 658 } else { 659 throw new ParseException("The \"" + name + "\" claim is not an Integer", 0); 660 } 661 } 662 663 664 /** 665 * Gets the specified claim (registered or custom) as 666 * {@link java.lang.Long}. 667 * 668 * @param name The name of the claim. Must not be {@code null}. 669 * 670 * @return The value of the claim, {@code null} if not specified. 671 * 672 * @throws ParseException If the claim value is not of the required 673 * type. 674 */ 675 public Long getLongClaim(final String name) 676 throws ParseException { 677 678 Object value = getClaim(name); 679 680 if (value == null) { 681 return null; 682 } else if (value instanceof Number) { 683 return ((Number)value).longValue(); 684 } else { 685 throw new ParseException("The \"" + name + "\" claim is not a Number", 0); 686 } 687 } 688 689 690 /** 691 * Gets the specified claim (registered or custom) as 692 * {@link java.util.Date}. The claim may be represented by a Date 693 * object or a number of a seconds since the Unix epoch. 694 * 695 * @param name The name of the claim. Must not be {@code null}. 696 * 697 * @return The value of the claim, {@code null} if not specified. 698 * 699 * @throws ParseException If the claim value is not of the required 700 * type. 701 */ 702 public Date getDateClaim(final String name) 703 throws ParseException { 704 705 Object value = getClaim(name); 706 707 if (value == null) { 708 return null; 709 } else if (value instanceof Date) { 710 return (Date)value; 711 } else if (value instanceof Number) { 712 return DateUtils.fromSecondsSinceEpoch(((Number)value).longValue()); 713 } else { 714 throw new ParseException("The \"" + name + "\" claim is not a Date", 0); 715 } 716 } 717 718 719 /** 720 * Gets the specified claim (registered or custom) as 721 * {@link java.lang.Float}. 722 * 723 * @param name The name of the claim. Must not be {@code null}. 724 * 725 * @return The value of the claim, {@code null} if not specified. 726 * 727 * @throws ParseException If the claim value is not of the required 728 * type. 729 */ 730 public Float getFloatClaim(final String name) 731 throws ParseException { 732 733 Object value = getClaim(name); 734 735 if (value == null) { 736 return null; 737 } else if (value instanceof Number) { 738 return ((Number)value).floatValue(); 739 } else { 740 throw new ParseException("The \"" + name + "\" claim is not a Float", 0); 741 } 742 } 743 744 745 /** 746 * Gets the specified claim (registered or custom) as 747 * {@link java.lang.Double}. 748 * 749 * @param name The name of the claim. Must not be {@code null}. 750 * 751 * @return The value of the claim, {@code null} if not specified. 752 * 753 * @throws ParseException If the claim value is not of the required 754 * type. 755 */ 756 public Double getDoubleClaim(final String name) 757 throws ParseException { 758 759 Object value = getClaim(name); 760 761 if (value == null) { 762 return null; 763 } else if (value instanceof Number) { 764 return ((Number)value).doubleValue(); 765 } else { 766 throw new ParseException("The \"" + name + "\" claim is not a Double", 0); 767 } 768 } 769 770 771 /** 772 * Gets the specified claim (registered or custom) as a JSON object. 773 * 774 * @param name The name of the claim. Must not be {@code null}. 775 * 776 * @return The value of the claim, {@code null} if not specified. 777 * 778 * @throws ParseException If the claim value is not of the required 779 * type. 780 */ 781 public Map<String, Object> getJSONObjectClaim(final String name) 782 throws ParseException { 783 784 Object value = getClaim(name); 785 786 if (value == null) { 787 return null; 788 } else if (value instanceof Map) { 789 Map<String, Object> jsonObject = JSONObjectUtils.newJSONObject(); 790 Map<?,?> map = (Map<?,?>)value; 791 for (Map.Entry<?,?> entry: map.entrySet()) { 792 if (entry.getKey() instanceof String) { 793 jsonObject.put((String)entry.getKey(), entry.getValue()); 794 } 795 } 796 return jsonObject; 797 } else { 798 throw new ParseException("The \"" + name + "\" claim is not a JSON object or Map", 0); 799 } 800 } 801 802 803 /** 804 * Gets the claims (registered and custom). 805 * 806 * <p>Note that the registered claims Expiration-Time ({@code exp}), 807 * Not-Before-Time ({@code nbf}) and Issued-At ({@code iat}) will be 808 * returned as {@code java.util.Date} instances. 809 * 810 * @return The claims, as an unmodifiable map, empty map if none. 811 */ 812 public Map<String,Object> getClaims() { 813 814 return Collections.unmodifiableMap(claims); 815 } 816 817 818 /** 819 * Returns a JOSE object payload representation of this claims set. 820 * 821 * @return The payload representation. 822 */ 823 public Payload toPayload() { 824 825 return new Payload(toJSONObject()); 826 } 827 828 829 /** 830 * Returns the JSON object representation of this claims set. The 831 * claims are serialised according to their insertion order. Claims 832 * with {@code null} values are not output. 833 * 834 * @return The JSON object representation. 835 */ 836 public Map<String, Object> toJSONObject() { 837 838 return toJSONObject(false); 839 } 840 841 842 /** 843 * Returns the JSON object representation of this claims set. The 844 * claims are serialised according to their insertion order. 845 * 846 * @param includeClaimsWithNullValues If {@code true} claims with 847 * {@code null} values will also be 848 * output. 849 * 850 * @return The JSON object representation. 851 */ 852 public Map<String, Object> toJSONObject(final boolean includeClaimsWithNullValues) { 853 854 Map<String, Object> o = JSONObjectUtils.newJSONObject(); 855 856 for (Map.Entry<String,Object> claim: claims.entrySet()) { 857 858 if (claim.getValue() instanceof Date) { 859 860 // Transform dates to Unix timestamps 861 Date dateValue = (Date) claim.getValue(); 862 o.put(claim.getKey(), DateUtils.toSecondsSinceEpoch(dateValue)); 863 864 } else if (JWTClaimNames.AUDIENCE.equals(claim.getKey())) { 865 866 // Serialise single audience list and string 867 List<String> audList = getAudience(); 868 869 if (audList != null && ! audList.isEmpty()) { 870 if (audList.size() == 1) { 871 o.put(JWTClaimNames.AUDIENCE, audList.get(0)); 872 } else { 873 List<Object> audArray = JSONArrayUtils.newJSONArray(); 874 audArray.addAll(audList); 875 o.put(JWTClaimNames.AUDIENCE, audArray); 876 } 877 } else if (includeClaimsWithNullValues) { 878 o.put(JWTClaimNames.AUDIENCE, null); 879 } 880 881 } else if (claim.getValue() != null) { 882 o.put(claim.getKey(), claim.getValue()); 883 } else if (includeClaimsWithNullValues) { 884 o.put(claim.getKey(), null); 885 } 886 } 887 888 return o; 889 } 890 891 892 /** 893 * Returns a JSON object string representation of this claims set. The 894 * claims are serialised according to their insertion order. Claims 895 * with {@code null} values are not output. 896 * 897 * @return The JSON object string representation. 898 */ 899 @Override 900 public String toString() { 901 902 return JSONObjectUtils.toJSONString(toJSONObject()); 903 } 904 905 906 /** 907 * Returns a JSON object string representation of this claims set. The 908 * claims are serialised according to their insertion order. 909 * 910 * @param includeClaimsWithNullValues If {@code true} claims with 911 * {@code null} values will also be 912 * output. 913 * 914 * @return The JSON object string representation. 915 */ 916 public String toString(final boolean includeClaimsWithNullValues) { 917 918 return JSONObjectUtils.toJSONString(toJSONObject(includeClaimsWithNullValues)); 919 } 920 921 922 /** 923 * Returns a transformation of this JWT claims set. 924 * 925 * @param <T> Type of the result. 926 * @param transformer The JWT claims set transformer. Must not be 927 * {@code null}. 928 * 929 * @return The transformed JWT claims set. 930 */ 931 public <T> T toType(final JWTClaimsSetTransformer<T> transformer) { 932 933 return transformer.transform(this); 934 } 935 936 937 /** 938 * Parses a JSON Web Token (JWT) claims set from the specified JSON 939 * object representation. 940 * 941 * @param json The JSON object to parse. Must not be {@code null}. 942 * 943 * @return The JWT claims set. 944 * 945 * @throws ParseException If the specified JSON object doesn't 946 * represent a valid JWT claims set. 947 */ 948 public static JWTClaimsSet parse(final Map<String, Object> json) 949 throws ParseException { 950 951 JWTClaimsSet.Builder builder = new JWTClaimsSet.Builder(); 952 953 // Parse registered + custom params 954 for (final String name: json.keySet()) { 955 956 switch (name) { 957 case JWTClaimNames.ISSUER: 958 builder.issuer(JSONObjectUtils.getString(json, JWTClaimNames.ISSUER)); 959 break; 960 case JWTClaimNames.SUBJECT: 961 Object subValue = json.get(JWTClaimNames.SUBJECT); 962 if (subValue instanceof String) { 963 builder.subject(JSONObjectUtils.getString(json, JWTClaimNames.SUBJECT)); 964 } else if (subValue instanceof Number) { 965 // Numbers not allowed per JWT spec, compromise 966 // to enable interop with non-compliant libs 967 // https://www.rfc-editor.org/rfc/rfc7519#section-4.1.2 968 builder.subject(String.valueOf(subValue)); 969 } else if (subValue == null) { 970 builder.subject(null); 971 } else { 972 throw new ParseException("Unexpected type of " + JWTClaimNames.SUBJECT + " claim", 0); 973 } 974 break; 975 case JWTClaimNames.AUDIENCE: 976 Object audValue = json.get(JWTClaimNames.AUDIENCE); 977 if (audValue instanceof String) { 978 List<String> singleAud = new ArrayList<>(); 979 singleAud.add(JSONObjectUtils.getString(json, JWTClaimNames.AUDIENCE)); 980 builder.audience(singleAud); 981 } else if (audValue instanceof List) { 982 builder.audience(JSONObjectUtils.getStringList(json, JWTClaimNames.AUDIENCE)); 983 } else if (audValue == null) { 984 builder.audience((String) null); 985 } else { 986 throw new ParseException("Unexpected type of " + JWTClaimNames.AUDIENCE + " claim", 0); 987 } 988 break; 989 case JWTClaimNames.EXPIRATION_TIME: 990 builder.expirationTime(new Date(JSONObjectUtils.getLong(json, JWTClaimNames.EXPIRATION_TIME) * 1000)); 991 break; 992 case JWTClaimNames.NOT_BEFORE: 993 builder.notBeforeTime(new Date(JSONObjectUtils.getLong(json, JWTClaimNames.NOT_BEFORE) * 1000)); 994 break; 995 case JWTClaimNames.ISSUED_AT: 996 builder.issueTime(new Date(JSONObjectUtils.getLong(json, JWTClaimNames.ISSUED_AT) * 1000)); 997 break; 998 case JWTClaimNames.JWT_ID: 999 builder.jwtID(JSONObjectUtils.getString(json, JWTClaimNames.JWT_ID)); 1000 break; 1001 default: 1002 builder.claim(name, json.get(name)); 1003 break; 1004 } 1005 } 1006 1007 return builder.build(); 1008 } 1009 1010 1011 /** 1012 * Parses a JSON Web Token (JWT) claims set from the specified JSON 1013 * object string representation. 1014 * 1015 * @param s The JSON object string to parse. Must not be {@code null}. 1016 * 1017 * @return The JWT claims set. 1018 * 1019 * @throws ParseException If the specified JSON object string doesn't 1020 * represent a valid JWT claims set. 1021 */ 1022 public static JWTClaimsSet parse(final String s) 1023 throws ParseException { 1024 1025 return parse(JSONObjectUtils.parse(s)); 1026 } 1027 1028 1029 @Override 1030 public boolean equals(Object o) { 1031 if (this == o) return true; 1032 if (!(o instanceof JWTClaimsSet)) return false; 1033 JWTClaimsSet that = (JWTClaimsSet) o; 1034 return Objects.equals(claims, that.claims); 1035 } 1036 1037 1038 @Override 1039 public int hashCode() { 1040 return Objects.hash(claims); 1041 } 1042}