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