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