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