001package com.nimbusds.openid.connect.sdk.claims; 002 003 004import java.util.*; 005 006import net.minidev.json.JSONArray; 007import net.minidev.json.JSONObject; 008 009import com.nimbusds.jose.jwk.JWK; 010import com.nimbusds.jwt.JWTClaimsSet; 011 012import com.nimbusds.oauth2.sdk.ParseException; 013import com.nimbusds.oauth2.sdk.ResponseType; 014import com.nimbusds.oauth2.sdk.id.Audience; 015import com.nimbusds.oauth2.sdk.id.Issuer; 016import com.nimbusds.oauth2.sdk.id.Subject; 017import com.nimbusds.oauth2.sdk.util.JSONObjectUtils; 018 019import com.nimbusds.openid.connect.sdk.Nonce; 020 021 022/** 023 * ID token claims set, serialisable to a JSON object. 024 * 025 * <p>Example ID token claims set: 026 * 027 * <pre> 028 * { 029 * "iss" : "https://server.example.com", 030 * "sub" : "24400320", 031 * "aud" : "s6BhdRkqt3", 032 * "nonce" : "n-0S6_WzA2Mj", 033 * "exp" : 1311281970, 034 * "iat" : 1311280970, 035 * "auth_time" : 1311280969, 036 * "acr" : "urn:mace:incommon:iap:silver", 037 * "at_hash" : "MTIzNDU2Nzg5MDEyMzQ1Ng" 038 * } 039 * </pre> 040 * 041 * <p>Related specifications: 042 * 043 * <ul> 044 * <li>OpenID Connect Core 1.0, section 2. 045 * </ul> 046 */ 047public class IDTokenClaimsSet extends ClaimsSet { 048 049 050 /** 051 * The issuer claim name. 052 */ 053 public static final String ISS_CLAIM_NAME = "iss"; 054 055 056 /** 057 * The subject claim name. 058 */ 059 public static final String SUB_CLAIM_NAME = "sub"; 060 061 062 /** 063 * The audience claim name. 064 */ 065 public static final String AUD_CLAIM_NAME = "aud"; 066 067 068 /** 069 * The expiration time claim name. 070 */ 071 public static final String EXP_CLAIM_NAME = "exp"; 072 073 074 /** 075 * The issue time claim name. 076 */ 077 public static final String IAT_CLAIM_NAME = "iat"; 078 079 080 /** 081 * The subject authentication time claim name. 082 */ 083 public static final String AUTH_TIME_CLAIM_NAME = "auth_time"; 084 085 086 /** 087 * The nonce claim name. 088 */ 089 public static final String NONCE_CLAIM_NAME = "nonce"; 090 091 092 /** 093 * The access token hash claim name. 094 */ 095 public static final String AT_HASH_CLAIM_NAME = "at_hash"; 096 097 098 /** 099 * The authorisation code hash claim name. 100 */ 101 public static final String C_HASH_CLAIM_NAME = "c_hash"; 102 103 104 /** 105 * The ACR claim name. 106 */ 107 public static final String ACR_CLAIM_NAME = "acr"; 108 109 110 /** 111 * The AMRs claim name. 112 */ 113 public static final String AMR_CLAIM_NAME = "amr"; 114 115 116 /** 117 * The authorised party claim name. 118 */ 119 public static final String AZP_CLAIM_NAME = "azp"; 120 121 122 /** 123 * The subject JWK claim name. 124 */ 125 public static final String SUB_JWK_CLAIM_NAME = "sub_jwk"; 126 127 128 /** 129 * The names of the standard top-level ID token claims. 130 */ 131 private static final Set<String> stdClaimNames = new LinkedHashSet<>(); 132 133 134 static { 135 stdClaimNames.add(ISS_CLAIM_NAME); 136 stdClaimNames.add(SUB_CLAIM_NAME); 137 stdClaimNames.add(AUD_CLAIM_NAME); 138 stdClaimNames.add(EXP_CLAIM_NAME); 139 stdClaimNames.add(IAT_CLAIM_NAME); 140 stdClaimNames.add(AUTH_TIME_CLAIM_NAME); 141 stdClaimNames.add(NONCE_CLAIM_NAME); 142 stdClaimNames.add(AT_HASH_CLAIM_NAME); 143 stdClaimNames.add(C_HASH_CLAIM_NAME); 144 stdClaimNames.add(ACR_CLAIM_NAME); 145 stdClaimNames.add(AMR_CLAIM_NAME); 146 stdClaimNames.add(AZP_CLAIM_NAME); 147 stdClaimNames.add(SUB_JWK_CLAIM_NAME); 148 } 149 150 151 /** 152 * Gets the names of the standard top-level ID token claims. 153 * 154 * @return The names of the standard top-level ID token claims 155 * (read-only set). 156 */ 157 public static Set<String> getStandardClaimNames() { 158 159 return Collections.unmodifiableSet(stdClaimNames); 160 } 161 162 163 /** 164 * Creates a new minimal ID token claims set. Note that the ID token 165 * may require additional claims to be present depending on the 166 * original OpenID Connect authorisation request. 167 * 168 * @param iss The issuer. Must not be {@code null}. 169 * @param sub The subject. Must not be {@code null}. 170 * @param aud The audience. Must not be {@code null}. 171 * @param exp The expiration time. Must not be {@code null}. 172 * @param iat The issue time. Must not be {@code null}. 173 */ 174 public IDTokenClaimsSet(final Issuer iss, 175 final Subject sub, 176 final List<Audience> aud, 177 final Date exp, 178 final Date iat) { 179 180 setClaim(ISS_CLAIM_NAME, iss.getValue()); 181 setClaim(SUB_CLAIM_NAME, sub.getValue()); 182 183 JSONArray audList = new JSONArray(); 184 185 for (Audience a: aud) 186 audList.add(a.getValue()); 187 188 setClaim(AUD_CLAIM_NAME, audList); 189 190 setDateClaim(EXP_CLAIM_NAME, exp); 191 setDateClaim(IAT_CLAIM_NAME, iat); 192 } 193 194 195 /** 196 * Creates a new ID token claims set from the specified JSON object. 197 * 198 * @param jsonObject The JSON object. Must be verified to represent a 199 * valid ID token claims set and not {@code null}. 200 * 201 * @throws ParseException If the JSON object doesn't contain the 202 * minimally required issuer {@code iss}, 203 * subject {@code sub}, audience list 204 * {@code aud}, expiration date {@code exp} and 205 * issue date {@code iat} claims. 206 */ 207 private IDTokenClaimsSet(final JSONObject jsonObject) 208 throws ParseException { 209 210 super(jsonObject); 211 212 if (getStringClaim(ISS_CLAIM_NAME) == null) 213 throw new ParseException("Missing or invalid \"iss\" claim"); 214 215 if (getStringClaim(SUB_CLAIM_NAME) == null) 216 throw new ParseException("Missing or invalid \"sub\" claim"); 217 218 if (getStringClaim(AUD_CLAIM_NAME) == null && getStringListClaim(AUD_CLAIM_NAME) == null || 219 getStringListClaim(AUD_CLAIM_NAME) != null && getStringListClaim(AUD_CLAIM_NAME).isEmpty()) 220 throw new ParseException("Missing or invalid \"aud\" claim"); 221 222 if (getDateClaim(EXP_CLAIM_NAME) == null) 223 throw new ParseException("Missing or invalid \"exp\" claim"); 224 225 if (getDateClaim(IAT_CLAIM_NAME) == null) 226 throw new ParseException("Missing or invalid \"iat\" claim"); 227 } 228 229 230 /** 231 * Creates a new ID token claims set from the specified JSON Web Token 232 * (JWT) claims set. 233 * 234 * @param jwtClaimsSet The JWT claims set. Must not be {@code null}. 235 * 236 * @throws ParseException If the JSON object doesn't contain the 237 * minimally required issuer {@code iss}, 238 * subject {@code sub}, audience list 239 * {@code aud}, expiration date {@code exp} and 240 * issue date {@code iat} claims. 241 */ 242 public IDTokenClaimsSet(final JWTClaimsSet jwtClaimsSet) 243 throws ParseException { 244 245 this(jwtClaimsSet.toJSONObject()); 246 } 247 248 249 /** 250 * Checks if this ID token claims set contains all required claims for 251 * the specified OpenID Connect response type. 252 * 253 * @param responseType The OpenID Connect response type. Must not 254 * be {@code null}. 255 * @param iatAuthzEndpoint Specifies the endpoint where the ID token 256 * was issued (required for hybrid flow). 257 * {@code true} if the ID token was issued at 258 * the authorisation endpoint, {@code false} if 259 * the ID token was issued at the token 260 * endpoint. 261 * 262 * @return {@code true} if the required claims are contained, else 263 * {@code false}. 264 */ 265 public boolean hasRequiredClaims(final ResponseType responseType, final boolean iatAuthzEndpoint) { 266 267 // Code flow 268 // See http://openid.net/specs/openid-connect-core-1_0.html#CodeIDToken 269 if (new ResponseType("code").equals(responseType)) { 270 // nonce, c_hash and at_hash not required 271 return true; // ok 272 } 273 274 // Implicit flow 275 // See http://openid.net/specs/openid-connect-core-1_0.html#ImplicitIDToken 276 if (new ResponseType("id_token").equals(responseType)) { 277 278 if (getNonce() == null) { 279 // nonce required 280 return false; 281 } 282 283 return true; // ok 284 } 285 286 if (new ResponseType("id_token", "token").equals(responseType)) { 287 288 if (getNonce() == null) { 289 // nonce required 290 return false; 291 } 292 293 if (getAccessTokenHash() == null) { 294 // at_hash required 295 return false; 296 } 297 298 return true; // ok 299 } 300 301 // Hybrid flow 302 // See http://openid.net/specs/openid-connect-core-1_0.html#HybridIDToken 303 if (new ResponseType("code", "id_token").equals(responseType)) { 304 305 if (getNonce() == null) { 306 // nonce required 307 return false; 308 } 309 310 if (! iatAuthzEndpoint) { 311 // c_hash and at_hash not required when id_token issued at token endpoint 312 // See http://openid.net/specs/openid-connect-core-1_0.html#HybridIDToken2 313 return true; 314 } 315 316 if (getCodeHash() == null) { 317 // c_hash required when issued at authz endpoint 318 return false; 319 } 320 321 return true; // ok 322 } 323 324 if (new ResponseType("code", "token").equals(responseType)) { 325 326 if (getNonce() == null) { 327 // nonce required 328 return false; 329 } 330 331 if (! iatAuthzEndpoint) { 332 // c_hash and at_hash not required when id_token issued at token endpoint 333 // See http://openid.net/specs/openid-connect-core-1_0.html#HybridIDToken2 334 return true; 335 } 336 337 return true; // ok 338 } 339 340 if (new ResponseType("code", "id_token", "token").equals(responseType)) { 341 342 if (getNonce() == null) { 343 // nonce required 344 return false; 345 } 346 347 if (! iatAuthzEndpoint) { 348 // c_hash and at_hash not required when id_token issued at token endpoint 349 // See http://openid.net/specs/openid-connect-core-1_0.html#HybridIDToken2 350 return true; 351 } 352 353 if (getAccessTokenHash() == null) { 354 // at_hash required when issued at authz endpoint 355 return false; 356 } 357 358 if (getCodeHash() == null) { 359 // c_hash required when issued at authz endpoint 360 return false; 361 } 362 363 return true; // ok 364 } 365 366 throw new IllegalArgumentException("Unsupported response_type: " + responseType); 367 } 368 369 370 /** 371 * Use {@link #hasRequiredClaims(ResponseType, boolean)} instead. 372 * 373 * @param responseType The OpenID Connect response type. Must not be 374 * {@code null}. 375 * 376 * @return {@code true} if the required claims are contained, else 377 * {@code false}. 378 */ 379 @Deprecated 380 public boolean hasRequiredClaims(final ResponseType responseType) { 381 382 return hasRequiredClaims(responseType, true); 383 } 384 385 386 /** 387 * Gets the ID token issuer. Corresponds to the {@code iss} claim. 388 * 389 * @return The issuer. 390 */ 391 public Issuer getIssuer() { 392 393 return new Issuer(getStringClaim(ISS_CLAIM_NAME)); 394 } 395 396 397 /** 398 * Gets the ID token subject. Corresponds to the {@code sub} claim. 399 * 400 * @return The subject. 401 */ 402 public Subject getSubject() { 403 404 return new Subject(getStringClaim(SUB_CLAIM_NAME)); 405 } 406 407 408 /** 409 * Gets the ID token audience. Corresponds to the {@code aud} claim. 410 * 411 * @return The audience. 412 */ 413 public List<Audience> getAudience() { 414 415 if (getClaim(AUD_CLAIM_NAME) instanceof String) { 416 // Special case - aud is a string 417 return new Audience(getStringClaim(AUD_CLAIM_NAME)).toSingleAudienceList(); 418 } 419 420 // General case - JSON string array 421 List<String> rawList = getStringListClaim(AUD_CLAIM_NAME); 422 423 List<Audience> audList = new ArrayList<>(rawList.size()); 424 425 for (String s: rawList) 426 audList.add(new Audience(s)); 427 428 return audList; 429 } 430 431 432 /** 433 * Gets the ID token expiration time. Corresponds to the {@code exp} 434 * claim. 435 * 436 * @return The expiration time. 437 */ 438 public Date getExpirationTime() { 439 440 return getDateClaim(EXP_CLAIM_NAME); 441 } 442 443 444 /** 445 * Gets the ID token issue time. Corresponds to the {@code iss} claim. 446 * 447 * @return The issue time. 448 */ 449 public Date getIssueTime() { 450 451 return getDateClaim(IAT_CLAIM_NAME); 452 } 453 454 455 /** 456 * Gets the subject authentication time. Corresponds to the 457 * {@code auth_time} claim. 458 * 459 * @return The authentication time, {@code null} if not specified or 460 * parsing failed. 461 */ 462 public Date getAuthenticationTime() { 463 464 return getDateClaim(AUTH_TIME_CLAIM_NAME); 465 } 466 467 468 /** 469 * Sets the subject authentication time. Corresponds to the 470 * {@code auth_time} claim. 471 * 472 * @param authTime The authentication time, {@code null} if not 473 * specified. 474 */ 475 public void setAuthenticationTime(final Date authTime) { 476 477 setDateClaim(AUTH_TIME_CLAIM_NAME, authTime); 478 } 479 480 481 /** 482 * Gets the ID token nonce. Corresponds to the {@code nonce} claim. 483 * 484 * @return The nonce, {@code null} if not specified or parsing failed. 485 */ 486 public Nonce getNonce() { 487 488 String value = getStringClaim(NONCE_CLAIM_NAME); 489 return value != null ? new Nonce(value) : null; 490 } 491 492 493 /** 494 * Sets the ID token nonce. Corresponds to the {@code nonce} claim. 495 * 496 * @param nonce The nonce, {@code null} if not specified. 497 */ 498 public void setNonce(final Nonce nonce) { 499 500 if (nonce != null) 501 setClaim(NONCE_CLAIM_NAME, nonce.getValue()); 502 else 503 setClaim(NONCE_CLAIM_NAME, null); 504 } 505 506 507 /** 508 * Gets the access token hash. Corresponds to the {@code at_hash} 509 * claim. 510 * 511 * @return The access token hash, {@code null} if not specified or 512 * parsing failed. 513 */ 514 public AccessTokenHash getAccessTokenHash() { 515 516 String value = getStringClaim(AT_HASH_CLAIM_NAME); 517 return value != null ? new AccessTokenHash(value) : null; 518 } 519 520 521 /** 522 * Sets the access token hash. Corresponds to the {@code at_hash} 523 * claim. 524 * 525 * @param atHash The access token hash, {@code null} if not specified. 526 */ 527 public void setAccessTokenHash(final AccessTokenHash atHash) { 528 529 if (atHash != null) 530 setClaim(AT_HASH_CLAIM_NAME, atHash.getValue()); 531 else 532 setClaim(AT_HASH_CLAIM_NAME, null); 533 } 534 535 536 /** 537 * Gets the authorisation code hash. Corresponds to the {@code c_hash} 538 * claim. 539 * 540 * @return The authorisation code hash, {@code null} if not specified 541 * or parsing failed. 542 */ 543 public CodeHash getCodeHash() { 544 545 String value = getStringClaim(C_HASH_CLAIM_NAME); 546 return value != null ? new CodeHash(value) : null; 547 } 548 549 550 /** 551 * Sets the authorisation code hash. Corresponds to the {@code c_hash} 552 * claim. 553 * 554 * @param cHash The authorisation code hash, {@code null} if not 555 * specified. 556 */ 557 public void setCodeHash(final CodeHash cHash) { 558 559 if (cHash != null) 560 setClaim(C_HASH_CLAIM_NAME, cHash.getValue()); 561 else 562 setClaim(C_HASH_CLAIM_NAME, null); 563 } 564 565 566 /** 567 * Gets the Authentication Context Class Reference (ACR). Corresponds 568 * to the {@code acr} claim. 569 * 570 * @return The Authentication Context Class Reference (ACR), 571 * {@code null} if not specified or parsing failed. 572 */ 573 public ACR getACR() { 574 575 String value = getStringClaim(ACR_CLAIM_NAME); 576 return value != null ? new ACR(value) : null; 577 } 578 579 580 /** 581 * Sets the Authentication Context Class Reference (ACR). Corresponds 582 * to the {@code acr} claim. 583 * 584 * @param acr The Authentication Context Class Reference (ACR), 585 * {@code null} if not specified. 586 */ 587 public void setACR(final ACR acr) { 588 589 if (acr != null) 590 setClaim(ACR_CLAIM_NAME, acr.getValue()); 591 else 592 setClaim(ACR_CLAIM_NAME, null); 593 } 594 595 596 /** 597 * Gets the Authentication Methods References (AMRs). Corresponds to 598 * the {@code amr} claim. 599 * 600 * @return The Authentication Methods Reference (AMR) list, 601 * {@code null} if not specified or parsing failed. 602 */ 603 public List<AMR> getAMR() { 604 605 List<String> rawList = getStringListClaim(AMR_CLAIM_NAME); 606 607 if (rawList == null || rawList.isEmpty()) 608 return null; 609 610 List<AMR> amrList = new ArrayList<>(rawList.size()); 611 612 for (String s: rawList) 613 amrList.add(new AMR(s)); 614 615 return amrList; 616 } 617 618 619 /** 620 * Sets the Authentication Methods References (AMRs). Corresponds to 621 * the {@code amr} claim. 622 * 623 * @param amr The Authentication Methods Reference (AMR) list, 624 * {@code null} if not specified. 625 */ 626 public void setAMR(final List<AMR> amr) { 627 628 if (amr != null) { 629 630 List<String> amrList = new ArrayList<>(amr.size()); 631 632 for (AMR a: amr) 633 amrList.add(a.getValue()); 634 635 setClaim(AMR_CLAIM_NAME, amrList); 636 637 } else { 638 setClaim(AMR_CLAIM_NAME, null); 639 } 640 } 641 642 643 /** 644 * Gets the authorised party for the ID token. Corresponds to the 645 * {@code azp} claim. 646 * 647 * @return The authorised party, {@code null} if not specified or 648 * parsing failed. 649 */ 650 public AuthorizedParty getAuthorizedParty() { 651 652 String value = getStringClaim(AZP_CLAIM_NAME); 653 return value != null ? new AuthorizedParty(value) : null; 654 } 655 656 657 /** 658 * Sets the authorised party for the ID token. Corresponds to the 659 * {@code azp} claim. 660 * 661 * @param azp The authorised party, {@code null} if not specified. 662 */ 663 public void setAuthorizedParty(final AuthorizedParty azp) { 664 665 if (azp != null) 666 setClaim(AZP_CLAIM_NAME, azp.getValue()); 667 else 668 setClaim(AZP_CLAIM_NAME, null); 669 } 670 671 672 /** 673 * Gets the subject's JSON Web Key (JWK) for a self-issued OpenID 674 * Connect provider. Corresponds to the {@code sub_jwk} claim. 675 * 676 * @return The subject's JWK, {@code null} if not specified or parsing 677 * failed. 678 */ 679 public JWK getSubjectJWK() { 680 681 JSONObject jsonObject = getClaim(SUB_JWK_CLAIM_NAME, JSONObject.class); 682 683 if (jsonObject == null) 684 return null; 685 686 try { 687 return JWK.parse(jsonObject); 688 689 } catch (java.text.ParseException e) { 690 691 return null; 692 } 693 } 694 695 696 /** 697 * Sets the subject's JSON Web Key (JWK) for a self-issued OpenID 698 * Connect provider. Corresponds to the {@code sub_jwk} claim. 699 * 700 * @param subJWK The subject's JWK (must be public), {@code null} if 701 * not specified. 702 */ 703 public void setSubjectJWK(final JWK subJWK) { 704 705 if (subJWK != null) { 706 707 if (subJWK.isPrivate()) 708 throw new IllegalArgumentException("The subject's JSON Web Key (JWK) must be public"); 709 710 setClaim(SUB_JWK_CLAIM_NAME, subJWK.toJSONObject()); 711 712 } else { 713 setClaim(SUB_JWK_CLAIM_NAME, null); 714 } 715 } 716 717 718 /** 719 * Parses an ID token claims set from the specified JSON object string. 720 * 721 * @param json The JSON object string to parse. Must not be 722 * {@code null}. 723 * 724 * @return The ID token claims set. 725 * 726 * @throws ParseException If parsing failed. 727 */ 728 public static IDTokenClaimsSet parse(final String json) 729 throws ParseException { 730 731 JSONObject jsonObject = JSONObjectUtils.parse(json); 732 733 try { 734 return new IDTokenClaimsSet(jsonObject); 735 736 } catch (IllegalArgumentException e) { 737 738 throw new ParseException(e.getMessage(), e); 739 } 740 } 741}