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 return getNonce() != null; 279 280 } 281 282 if (new ResponseType("id_token", "token").equals(responseType)) { 283 284 if (getNonce() == null) { 285 // nonce required 286 return false; 287 } 288 289 return getAccessTokenHash() != null; 290 291 } 292 293 // Hybrid flow 294 // See http://openid.net/specs/openid-connect-core-1_0.html#HybridIDToken 295 if (new ResponseType("code", "id_token").equals(responseType)) { 296 297 if (getNonce() == null) { 298 // nonce required 299 return false; 300 } 301 302 if (! iatAuthzEndpoint) { 303 // c_hash and at_hash not required when id_token issued at token endpoint 304 // See http://openid.net/specs/openid-connect-core-1_0.html#HybridIDToken2 305 return true; 306 } 307 308 return getCodeHash() != null; 309 310 } 311 312 if (new ResponseType("code", "token").equals(responseType)) { 313 314 if (getNonce() == null) { 315 // nonce required 316 return false; 317 } 318 319 if (! iatAuthzEndpoint) { 320 // c_hash and at_hash not required when id_token issued at token endpoint 321 // See http://openid.net/specs/openid-connect-core-1_0.html#HybridIDToken2 322 return true; 323 } 324 325 return true; // ok 326 } 327 328 if (new ResponseType("code", "id_token", "token").equals(responseType)) { 329 330 if (getNonce() == null) { 331 // nonce required 332 return false; 333 } 334 335 if (! iatAuthzEndpoint) { 336 // c_hash and at_hash not required when id_token issued at token endpoint 337 // See http://openid.net/specs/openid-connect-core-1_0.html#HybridIDToken2 338 return true; 339 } 340 341 if (getAccessTokenHash() == null) { 342 // at_hash required when issued at authz endpoint 343 return false; 344 } 345 346 return getCodeHash() != null; 347 348 } 349 350 throw new IllegalArgumentException("Unsupported response_type: " + responseType); 351 } 352 353 354 /** 355 * Use {@link #hasRequiredClaims(ResponseType, boolean)} instead. 356 * 357 * @param responseType The OpenID Connect response type. Must not be 358 * {@code null}. 359 * 360 * @return {@code true} if the required claims are contained, else 361 * {@code false}. 362 */ 363 @Deprecated 364 public boolean hasRequiredClaims(final ResponseType responseType) { 365 366 return hasRequiredClaims(responseType, true); 367 } 368 369 370 /** 371 * Gets the ID token issuer. Corresponds to the {@code iss} claim. 372 * 373 * @return The issuer. 374 */ 375 public Issuer getIssuer() { 376 377 return new Issuer(getStringClaim(ISS_CLAIM_NAME)); 378 } 379 380 381 /** 382 * Gets the ID token subject. Corresponds to the {@code sub} claim. 383 * 384 * @return The subject. 385 */ 386 public Subject getSubject() { 387 388 return new Subject(getStringClaim(SUB_CLAIM_NAME)); 389 } 390 391 392 /** 393 * Gets the ID token audience. Corresponds to the {@code aud} claim. 394 * 395 * @return The audience. 396 */ 397 public List<Audience> getAudience() { 398 399 if (getClaim(AUD_CLAIM_NAME) instanceof String) { 400 // Special case - aud is a string 401 return new Audience(getStringClaim(AUD_CLAIM_NAME)).toSingleAudienceList(); 402 } 403 404 // General case - JSON string array 405 List<String> rawList = getStringListClaim(AUD_CLAIM_NAME); 406 407 List<Audience> audList = new ArrayList<>(rawList.size()); 408 409 for (String s: rawList) 410 audList.add(new Audience(s)); 411 412 return audList; 413 } 414 415 416 /** 417 * Gets the ID token expiration time. Corresponds to the {@code exp} 418 * claim. 419 * 420 * @return The expiration time. 421 */ 422 public Date getExpirationTime() { 423 424 return getDateClaim(EXP_CLAIM_NAME); 425 } 426 427 428 /** 429 * Gets the ID token issue time. Corresponds to the {@code iss} claim. 430 * 431 * @return The issue time. 432 */ 433 public Date getIssueTime() { 434 435 return getDateClaim(IAT_CLAIM_NAME); 436 } 437 438 439 /** 440 * Gets the subject authentication time. Corresponds to the 441 * {@code auth_time} claim. 442 * 443 * @return The authentication time, {@code null} if not specified or 444 * parsing failed. 445 */ 446 public Date getAuthenticationTime() { 447 448 return getDateClaim(AUTH_TIME_CLAIM_NAME); 449 } 450 451 452 /** 453 * Sets the subject authentication time. Corresponds to the 454 * {@code auth_time} claim. 455 * 456 * @param authTime The authentication time, {@code null} if not 457 * specified. 458 */ 459 public void setAuthenticationTime(final Date authTime) { 460 461 setDateClaim(AUTH_TIME_CLAIM_NAME, authTime); 462 } 463 464 465 /** 466 * Gets the ID token nonce. Corresponds to the {@code nonce} claim. 467 * 468 * @return The nonce, {@code null} if not specified or parsing failed. 469 */ 470 public Nonce getNonce() { 471 472 String value = getStringClaim(NONCE_CLAIM_NAME); 473 return value != null ? new Nonce(value) : null; 474 } 475 476 477 /** 478 * Sets the ID token nonce. Corresponds to the {@code nonce} claim. 479 * 480 * @param nonce The nonce, {@code null} if not specified. 481 */ 482 public void setNonce(final Nonce nonce) { 483 484 if (nonce != null) 485 setClaim(NONCE_CLAIM_NAME, nonce.getValue()); 486 else 487 setClaim(NONCE_CLAIM_NAME, null); 488 } 489 490 491 /** 492 * Gets the access token hash. Corresponds to the {@code at_hash} 493 * claim. 494 * 495 * @return The access token hash, {@code null} if not specified or 496 * parsing failed. 497 */ 498 public AccessTokenHash getAccessTokenHash() { 499 500 String value = getStringClaim(AT_HASH_CLAIM_NAME); 501 return value != null ? new AccessTokenHash(value) : null; 502 } 503 504 505 /** 506 * Sets the access token hash. Corresponds to the {@code at_hash} 507 * claim. 508 * 509 * @param atHash The access token hash, {@code null} if not specified. 510 */ 511 public void setAccessTokenHash(final AccessTokenHash atHash) { 512 513 if (atHash != null) 514 setClaim(AT_HASH_CLAIM_NAME, atHash.getValue()); 515 else 516 setClaim(AT_HASH_CLAIM_NAME, null); 517 } 518 519 520 /** 521 * Gets the authorisation code hash. Corresponds to the {@code c_hash} 522 * claim. 523 * 524 * @return The authorisation code hash, {@code null} if not specified 525 * or parsing failed. 526 */ 527 public CodeHash getCodeHash() { 528 529 String value = getStringClaim(C_HASH_CLAIM_NAME); 530 return value != null ? new CodeHash(value) : null; 531 } 532 533 534 /** 535 * Sets the authorisation code hash. Corresponds to the {@code c_hash} 536 * claim. 537 * 538 * @param cHash The authorisation code hash, {@code null} if not 539 * specified. 540 */ 541 public void setCodeHash(final CodeHash cHash) { 542 543 if (cHash != null) 544 setClaim(C_HASH_CLAIM_NAME, cHash.getValue()); 545 else 546 setClaim(C_HASH_CLAIM_NAME, null); 547 } 548 549 550 /** 551 * Gets the Authentication Context Class Reference (ACR). Corresponds 552 * to the {@code acr} claim. 553 * 554 * @return The Authentication Context Class Reference (ACR), 555 * {@code null} if not specified or parsing failed. 556 */ 557 public ACR getACR() { 558 559 String value = getStringClaim(ACR_CLAIM_NAME); 560 return value != null ? new ACR(value) : null; 561 } 562 563 564 /** 565 * Sets the Authentication Context Class Reference (ACR). Corresponds 566 * to the {@code acr} claim. 567 * 568 * @param acr The Authentication Context Class Reference (ACR), 569 * {@code null} if not specified. 570 */ 571 public void setACR(final ACR acr) { 572 573 if (acr != null) 574 setClaim(ACR_CLAIM_NAME, acr.getValue()); 575 else 576 setClaim(ACR_CLAIM_NAME, null); 577 } 578 579 580 /** 581 * Gets the Authentication Methods References (AMRs). Corresponds to 582 * the {@code amr} claim. 583 * 584 * @return The Authentication Methods Reference (AMR) list, 585 * {@code null} if not specified or parsing failed. 586 */ 587 public List<AMR> getAMR() { 588 589 List<String> rawList = getStringListClaim(AMR_CLAIM_NAME); 590 591 if (rawList == null || rawList.isEmpty()) 592 return null; 593 594 List<AMR> amrList = new ArrayList<>(rawList.size()); 595 596 for (String s: rawList) 597 amrList.add(new AMR(s)); 598 599 return amrList; 600 } 601 602 603 /** 604 * Sets the Authentication Methods References (AMRs). Corresponds to 605 * the {@code amr} claim. 606 * 607 * @param amr The Authentication Methods Reference (AMR) list, 608 * {@code null} if not specified. 609 */ 610 public void setAMR(final List<AMR> amr) { 611 612 if (amr != null) { 613 614 List<String> amrList = new ArrayList<>(amr.size()); 615 616 for (AMR a: amr) 617 amrList.add(a.getValue()); 618 619 setClaim(AMR_CLAIM_NAME, amrList); 620 621 } else { 622 setClaim(AMR_CLAIM_NAME, null); 623 } 624 } 625 626 627 /** 628 * Gets the authorised party for the ID token. Corresponds to the 629 * {@code azp} claim. 630 * 631 * @return The authorised party, {@code null} if not specified or 632 * parsing failed. 633 */ 634 public AuthorizedParty getAuthorizedParty() { 635 636 String value = getStringClaim(AZP_CLAIM_NAME); 637 return value != null ? new AuthorizedParty(value) : null; 638 } 639 640 641 /** 642 * Sets the authorised party for the ID token. Corresponds to the 643 * {@code azp} claim. 644 * 645 * @param azp The authorised party, {@code null} if not specified. 646 */ 647 public void setAuthorizedParty(final AuthorizedParty azp) { 648 649 if (azp != null) 650 setClaim(AZP_CLAIM_NAME, azp.getValue()); 651 else 652 setClaim(AZP_CLAIM_NAME, null); 653 } 654 655 656 /** 657 * Gets the subject's JSON Web Key (JWK) for a self-issued OpenID 658 * Connect provider. Corresponds to the {@code sub_jwk} claim. 659 * 660 * @return The subject's JWK, {@code null} if not specified or parsing 661 * failed. 662 */ 663 public JWK getSubjectJWK() { 664 665 JSONObject jsonObject = getClaim(SUB_JWK_CLAIM_NAME, JSONObject.class); 666 667 if (jsonObject == null) 668 return null; 669 670 try { 671 return JWK.parse(jsonObject); 672 673 } catch (java.text.ParseException e) { 674 675 return null; 676 } 677 } 678 679 680 /** 681 * Sets the subject's JSON Web Key (JWK) for a self-issued OpenID 682 * Connect provider. Corresponds to the {@code sub_jwk} claim. 683 * 684 * @param subJWK The subject's JWK (must be public), {@code null} if 685 * not specified. 686 */ 687 public void setSubjectJWK(final JWK subJWK) { 688 689 if (subJWK != null) { 690 691 if (subJWK.isPrivate()) 692 throw new IllegalArgumentException("The subject's JSON Web Key (JWK) must be public"); 693 694 setClaim(SUB_JWK_CLAIM_NAME, subJWK.toJSONObject()); 695 696 } else { 697 setClaim(SUB_JWK_CLAIM_NAME, null); 698 } 699 } 700 701 702 /** 703 * Parses an ID token claims set from the specified JSON object string. 704 * 705 * @param json The JSON object string to parse. Must not be 706 * {@code null}. 707 * 708 * @return The ID token claims set. 709 * 710 * @throws ParseException If parsing failed. 711 */ 712 public static IDTokenClaimsSet parse(final String json) 713 throws ParseException { 714 715 JSONObject jsonObject = JSONObjectUtils.parse(json); 716 717 try { 718 return new IDTokenClaimsSet(jsonObject); 719 720 } catch (IllegalArgumentException e) { 721 722 throw new ParseException(e.getMessage(), e); 723 } 724 } 725}