001package com.nimbusds.jwt; 002 003 004import java.text.ParseException; 005import java.util.ArrayList; 006import java.util.Collections; 007import java.util.Date; 008import java.util.HashMap; 009import java.util.HashSet; 010import java.util.List; 011import java.util.Map; 012import java.util.Set; 013 014import net.minidev.json.JSONArray; 015import net.minidev.json.JSONObject; 016 017import com.nimbusds.jose.util.JSONObjectUtils; 018 019 020/** 021 * JSON Web Token (JWT) claims set. 022 * 023 * <p>Supports all {@link #getReservedNames reserved claims} of the JWT 024 * specification: 025 * 026 * <ul> 027 * <li>iss - Issuer 028 * <li>sub - Subject 029 * <li>aud - Audience 030 * <li>exp - Expiration Time 031 * <li>nbf - Not Before 032 * <li>iat - Issued At 033 * <li>jti - JWT ID 034 * <li>typ - Type 035 * </ul> 036 * 037 * <p>The set may also carry {@link #setCustomClaims custom claims}; these will 038 * be serialised and parsed along the reserved ones. 039 * 040 * @author Vladimir Dzhuvinov 041 * @author Justin Richer 042 * @version $version$ (2013-02-21) 043 */ 044public class JWTClaimsSet implements ReadOnlyJWTClaimsSet { 045 046 047 private static final String TYPE_CLAIM = "typ"; 048 private static final String JWT_ID_CLAIM = "jti"; 049 private static final String ISSUED_AT_CLAIM = "iat"; 050 private static final String NOT_BEFORE_CLAIM = "nbf"; 051 private static final String EXPIRATION_TIME_CLAIM = "exp"; 052 private static final String AUDIENCE_CLAIM = "aud"; 053 private static final String SUBJECT_CLAIM = "sub"; 054 private static final String ISSUER_CLAIM = "iss"; 055 056 057 /** 058 * The reserved claim names. 059 */ 060 private static final Set<String> RESERVED_CLAIM_NAMES; 061 062 063 /** 064 * Initialises the reserved claim name set. 065 */ 066 static { 067 Set<String> n = new HashSet<String>(); 068 069 n.add(ISSUER_CLAIM); 070 n.add(SUBJECT_CLAIM); 071 n.add(AUDIENCE_CLAIM); 072 n.add(EXPIRATION_TIME_CLAIM); 073 n.add(NOT_BEFORE_CLAIM); 074 n.add(ISSUED_AT_CLAIM); 075 n.add(JWT_ID_CLAIM); 076 n.add(TYPE_CLAIM); 077 078 RESERVED_CLAIM_NAMES = Collections.unmodifiableSet(n); 079 } 080 081 082 /** 083 * The issuer claim. 084 */ 085 private String iss = null; 086 087 088 /** 089 * The subject claim. 090 */ 091 private String sub = null; 092 093 094 /** 095 * The audience claim. 096 */ 097 private List<String> aud = null; 098 099 100 /** 101 * The expiration time claim. 102 */ 103 private Date exp = null; 104 105 106 /** 107 * The not-before claim. 108 */ 109 private Date nbf = null; 110 111 112 /** 113 * The issued-at claim. 114 */ 115 private Date iat = null; 116 117 118 /** 119 * The JWT ID claim. 120 */ 121 private String jti = null; 122 123 124 /** 125 * The type claim. 126 */ 127 private String typ = null; 128 129 130 /** 131 * Custom claims. 132 */ 133 private Map<String,Object> customClaims = new HashMap<String,Object>(); 134 135 136 /** 137 * Creates a new empty JWT claims set. 138 */ 139 public JWTClaimsSet() { 140 141 // Nothing to do 142 } 143 144 145 /** 146 * Creates a copy of the specified JWT claims set. 147 * 148 * @param old The JWT claims set to copy. Must not be {@code null}. 149 */ 150 public JWTClaimsSet(final ReadOnlyJWTClaimsSet old) { 151 152 super(); 153 setAllClaims(old.getAllClaims()); 154 } 155 156 157 /* (non-Javadoc) 158 * @see java.lang.Object#clone() 159 */ 160 @Override 161 protected Object clone() throws CloneNotSupportedException { 162 163 // TODO Auto-generated method stub 164 return super.clone(); 165 } 166 167 168 /** 169 * Gets the reserved JWT claim names. 170 * 171 * @return The reserved claim names, as an unmodifiable set. 172 */ 173 public static Set<String> getReservedNames() { 174 175 return RESERVED_CLAIM_NAMES; 176 } 177 178 179 @Override 180 public String getIssuer() { 181 182 return iss; 183 } 184 185 186 /** 187 * Sets the issuer ({@code iss}) claim. 188 * 189 * @param iss The issuer claim, {@code null} if not specified. 190 */ 191 public void setIssuer(final String iss) { 192 193 this.iss = iss; 194 } 195 196 197 @Override 198 public String getSubject() { 199 200 return sub; 201 } 202 203 204 /** 205 * Sets the subject ({@code sub}) claim. 206 * 207 * @param sub The subject claim, {@code null} if not specified. 208 */ 209 public void setSubject(final String sub) { 210 211 this.sub = sub; 212 } 213 214 215 @Override 216 public List<String> getAudience() { 217 218 return aud; 219 } 220 221 222 /** 223 * Sets the audience ({@code aud}) clam. 224 * 225 * @param aud The audience claim, {@code null} if not specified. 226 */ 227 public void setAudience(final List<String> aud) { 228 229 this.aud = aud; 230 } 231 232 233 @Override 234 public Date getExpirationTime() { 235 236 return exp; 237 } 238 239 240 /** 241 * Sets the expiration time ({@code exp}) claim. 242 * 243 * @param exp The expiration time, {@code null} if not specified. 244 */ 245 public void setExpirationTime(final Date exp) { 246 247 this.exp = exp; 248 } 249 250 251 @Override 252 public Date getNotBeforeTime() { 253 254 return nbf; 255 } 256 257 258 /** 259 * Sets the not-before ({@code nbf}) claim. 260 * 261 * @param nbf The not-before claim, {@code null} if not specified. 262 */ 263 public void setNotBeforeTime(final Date nbf) { 264 265 this.nbf = nbf; 266 } 267 268 269 @Override 270 public Date getIssueTime() { 271 272 return iat; 273 } 274 275 276 /** 277 * Sets the issued-at ({@code iat}) claim. 278 * 279 * @param iat The issued-at claim, {@code null} if not specified. 280 */ 281 public void setIssueTime(final Date iat) { 282 283 this.iat = iat; 284 } 285 286 287 @Override 288 public String getJWTID() { 289 290 return jti; 291 } 292 293 294 /** 295 * Sets the JWT ID ({@code jti}) claim. 296 * 297 * @param jti The JWT ID claim, {@code null} if not specified. 298 */ 299 public void setJWTID(final String jti) { 300 301 this.jti = jti; 302 } 303 304 305 @Override 306 public String getType() { 307 308 return typ; 309 } 310 311 312 /** 313 * Sets the type ({@code typ}) claim. 314 * 315 * @param typ The type claim, {@code null} if not specified. 316 */ 317 public void setType(final String typ) { 318 319 this.typ = typ; 320 } 321 322 323 @Override 324 public Object getCustomClaim(final String name) { 325 326 return customClaims.get(name); 327 } 328 329 330 /** 331 * Sets a custom (non-reserved) claim. 332 * 333 * @param name The name of the custom claim. Must not be {@code null}. 334 * @param value The value of the custom claim, should map to a valid 335 * JSON entity, {@code null} if not specified. 336 * 337 * @throws IllegalArgumentException If the specified custom claim name 338 * matches a reserved claim name. 339 */ 340 public void setCustomClaim(final String name, final Object value) { 341 342 if (getReservedNames().contains(name)) { 343 throw new IllegalArgumentException("The claim name \"" + name + "\" matches a reserved name"); 344 } 345 346 customClaims.put(name, value); 347 } 348 349 350 @Override 351 public Map<String,Object> getCustomClaims() { 352 353 return Collections.unmodifiableMap(customClaims); 354 } 355 356 357 /** 358 * Sets the custom (non-reserved) claims. The values must be 359 * serialisable to a JSON entity, otherwise will be ignored. 360 * 361 * @param customClaims The custom claims, empty map or {@code null} if 362 * none. 363 */ 364 public void setCustomClaims(final Map<String,Object> customClaims) { 365 366 if (customClaims == null) { 367 return; 368 } 369 370 this.customClaims = customClaims; 371 } 372 373 374 @Override 375 public Object getClaim(String name) { 376 377 if (!getReservedNames().contains(name)) { 378 379 return getCustomClaim(name); 380 381 } else { 382 // it's a reserved name, find out which one 383 if (ISSUER_CLAIM.equals(name)) { 384 return getIssuer(); 385 } else if (SUBJECT_CLAIM.equals(name)) { 386 return getSubject(); 387 } else if (AUDIENCE_CLAIM.equals(name)) { 388 return getAudience(); 389 } else if (EXPIRATION_TIME_CLAIM.equals(name)) { 390 return getExpirationTime(); 391 } else if (NOT_BEFORE_CLAIM.equals(name)) { 392 return getNotBeforeTime(); 393 } else if (ISSUED_AT_CLAIM.equals(name)) { 394 return getIssueTime(); 395 } else if (JWT_ID_CLAIM.equals(name)) { 396 return getJWTID(); 397 } else if (TYPE_CLAIM.equals(name)) { 398 return getType(); 399 } else { 400 // if we fall through down to here, something is wrong 401 throw new IllegalArgumentException("Couldn't find reserved claim: " + name); 402 } 403 } 404 } 405 406 407 /** 408 * Sets the specified claim, whether reserved or custom. 409 * 410 * @param name The name of the claim to set. Must not be {@code null}. 411 * @param value The value of the claim to set. May be {@code null}. 412 * 413 * @throws IllegalArgumentException If the claim is reserved and its 414 * value is not of the expected type. 415 */ 416 public void setClaim(String name, Object value) { 417 418 if (!getReservedNames().contains(name)) { 419 setCustomClaim(name, value); 420 } else { 421 // it's a reserved name, find out which one 422 if (ISSUER_CLAIM.equals(name)) { 423 if (value instanceof String) { 424 setIssuer((String)value); 425 } else { 426 throw new IllegalArgumentException("Issuer claim must be a String"); 427 } 428 } else if (SUBJECT_CLAIM.equals(name)) { 429 if (value instanceof String) { 430 setSubject((String)value); 431 } else { 432 throw new IllegalArgumentException("Subject claim must be a String"); 433 } 434 } else if (AUDIENCE_CLAIM.equals(name)) { 435 if (value instanceof List<?>) { 436 setAudience((List<String>)value); 437 } else { 438 throw new IllegalArgumentException("Audience claim must be a List<String>"); 439 } 440 } else if (EXPIRATION_TIME_CLAIM.equals(name)) { 441 if (value instanceof Date) { 442 setExpirationTime((Date)value); 443 } else { 444 throw new IllegalArgumentException("Expiration claim must be a Date"); 445 } 446 } else if (NOT_BEFORE_CLAIM.equals(name)) { 447 if (value instanceof Date) { 448 setNotBeforeTime((Date)value); 449 } else { 450 throw new IllegalArgumentException("Not-before claim must be a Date"); 451 } 452 } else if (ISSUED_AT_CLAIM.equals(name)) { 453 if (value instanceof Date) { 454 setIssueTime((Date)value); 455 } else { 456 throw new IllegalArgumentException("Issued-at claim must be a Date"); 457 } 458 } else if (JWT_ID_CLAIM.equals(name)) { 459 if (value instanceof String) { 460 setJWTID((String)value); 461 } else { 462 throw new IllegalArgumentException("JWT-ID claim must be a String"); 463 } 464 } else if (TYPE_CLAIM.equals(name)) { 465 if (value instanceof String) { 466 setType((String)value); 467 } else { 468 throw new IllegalArgumentException("Type claim must be a String"); 469 } 470 } else { 471 // if we fall through down to here, something is wrong 472 throw new IllegalArgumentException("Couldn't find reserved claim: " + name); 473 } 474 } 475 } 476 477 478 @Override 479 public Map<String, Object> getAllClaims() { 480 481 Map<String, Object> allClaims = new HashMap<String, Object>(); 482 483 allClaims.putAll(customClaims); 484 485 for (String reservedClaim : RESERVED_CLAIM_NAMES) { 486 487 allClaims.put(reservedClaim, getClaim(reservedClaim)); 488 } 489 490 return Collections.unmodifiableMap(allClaims); 491 } 492 493 494 /** 495 * Sets the claims of this JWT claims set, replacing any existing ones. 496 * 497 * @param newClaims The JWT claims. Must not be {@code null}. 498 */ 499 public void setAllClaims(Map<String, Object> newClaims) { 500 501 for (String name : newClaims.keySet()) { 502 setClaim(name, newClaims.get(name)); 503 } 504 } 505 506 507 @Override 508 public JSONObject toJSONObject() { 509 510 JSONObject o = new JSONObject(customClaims); 511 512 if (iss != null) { 513 o.put(ISSUER_CLAIM, iss); 514 } 515 516 if (sub != null) { 517 o.put(SUBJECT_CLAIM, sub); 518 } 519 520 if (aud != null) { 521 JSONArray audArray = new JSONArray(); 522 audArray.addAll(aud); 523 o.put(AUDIENCE_CLAIM, audArray); 524 } 525 526 if (exp != null) { 527 o.put(EXPIRATION_TIME_CLAIM, exp.getTime()); 528 } 529 530 if (nbf != null) { 531 o.put(NOT_BEFORE_CLAIM, nbf.getTime()); 532 } 533 534 if (iat != null) { 535 o.put(ISSUED_AT_CLAIM, iat.getTime()); 536 } 537 538 if (jti != null) { 539 o.put(JWT_ID_CLAIM, jti); 540 } 541 542 if (typ != null) { 543 o.put(TYPE_CLAIM, typ); 544 } 545 546 return o; 547 } 548 549 550 /** 551 * Parses a JSON Web Token (JWT) claims set from the specified 552 * JSON object representation. 553 * 554 * @param json The JSON object to parse. Must not be {@code null}. 555 * 556 * @return The JWT claims set. 557 * 558 * @throws ParseException If the specified JSON object doesn't represent 559 * a valid JWT claims set. 560 */ 561 public static JWTClaimsSet parse(final JSONObject json) 562 throws ParseException { 563 564 JWTClaimsSet cs = new JWTClaimsSet(); 565 566 // Parse reserved + custom params 567 for (final String name: json.keySet()) { 568 569 if (name.equals(ISSUER_CLAIM)) { 570 571 cs.setIssuer(JSONObjectUtils.getString(json, ISSUER_CLAIM)); 572 } 573 else if (name.equals(SUBJECT_CLAIM)) { 574 575 cs.setSubject(JSONObjectUtils.getString(json, SUBJECT_CLAIM)); 576 } 577 else if (name.equals(AUDIENCE_CLAIM)) { 578 579 Object audValue = json.get(AUDIENCE_CLAIM); 580 581 if (audValue != null && audValue instanceof String) { 582 List<String> singleAud = new ArrayList<String>(); 583 singleAud.add(JSONObjectUtils.getString(json, AUDIENCE_CLAIM)); 584 cs.setAudience(singleAud); 585 } 586 else { 587 cs.setAudience(JSONObjectUtils.getStringList(json, AUDIENCE_CLAIM)); 588 } 589 } 590 else if (name.equals(EXPIRATION_TIME_CLAIM)) { 591 592 cs.setExpirationTime(new Date(JSONObjectUtils.getLong(json, EXPIRATION_TIME_CLAIM))); 593 } 594 else if (name.equals(NOT_BEFORE_CLAIM)) { 595 596 cs.setNotBeforeTime(new Date(JSONObjectUtils.getLong(json, NOT_BEFORE_CLAIM))); 597 } 598 else if (name.equals(ISSUED_AT_CLAIM)) { 599 600 cs.setIssueTime(new Date(JSONObjectUtils.getLong(json, ISSUED_AT_CLAIM))); 601 } 602 else if (name.equals(JWT_ID_CLAIM)) { 603 604 cs.setJWTID(JSONObjectUtils.getString(json, JWT_ID_CLAIM)); 605 } 606 else if (name.equals(TYPE_CLAIM)) { 607 608 cs.setType(JSONObjectUtils.getString(json, TYPE_CLAIM)); 609 } 610 else { 611 cs.setCustomClaim(name, json.get(name)); 612 } 613 } 614 615 return cs; 616 } 617 618 619 /* (non-Javadoc) 620 * @see java.lang.Object#toString() 621 */ 622 @Override 623 public String toString() { 624 625 return "JWTClaimsSet [iss=" + iss + ", sub=" + sub + ", aud=" + aud + ", exp=" + exp + ", nbf=" + nbf + ", iat=" + iat + ", jti=" + jti + ", typ=" + typ + ", customClaims=" + customClaims + "]"; 626 } 627}