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-08)
041 */
042 public class ClaimsSet implements ReadOnlyClaimsSet {
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 claims set.
126 */
127 public ClaimsSet() {
128
129 // Nothing to do
130 }
131
132
133 /**
134 * Gets the reserved 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 (public or private) 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 claims set.
380 *
381 * @throws ParseException If the specified JSON object doesn't represent
382 * a valid JWT claims set.
383 */
384 public static ClaimsSet parse(final JSONObject json)
385 throws ParseException {
386
387 ClaimsSet cs = new ClaimsSet();
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 }