001package com.nimbusds.oauth2.sdk.assertions.jwt; 002 003 004import java.util.*; 005 006import net.minidev.json.JSONObject; 007 008import com.nimbusds.jwt.JWTClaimsSet; 009 010import com.nimbusds.oauth2.sdk.ParseException; 011import com.nimbusds.oauth2.sdk.auth.ClientSecretJWT; 012import com.nimbusds.oauth2.sdk.auth.PrivateKeyJWT; 013import com.nimbusds.oauth2.sdk.id.*; 014import com.nimbusds.oauth2.sdk.util.DateUtils; 015import com.nimbusds.oauth2.sdk.util.JSONObjectUtils; 016 017 018/** 019 * JSON Web Token (JWT) bearer assertion claims set for OAuth 2.0 client 020 * authentication and authorisation grants. 021 * 022 * <p>Used for {@link ClientSecretJWT client secret JWT} and 023 * {@link PrivateKeyJWT private key JWT} authentication at the Token endpoint. 024 * 025 * <p>Example JWT bearer assertion claims set for client authentication: 026 * 027 * <pre> 028 * { 029 * "iss" : "http://client.example.com", 030 * "sub" : "http://client.example.com", 031 * "aud" : [ "http://idp.example.com/token" ], 032 * "jti" : "d396036d-c4d9-40d8-8e98-f7e8327002d9", 033 * "exp" : 1311281970, 034 * "iat" : 1311280970 035 * } 036 * </pre> 037 * 038 * <p>Related specifications: 039 * 040 * <ul> 041 * <li>JSON Web Token (JWT) Profile for OAuth 2.0 Client Authentication and 042 * Authorization Grants (RFC 7523), section 3. 043 * </ul> 044 */ 045public class JWTAssertionClaimsSet { 046 047 048 /** 049 * The names of the reserved JWT claims. 050 */ 051 private static final Set<String> reservedClaimsNames = new LinkedHashSet<>(); 052 053 054 static { 055 reservedClaimsNames.add("iss"); 056 reservedClaimsNames.add("sub"); 057 reservedClaimsNames.add("aud"); 058 reservedClaimsNames.add("exp"); 059 reservedClaimsNames.add("nbf"); 060 reservedClaimsNames.add("iat"); 061 reservedClaimsNames.add("jti"); 062 } 063 064 065 /** 066 * Gets the names of the reserved JWT bearer assertion claims. 067 * 068 * @return The names of the reserved JWT bearer assertion claims 069 * (read-only set). 070 */ 071 public static Set<String> getReservedClaimsNames() { 072 073 return Collections.unmodifiableSet(reservedClaimsNames); 074 } 075 076 077 /** 078 * The issuer (required). 079 */ 080 private final Issuer iss; 081 082 083 /** 084 * The subject (required). 085 */ 086 private final Subject sub; 087 088 089 /** 090 * The audience that this token is intended for (required). 091 */ 092 private final List<Audience> aud; 093 094 095 /** 096 * The expiration time that limits the time window during which the JWT 097 * can be used (required). The serialised value is number of seconds 098 * from 1970-01-01T0:0:0Z as measured in UTC until the desired 099 * date/time. 100 */ 101 private final Date exp; 102 103 104 /** 105 * The time before which this token must not be accepted for 106 * processing (optional). The serialised value is number of seconds 107 * from 1970-01-01T0:0:0Z as measured in UTC until the desired 108 * date/time. 109 */ 110 private final Date nbf; 111 112 113 /** 114 * The time at which this token was issued (optional). The serialised 115 * value is number of seconds from 1970-01-01T0:0:0Z as measured in UTC 116 * until the desired date/time. 117 */ 118 private final Date iat; 119 120 121 /** 122 * Unique identifier for the JWT (optional). The JWT ID may be used by 123 * implementations requiring message de-duplication for one-time use 124 * assertions. 125 */ 126 private final JWTID jti; 127 128 129 /** 130 * Other optional custom claims. 131 */ 132 private final Map<String,Object> other; 133 134 135 /** 136 * Creates a new JWT JWT bearer assertion claims set. The expiration 137 * time (exp) is set to five minutes from the current system time. 138 * Generates a default identifier (jti) for the JWT. The issued-at 139 * (iat) and not-before (nbf) claims are not set. 140 * 141 * @param iss The issuer identifier. Must not be {@code null}. 142 * @param sub The subject. Must not be {@code null}. 143 * @param aud The audience identifier, typically the URI of the 144 * authorisation server's Token endpoint. Must not be 145 * {@code null}. 146 */ 147 public JWTAssertionClaimsSet(final Issuer iss, 148 final Subject sub, 149 final Audience aud) { 150 151 this(iss, sub, aud.toSingleAudienceList(), new Date(new Date().getTime() + 5*60*1000l), null, null, new JWTID(), null); 152 } 153 154 155 /** 156 * Creates a new JWT JWT bearer assertion claims set. 157 * 158 * @param iss The issuer identifier. Must not be {@code null}. 159 * @param sub The subject. Must not be {@code null}. 160 * @param aud The audience, typically including the URI of the 161 * authorisation server's token endpoint. Must not be 162 * {@code null}. 163 * @param exp The expiration time. Must not be {@code null}. 164 * @param nbf The time before which the token must not be accepted 165 * for processing, {@code null} if not specified. 166 * @param iat The time at which the token was issued, {@code null} if 167 * not specified. 168 * @param jti Unique identifier for the JWT, {@code null} if not 169 * specified. 170 * @param other Other custom claims to include, {@code null} if none. 171 */ 172 public JWTAssertionClaimsSet(final Issuer iss, 173 final Subject sub, 174 final List<Audience> aud, 175 final Date exp, 176 final Date nbf, 177 final Date iat, 178 final JWTID jti, 179 final Map<String,Object> other) { 180 181 if (iss == null) 182 throw new IllegalArgumentException("The issuer must not be null"); 183 184 this.iss = iss; 185 186 if (sub == null) 187 throw new IllegalArgumentException("The subject must not be null"); 188 189 this.sub = sub; 190 191 192 if (aud == null || aud.isEmpty()) 193 throw new IllegalArgumentException("The audience must not be null or empty"); 194 195 this.aud = aud; 196 197 198 if (exp == null) 199 throw new IllegalArgumentException("The expiration time must not be null"); 200 201 this.exp = exp; 202 203 this.nbf = nbf; 204 this.iat = iat; 205 this.jti = jti; 206 207 this.other = other; 208 } 209 210 211 212 /** 213 * Gets the issuer. Corresponds to the {@code iss} claim. 214 * 215 * @return The issuer. Contains the identifier of the OAuth client. 216 */ 217 public Issuer getIssuer() { 218 219 return iss; 220 } 221 222 223 /** 224 * Gets the subject. Corresponds to the {@code sub} claim. 225 * 226 * @return The subject. Contains the identifier of the OAuth client. 227 */ 228 public Subject getSubject() { 229 230 return sub; 231 } 232 233 234 /** 235 * Gets the audience. Corresponds to the {@code aud} claim. 236 * 237 * @return The audience, typically including the URI of the 238 * authorisation server's token endpoint. 239 */ 240 public List<Audience> getAudience() { 241 242 return aud; 243 } 244 245 246 /** 247 * Gets the expiration time. Corresponds to the {@code exp} claim. 248 * 249 * @return The expiration time. 250 */ 251 public Date getExpirationTime() { 252 253 return exp; 254 } 255 256 257 /** 258 * Gets the not-before time. Corresponds to the {@code nbf} claim. 259 * 260 * @return The not-before time, {@code null} if not specified. 261 */ 262 public Date getNotBeforeTime() { 263 264 return nbf; 265 } 266 267 268 /** 269 * Gets the optional issue time. Corresponds to the {@code iat} claim. 270 * 271 * @return The issued-at time, {@code null} if not specified. 272 */ 273 public Date getIssueTime() { 274 275 return iat; 276 } 277 278 279 /** 280 * Gets the identifier for the JWT. Corresponds to the {@code jti} 281 * claim. 282 * 283 * @return The identifier for the JWT, {@code null} if not specified. 284 */ 285 public JWTID getJWTID() { 286 287 return jti; 288 } 289 290 291 /** 292 * Gets the custom claims. 293 * 294 * @return The custom claims, {@code null} if not specified. 295 */ 296 public Map<String,Object> getCustomClaims() { 297 298 return other; 299 } 300 301 302 /** 303 * Returns a JSON object representation of this JWT bearer assertion 304 * claims set. 305 * 306 * @return The JSON object. 307 */ 308 public JSONObject toJSONObject() { 309 310 JSONObject o = new JSONObject(); 311 312 o.put("iss", iss.getValue()); 313 o.put("sub", sub.getValue()); 314 o.put("aud", Audience.toStringList(aud)); 315 o.put("exp", DateUtils.toSecondsSinceEpoch(exp)); 316 317 if (nbf != null) 318 o.put("nbf", DateUtils.toSecondsSinceEpoch(nbf)); 319 320 if (iat != null) 321 o.put("iat", DateUtils.toSecondsSinceEpoch(iat)); 322 323 if (jti != null) 324 o.put("jti", jti.getValue()); 325 326 if (other != null) { 327 o.putAll(other); 328 } 329 330 return o; 331 } 332 333 334 /** 335 * Returns a JSON Web Token (JWT) claims set representation of this 336 * JWT bearer assertion claims set. 337 * 338 * @return The JWT claims set. 339 */ 340 public JWTClaimsSet toJWTClaimsSet() { 341 342 JWTClaimsSet.Builder builder = new JWTClaimsSet.Builder() 343 .issuer(iss.getValue()) 344 .subject(sub.getValue()) 345 .audience(Audience.toStringList(aud)) 346 .expirationTime(exp) 347 .notBeforeTime(nbf) // optional 348 .issueTime(iat) // optional 349 .jwtID(jti != null ? jti.getValue() : null); // optional 350 351 // Append custom claims if any 352 if (other != null) { 353 for (Map.Entry<String,?> entry: other.entrySet()) { 354 builder = builder.claim(entry.getKey(), entry.getValue()); 355 } 356 } 357 358 return builder.build(); 359 } 360 361 362 /** 363 * Parses a JWT bearer assertion claims set from the specified JSON 364 * object. 365 * 366 * @param jsonObject The JSON object. Must not be {@code null}. 367 * 368 * @return The JWT bearer assertion claims set. 369 * 370 * @throws ParseException If the JSON object couldn't be parsed to a 371 * JWT bearer assertion claims set. 372 */ 373 public static JWTAssertionClaimsSet parse(final JSONObject jsonObject) 374 throws ParseException { 375 376 // Parse required claims 377 Issuer iss = new Issuer(JSONObjectUtils.getString(jsonObject, "iss")); 378 Subject sub = new Subject(JSONObjectUtils.getString(jsonObject, "sub")); 379 380 List<Audience> aud; 381 382 if (jsonObject.get("aud") instanceof String) { 383 aud = new Audience(JSONObjectUtils.getString(jsonObject, "aud")).toSingleAudienceList(); 384 } else { 385 aud = Audience.create(JSONObjectUtils.getStringList(jsonObject, "aud")); 386 } 387 388 Date exp = DateUtils.fromSecondsSinceEpoch(JSONObjectUtils.getLong(jsonObject, "exp")); 389 390 391 // Parse optional claims 392 393 Date nbf = null; 394 395 if (jsonObject.containsKey("nbf")) 396 nbf = DateUtils.fromSecondsSinceEpoch(JSONObjectUtils.getLong(jsonObject, "nbf")); 397 398 Date iat = null; 399 400 if (jsonObject.containsKey("iat")) 401 iat = DateUtils.fromSecondsSinceEpoch(JSONObjectUtils.getLong(jsonObject, "iat")); 402 403 JWTID jti = null; 404 405 if (jsonObject.containsKey("jti")) 406 jti = new JWTID(JSONObjectUtils.getString(jsonObject, "jti")); 407 408 // Parse custom claims 409 Map<String,Object> other = null; 410 411 Set<String> customClaimNames = jsonObject.keySet(); 412 if (customClaimNames.removeAll(reservedClaimsNames)) { 413 other = new LinkedHashMap<>(); 414 for (String claim: customClaimNames) { 415 other.put(claim, jsonObject.get(claim)); 416 } 417 } 418 419 return new JWTAssertionClaimsSet(iss, sub, aud, exp, nbf, iat, jti, other); 420 } 421 422 423 /** 424 * Parses a JWT bearer assertion claims set from the specified JWT 425 * claims set. 426 * 427 * @param jwtClaimsSet The JWT claims set. Must not be {@code null}. 428 * 429 * @return The JWT bearer assertion claims set. 430 * 431 * @throws ParseException If the JWT claims set couldn't be parsed to a 432 * JWT bearer assertion claims set. 433 */ 434 public static JWTAssertionClaimsSet parse(final JWTClaimsSet jwtClaimsSet) 435 throws ParseException { 436 437 return parse(jwtClaimsSet.toJSONObject()); 438 } 439}