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