001 package com.nimbusds.jwt; 002 003 004 import java.text.ParseException; 005 006 import java.util.Arrays; 007 import java.util.Collections; 008 import java.util.HashMap; 009 import java.util.HashSet; 010 import java.util.Map; 011 import java.util.Set; 012 013 import net.minidev.json.JSONArray; 014 import net.minidev.json.JSONObject; 015 016 import com.nimbusds.jose.util.JSONObjectUtils; 017 018 019 /** 020 * JSON Web Token (JWT) claims set. 021 * 022 * <p>Supports all {@link #getReservedNames reserved claims} of the JWT 023 * specification: 024 * 025 * <ul> 026 * <li>iss - Issuer 027 * <li>sub - Subject 028 * <li>aud - Audience 029 * <li>exp - Expiration Time 030 * <li>nbf - Not Before 031 * <li>iat - Issued At 032 * <li>jti - JWT ID 033 * <li>typ - Type 034 * </ul> 035 * 036 * <p>The set may also carry {@link #setCustomClaims custom claims}; these will 037 * be serialised and parsed along the reserved ones. 038 * 039 * @author Vladimir Dzhuvinov 040 * @version $version$ (2013-01-15) 041 */ 042 public class JWTClaimsSet implements ReadOnlyJWTClaimsSet { 043 044 045 /** 046 * The reserved claim names. 047 */ 048 private static final Set<String> RESERVED_CLAIM_NAMES; 049 050 051 /** 052 * Initialises the reserved claim name set. 053 */ 054 static { 055 Set<String> n = new HashSet<String>(); 056 057 n.add("iss"); 058 n.add("sub"); 059 n.add("aud"); 060 n.add("exp"); 061 n.add("nbf"); 062 n.add("iat"); 063 n.add("jti"); 064 n.add("typ"); 065 066 RESERVED_CLAIM_NAMES = Collections.unmodifiableSet(n); 067 } 068 069 070 /** 071 * The issuer claim. 072 */ 073 private String iss = null; 074 075 076 /** 077 * The subject claim. 078 */ 079 private String sub = null; 080 081 082 /** 083 * The audience claim. 084 */ 085 private String[] aud = null; 086 087 088 /** 089 * The expiration time claim. 090 */ 091 private long exp = -1l; 092 093 094 /** 095 * The not-before claim. 096 */ 097 private long nbf = -1l; 098 099 100 /** 101 * The issued-at claim. 102 */ 103 private long iat = -1l; 104 105 106 /** 107 * The JWT ID claim. 108 */ 109 private String jti = null; 110 111 112 /** 113 * The type claim. 114 */ 115 private String typ = null; 116 117 118 /** 119 * Custom claims. 120 */ 121 private Map<String,Object> customClaims = new HashMap<String,Object>(); 122 123 124 /** 125 * Creates a new empty JWT claims set. 126 */ 127 public JWTClaimsSet() { 128 129 // Nothing to do 130 } 131 132 133 /** 134 * Gets the reserved JWT claim names. 135 * 136 * @return The reserved claim names, as an unmodifiable set. 137 */ 138 public static Set<String> getReservedNames() { 139 140 return RESERVED_CLAIM_NAMES; 141 } 142 143 144 @Override 145 public String getIssuerClaim() { 146 147 return iss; 148 } 149 150 151 /** 152 * Sets the issuer ({@code iss}) claim. 153 * 154 * @param iss The issuer claim, {@code null} if not specified. 155 */ 156 public void setIssuerClaim(final String iss) { 157 158 this.iss = iss; 159 } 160 161 162 @Override 163 public String getSubjectClaim() { 164 165 return sub; 166 } 167 168 169 /** 170 * Sets the subject ({@code sub}) claim. 171 * 172 * @param sub The subject claim, {@code null} if not specified. 173 */ 174 public void setSubjectClaim(final String sub) { 175 176 this.sub = sub; 177 } 178 179 180 @Override 181 public String[] getAudienceClaim() { 182 183 return aud; 184 } 185 186 187 /** 188 * Sets the audience ({@code aud}) clam. 189 * 190 * @param aud The audience claim, {@code null} if not specified. 191 */ 192 public void setAudienceClaim(final String[] aud) { 193 194 this.aud = aud; 195 } 196 197 198 @Override 199 public long getExpirationTimeClaim() { 200 201 return exp; 202 } 203 204 205 /** 206 * Sets the expiration time ({@code exp}) claim. 207 * 208 * @param exp The expiration time, -1 if not specified. 209 */ 210 public void setExpirationTimeClaim(final long exp) { 211 212 this.exp = exp; 213 } 214 215 216 @Override 217 public long getNotBeforeClaim() { 218 219 return nbf; 220 } 221 222 223 /** 224 * Sets the not-before ({@code nbf}) claim. 225 * 226 * @param nbf The not-before claim, -1 if not specified. 227 */ 228 public void setNotBeforeClaim(final long nbf) { 229 230 this.nbf = nbf; 231 } 232 233 234 @Override 235 public long getIssuedAtClaim() { 236 237 return iat; 238 } 239 240 241 /** 242 * Sets the issued-at ({@code iat}) claim. 243 * 244 * @param iat The issued-at claim, -1 if not specified. 245 */ 246 public void setIssuedAtClaim(final long iat) { 247 248 this.iat = iat; 249 } 250 251 252 @Override 253 public String getJWTIDClaim() { 254 255 return jti; 256 } 257 258 259 /** 260 * Sets the JWT ID ({@code jti}) claim. 261 * 262 * @param jti The JWT ID claim, {@code null} if not specified. 263 */ 264 public void setJWTIDClaim(final String jti) { 265 266 this.jti = jti; 267 } 268 269 270 @Override 271 public String getTypeClaim() { 272 273 return typ; 274 } 275 276 277 /** 278 * Sets the type ({@code typ}) claim. 279 * 280 * @param typ The type claim, {@code null} if not specified. 281 */ 282 public void setTypeClaim(final String typ) { 283 284 this.typ = typ; 285 } 286 287 288 @Override 289 public Object getCustomClaim(final String name) { 290 291 return customClaims.get(name); 292 } 293 294 295 /** 296 * Sets a custom (non-reserved) claim. 297 * 298 * @param name The name of the custom claim. Must not be {@code null}. 299 * @param value The value of the custom claim, should map to a valid 300 * JSON entity, {@code null} if not specified. 301 * 302 * @throws IllegalArgumentException If the specified custom claim name 303 * matches a reserved claim name. 304 */ 305 public void setCustomClaim(final String name, final Object value) { 306 307 if (getReservedNames().contains(name)) 308 throw new IllegalArgumentException("The claim name \"" + name + "\" matches a reserved name"); 309 310 customClaims.put(name, value); 311 } 312 313 314 @Override 315 public Map<String,Object> getCustomClaims() { 316 317 return Collections.unmodifiableMap(customClaims); 318 } 319 320 321 /** 322 * Sets the custom (non-reserved) claims. The values must be 323 * serialisable to a JSON entity, otherwise will be ignored. 324 * 325 * @param customClaims The custom claims, empty map or {@code null} if 326 * none. 327 */ 328 public void setCustomClaims(final Map<String,Object> customClaims) { 329 330 if (customClaims == null) 331 return; 332 333 this.customClaims = customClaims; 334 } 335 336 337 @Override 338 public JSONObject toJSONObject() { 339 340 JSONObject o = new JSONObject(customClaims); 341 342 if (iss != null) 343 o.put("iss", iss); 344 345 if (sub != null) 346 o.put("sub", sub); 347 348 if (aud != null) { 349 JSONArray audArray = new JSONArray(); 350 audArray.addAll(Arrays.asList(aud)); 351 o.put("aud", audArray); 352 } 353 354 if (exp > -1) 355 o.put("exp", exp); 356 357 if (nbf > -1) 358 o.put("nbf", nbf); 359 360 if (iat > -1) 361 o.put("iat", iat); 362 363 if (jti != null) 364 o.put("jti", jti); 365 366 if (typ != null) 367 o.put("typ", typ); 368 369 return o; 370 } 371 372 373 /** 374 * Parses a JSON Web Token (JWT) claims set from the specified 375 * JSON object representation. 376 * 377 * @param json The JSON object to parse. Must not be {@code null}. 378 * 379 * @return The JWT claims set. 380 * 381 * @throws ParseException If the specified JSON object doesn't represent 382 * a valid JWT claims set. 383 */ 384 public static JWTClaimsSet parse(final JSONObject json) 385 throws ParseException { 386 387 JWTClaimsSet cs = new JWTClaimsSet(); 388 389 // Parse reserved + custom params 390 for (final String name: json.keySet()) { 391 392 if (name.equals("iss")) { 393 394 cs.setIssuerClaim(JSONObjectUtils.getString(json, "iss")); 395 } 396 else if (name.equals("sub")) { 397 398 cs.setSubjectClaim(JSONObjectUtils.getString(json, "sub")); 399 } 400 else if (name.equals("aud")) { 401 402 Object audValue = json.get("aud"); 403 404 if (audValue != null && audValue instanceof String) { 405 String[] singleAud = {JSONObjectUtils.getString(json, "aud")}; 406 cs.setAudienceClaim(singleAud); 407 } 408 else { 409 cs.setAudienceClaim(JSONObjectUtils.getStringArray(json, "aud")); 410 } 411 } 412 else if (name.equals("exp")) { 413 414 cs.setExpirationTimeClaim(JSONObjectUtils.getLong(json, "exp")); 415 } 416 else if (name.equals("nbf")) { 417 418 cs.setNotBeforeClaim(JSONObjectUtils.getLong(json, "nbf")); 419 } 420 else if (name.equals("iat")) { 421 422 cs.setIssuedAtClaim(JSONObjectUtils.getLong(json, "iat")); 423 } 424 else if (name.equals("jti")) { 425 426 cs.setJWTIDClaim(JSONObjectUtils.getString(json, "jti")); 427 } 428 else if (name.equals("typ")) { 429 430 cs.setTypeClaim(JSONObjectUtils.getString(json, "typ")); 431 } 432 else { 433 cs.setCustomClaim(name, json.get(name)); 434 } 435 } 436 437 return cs; 438 } 439 }