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