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 not be {@code null}. 199 * 200 * @throws IllegalArgumentException If the JSON object doesn't contain 201 * the minimally required issuer 202 * {@code iss}, subject {@code sub}, 203 * audience list {@code aud}, 204 * expiration date {@code exp} and 205 * issue date {@code iat} claims. 206 */ 207 public IDTokenClaimsSet(final JSONObject jsonObject) { 208 209 super(jsonObject); 210 211 if (getStringClaim(ISS_CLAIM_NAME) == null) 212 throw new IllegalArgumentException("Missing or invalid \"iss\" claim"); 213 214 if (getStringClaim(SUB_CLAIM_NAME) == null) 215 throw new IllegalArgumentException("Missing or invalid \"sub\" claim"); 216 217 if (getStringListClaim(AUD_CLAIM_NAME) == null) 218 throw new IllegalArgumentException("Missing or invalid \"aud\" claim"); 219 220 if (getDateClaim(EXP_CLAIM_NAME) == null) 221 throw new IllegalArgumentException("Missing or invalid \"exp\" claim"); 222 223 if (getDateClaim(IAT_CLAIM_NAME) == null) 224 throw new IllegalArgumentException("Missing or invalid \"iat\" claim"); 225 } 226 227 228 /** 229 * Creates a new ID token claims set from the specified JSON Web Token 230 * (JWT) claims set. 231 * 232 * @param jwtClaimsSet The JWT claims set. Must not be {@code null}. 233 * 234 * @throws IllegalArgumentException If the JWT claims set doesn't 235 * contain the minimally required 236 * issuer {@code iss}, subject 237 * {@code sub}, audience list 238 * {@code aud}, expiration date 239 * {@code exp} and issue date 240 * {@code iat} claims. 241 */ 242 public IDTokenClaimsSet(final JWTClaimsSet jwtClaimsSet) { 243 244 this(jwtClaimsSet.toJSONObject()); 245 } 246 247 248 /** 249 * Checks if this ID token claims set contains all required claims for 250 * the specified OpenID Connect response type. 251 * 252 * @param rt The OpenID Connect response type. Must not be 253 * {@code null}. 254 * 255 * @return {@code true} if the required claims are contained, else 256 * {@code false}. 257 */ 258 public boolean hasRequiredClaims(final ResponseType rt) { 259 260 if (rt.impliesImplicitFlow() && getNonce() == null) 261 return false; 262 263 if (rt.impliesImplicitFlow() && rt.contains(ResponseType.Value.TOKEN) && getAccessTokenHash() == null) 264 return false; 265 266 if (rt.impliesCodeFlow() && getCodeHash() == null) 267 return false; 268 269 return true; 270 } 271 272 273 /** 274 * Gets the ID token issuer. Corresponds to the {@code iss} claim. 275 * 276 * @return The issuer, {@code null} if not specified or parsing failed. 277 */ 278 public Issuer getIssuer() { 279 280 return new Issuer(getStringClaim(ISS_CLAIM_NAME)); 281 } 282 283 284 /** 285 * Gets the ID token subject. Corresponds to the {@code sub} claim. 286 * 287 * @return The subject, {@code null} if not specified or parsing 288 * failed. 289 */ 290 public Subject getSubject() { 291 292 return new Subject(getStringClaim(SUB_CLAIM_NAME)); 293 } 294 295 296 /** 297 * Gets the ID token audience. Corresponds to the {@code aud} claim. 298 * 299 * @return The audience, {@code null} if not specified or parsing 300 * failed. 301 */ 302 public List<Audience> getAudience() { 303 304 if (getClaim(AUD_CLAIM_NAME) instanceof String) { 305 306 return new Audience(getStringClaim(AUD_CLAIM_NAME)).toSingleAudienceList(); 307 308 } else if (getClaim(AUD_CLAIM_NAME) instanceof List) { 309 310 List<String> rawList = getStringListClaim(AUD_CLAIM_NAME); 311 312 if (rawList == null || rawList.isEmpty()) 313 return null; 314 315 List<Audience> audList = new ArrayList<>(rawList.size()); 316 317 for (String s: rawList) 318 audList.add(new Audience(s)); 319 320 return audList; 321 322 } else { 323 324 return null; 325 } 326 } 327 328 329 /** 330 * Gets the ID token expiration time. Corresponds to the {@code exp} 331 * claim. 332 * 333 * @return The expiration time, {@code null} if not specified or 334 * parsing failed. 335 */ 336 public Date getExpirationTime() { 337 338 return getDateClaim(EXP_CLAIM_NAME); 339 } 340 341 342 /** 343 * Gets the ID token issue time. Corresponds to the {@code iss} claim. 344 * 345 * @return The issue time, {@code null} if not specified or parsing 346 * failed. 347 */ 348 public Date getIssueTime() { 349 350 return getDateClaim(IAT_CLAIM_NAME); 351 } 352 353 354 /** 355 * Gets the subject authentication time. Corresponds to the 356 * {@code auth_time} claim. 357 * 358 * @return The authentication time, {@code null} if not specified or 359 * parsing failed. 360 */ 361 public Date getAuthenticationTime() { 362 363 return getDateClaim(AUTH_TIME_CLAIM_NAME); 364 } 365 366 367 /** 368 * Sets the subject authentication time. Corresponds to the 369 * {@code auth_time} claim. 370 * 371 * @param authTime The authentication time, {@code null} if not 372 * specified. 373 */ 374 public void setAuthenticationTime(final Date authTime) { 375 376 setDateClaim(AUTH_TIME_CLAIM_NAME, authTime); 377 } 378 379 380 /** 381 * Gets the ID token nonce. Corresponds to the {@code nonce} claim. 382 * 383 * @return The nonce, {@code null} if not specified or parsing failed. 384 */ 385 public Nonce getNonce() { 386 387 String value = getStringClaim(NONCE_CLAIM_NAME); 388 389 if (value == null) 390 return null; 391 392 return new Nonce(value); 393 } 394 395 396 /** 397 * Sets the ID token nonce. Corresponds to the {@code nonce} claim. 398 * 399 * @param nonce The nonce, {@code null} if not specified. 400 */ 401 public void setNonce(final Nonce nonce) { 402 403 if (nonce != null) 404 setClaim(NONCE_CLAIM_NAME, nonce.getValue()); 405 else 406 setClaim(NONCE_CLAIM_NAME, null); 407 } 408 409 410 /** 411 * Gets the access token hash. Corresponds to the {@code at_hash} 412 * claim. 413 * 414 * @return The access token hash, {@code null} if not specified or 415 * parsing failed. 416 */ 417 public AccessTokenHash getAccessTokenHash() { 418 419 String value = getStringClaim(AT_HASH_CLAIM_NAME); 420 421 if (value == null) 422 return null; 423 424 return new AccessTokenHash(value); 425 } 426 427 428 /** 429 * Sets the access token hash. Corresponds to the {@code at_hash} 430 * claim. 431 * 432 * @param atHash The access token hash, {@code null} if not specified. 433 */ 434 public void setAccessTokenHash(final AccessTokenHash atHash) { 435 436 if (atHash != null) 437 setClaim(AT_HASH_CLAIM_NAME, atHash.getValue()); 438 else 439 setClaim(AT_HASH_CLAIM_NAME, null); 440 } 441 442 443 /** 444 * Gets the authorisation code hash. Corresponds to the {@code c_hash} 445 * claim. 446 * 447 * @return The authorisation code hash, {@code null} if not specified 448 * or parsing failed. 449 */ 450 public CodeHash getCodeHash() { 451 452 String value = getStringClaim(C_HASH_CLAIM_NAME); 453 454 if (value == null) 455 return null; 456 457 return new CodeHash(value); 458 } 459 460 461 /** 462 * Sets the authorisation code hash. Corresponds to the {@code c_hash} 463 * claim. 464 * 465 * @param cHash The authorisation code hash, {@code null} if not 466 * specified. 467 */ 468 public void setCodeHash(final CodeHash cHash) { 469 470 if (cHash != null) 471 setClaim(C_HASH_CLAIM_NAME, cHash.getValue()); 472 else 473 setClaim(C_HASH_CLAIM_NAME, null); 474 } 475 476 477 /** 478 * Gets the Authentication Context Class Reference (ACR). Corresponds 479 * to the {@code acr} claim. 480 * 481 * @return The Authentication Context Class Reference (ACR), 482 * {@code null} if not specified or parsing failed. 483 */ 484 public ACR getACR() { 485 486 String value = getStringClaim(ACR_CLAIM_NAME); 487 488 if (value == null) 489 return null; 490 491 return new ACR(value); 492 } 493 494 495 /** 496 * Sets the Authentication Context Class Reference (ACR). Corresponds 497 * to the {@code acr} claim. 498 * 499 * @param acr The Authentication Context Class Reference (ACR), 500 * {@code null} if not specified. 501 */ 502 public void setACR(final ACR acr) { 503 504 if (acr != null) 505 setClaim(ACR_CLAIM_NAME, acr.getValue()); 506 else 507 setClaim(ACR_CLAIM_NAME, null); 508 } 509 510 511 /** 512 * Gets the Authentication Methods References (AMRs). Corresponds to 513 * the {@code amr} claim. 514 * 515 * @return The Authentication Methods Reference (AMR) list, 516 * {@code null} if not specified or parsing failed. 517 */ 518 public List<AMR> getAMR() { 519 520 List<String> rawList = getStringListClaim(AMR_CLAIM_NAME); 521 522 if (rawList == null || rawList.isEmpty()) 523 return null; 524 525 List<AMR> amrList = new ArrayList<>(rawList.size()); 526 527 for (String s: rawList) 528 amrList.add(new AMR(s)); 529 530 return amrList; 531 } 532 533 534 /** 535 * Sets the Authentication Methods References (AMRs). Corresponds to 536 * the {@code amr} claim. 537 * 538 * @param amr The Authentication Methods Reference (AMR) list, 539 * {@code null} if not specified. 540 */ 541 public void setAMR(final List<AMR> amr) { 542 543 if (amr != null) { 544 545 List<String> amrList = new ArrayList<>(amr.size()); 546 547 for (AMR a: amr) 548 amrList.add(a.getValue()); 549 550 setClaim(AMR_CLAIM_NAME, amrList); 551 552 } else { 553 setClaim(AMR_CLAIM_NAME, null); 554 } 555 } 556 557 558 /** 559 * Gets the authorised party for the ID token. Corresponds to the 560 * {@code azp} claim. 561 * 562 * @return The authorised party, {@code null} if not specified or 563 * parsing failed. 564 */ 565 public AuthorizedParty getAuthorizedParty() { 566 567 String value = getStringClaim(AZP_CLAIM_NAME); 568 569 if (value == null) 570 return null; 571 572 return new AuthorizedParty(value); 573 } 574 575 576 /** 577 * Sets the authorised party for the ID token. Corresponds to the 578 * {@code azp} claim. 579 * 580 * @param azp The authorised party, {@code null} if not specified. 581 */ 582 public void setAuthorizedParty(final AuthorizedParty azp) { 583 584 if (azp != null) 585 setClaim(AZP_CLAIM_NAME, azp.getValue()); 586 else 587 setClaim(AZP_CLAIM_NAME, null); 588 } 589 590 591 /** 592 * Gets the subject's JSON Web Key (JWK) for a self-issued OpenID 593 * Connect provider. Corresponds to the {@code sub_jwk} claim. 594 * 595 * @return The subject's JWK, {@code null} if not specified or parsing 596 * failed. 597 */ 598 public JWK getSubjectJWK() { 599 600 JSONObject jsonObject = getClaim(SUB_JWK_CLAIM_NAME, JSONObject.class); 601 602 if (jsonObject == null) 603 return null; 604 605 try { 606 return JWK.parse(jsonObject); 607 608 } catch (java.text.ParseException e) { 609 610 return null; 611 } 612 } 613 614 615 /** 616 * Sets the subject's JSON Web Key (JWK) for a self-issued OpenID 617 * Connect provider. Corresponds to the {@code sub_jwk} claim. 618 * 619 * @param subJWK The subject's JWK (must be public), {@code null} if 620 * not specified. 621 */ 622 public void setSubjectJWK(final JWK subJWK) { 623 624 if (subJWK != null) { 625 626 if (subJWK.isPrivate()) 627 throw new IllegalArgumentException("The subject's JSON Web Key (JWK) must be public"); 628 629 setClaim(SUB_JWK_CLAIM_NAME, subJWK.toJSONObject()); 630 631 } else { 632 setClaim(SUB_JWK_CLAIM_NAME, null); 633 } 634 } 635 636 637 /** 638 * Parses an ID token claims set from the specified JSON object string. 639 * 640 * @param json The JSON object string to parse. Must not be 641 * {@code null}. 642 * 643 * @return The ID token claims set. 644 * 645 * @throws ParseException If parsing failed. 646 */ 647 public static IDTokenClaimsSet parse(final String json) 648 throws ParseException { 649 650 JSONObject jsonObject = JSONObjectUtils.parseJSONObject(json); 651 652 try { 653 return new IDTokenClaimsSet(jsonObject); 654 655 } catch (IllegalArgumentException e) { 656 657 throw new ParseException(e.getMessage(), e); 658 } 659 } 660}