001/* 002 * oauth2-oidc-sdk 003 * 004 * Copyright 2012-2016, Connect2id Ltd and contributors. 005 * 006 * Licensed under the Apache License, Version 2.0 (the "License"); you may not use 007 * this file except in compliance with the License. You may obtain a copy of the 008 * License at 009 * 010 * http://www.apache.org/licenses/LICENSE-2.0 011 * 012 * Unless required by applicable law or agreed to in writing, software distributed 013 * under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR 014 * CONDITIONS OF ANY KIND, either express or implied. See the License for the 015 * specific language governing permissions and limitations under the License. 016 */ 017 018package com.nimbusds.oauth2.sdk.assertions.jwt; 019 020 021import java.util.*; 022 023import com.nimbusds.jwt.util.DateUtils; 024import com.nimbusds.jwt.JWTClaimsSet; 025import com.nimbusds.oauth2.sdk.ParseException; 026import com.nimbusds.oauth2.sdk.assertions.AssertionDetails; 027import com.nimbusds.oauth2.sdk.auth.ClientSecretJWT; 028import com.nimbusds.oauth2.sdk.auth.PrivateKeyJWT; 029import com.nimbusds.oauth2.sdk.id.*; 030import com.nimbusds.oauth2.sdk.util.JSONObjectUtils; 031import net.jcip.annotations.Immutable; 032import net.minidev.json.JSONObject; 033 034 035/** 036 * JSON Web Token (JWT) bearer assertion details (claims set) for OAuth 2.0 037 * client authentication and authorisation grants. 038 * 039 * <p>Used for {@link ClientSecretJWT client secret JWT} and 040 * {@link PrivateKeyJWT private key JWT} authentication at the Token endpoint 041 * as well as {@link com.nimbusds.oauth2.sdk.JWTBearerGrant JWT bearer 042 * assertion grants}. 043 * 044 * <p>Example JWT bearer assertion claims set for client authentication: 045 * 046 * <pre> 047 * { 048 * "iss" : "http://client.example.com", 049 * "sub" : "http://client.example.com", 050 * "aud" : [ "http://idp.example.com/token" ], 051 * "jti" : "d396036d-c4d9-40d8-8e98-f7e8327002d9", 052 * "exp" : 1311281970, 053 * "iat" : 1311280970 054 * } 055 * </pre> 056 * 057 * <p>Related specifications: 058 * 059 * <ul> 060 * <li>JSON Web Token (JWT) Profile for OAuth 2.0 Client Authentication and 061 * Authorization Grants (RFC 7523), section 3. 062 * </ul> 063 */ 064@Immutable 065public class JWTAssertionDetails extends AssertionDetails { 066 067 068 /** 069 * The names of the reserved JWT claims. 070 */ 071 private static final Set<String> reservedClaimsNames = new LinkedHashSet<>(); 072 073 074 static { 075 reservedClaimsNames.add("iss"); 076 reservedClaimsNames.add("sub"); 077 reservedClaimsNames.add("aud"); 078 reservedClaimsNames.add("exp"); 079 reservedClaimsNames.add("nbf"); 080 reservedClaimsNames.add("iat"); 081 reservedClaimsNames.add("jti"); 082 } 083 084 085 /** 086 * Gets the names of the reserved JWT bearer assertion claims. 087 * 088 * @return The names of the reserved JWT bearer assertion claims 089 * (read-only set). 090 */ 091 public static Set<String> getReservedClaimsNames() { 092 093 return Collections.unmodifiableSet(reservedClaimsNames); 094 } 095 096 097 /** 098 * The time before which this token must not be accepted for 099 * processing (optional). The serialised value is number of seconds 100 * from 1970-01-01T0:0:0Z as measured in UTC until the desired 101 * date/time. 102 */ 103 private final Date nbf; 104 105 106 /** 107 * Other optional custom claims. 108 */ 109 private final Map<String,Object> other; 110 111 112 /** 113 * Creates a new JWT bearer assertion details (claims set) instance. 114 * The expiration time (exp) is set to five minutes from the current 115 * system time. Generates a default identifier (jti) for the JWT. The 116 * issued-at (iat) and not-before (nbf) claims are not set. 117 * 118 * @param iss The issuer identifier. Must not be {@code null}. 119 * @param sub The subject. Must not be {@code null}. 120 * @param aud The audience identifier, typically the URI of the 121 * authorisation server's Token endpoint. Must not be 122 * {@code null}. 123 */ 124 public JWTAssertionDetails(final Issuer iss, 125 final Subject sub, 126 final Audience aud) { 127 128 this(iss, sub, aud.toSingleAudienceList(), new Date(new Date().getTime() + 5*60*1000L), null, null, new JWTID(), null); 129 } 130 131 132 /** 133 * Creates a new JWT bearer assertion details (claims set) instance. 134 * 135 * @param iss The issuer identifier. Must not be {@code null}. 136 * @param sub The subject. Must not be {@code null}. 137 * @param aud The audience, typically including the URI of the 138 * authorisation server's token endpoint. Must not be 139 * {@code null}. 140 * @param exp The expiration time. Must not be {@code null}. 141 * @param nbf The time before which the token must not be accepted 142 * for processing, {@code null} if not specified. 143 * @param iat The time at which the token was issued, {@code null} if 144 * not specified. 145 * @param jti Unique identifier for the JWT, {@code null} if not 146 * specified. 147 * @param other Other custom claims to include, {@code null} if none. 148 */ 149 public JWTAssertionDetails(final Issuer iss, 150 final Subject sub, 151 final List<Audience> aud, 152 final Date exp, 153 final Date nbf, 154 final Date iat, 155 final JWTID jti, 156 final Map<String,Object> other) { 157 158 super(iss, sub, aud, iat, exp, jti); 159 this.nbf = nbf; 160 this.other = other; 161 } 162 163 164 /** 165 * Returns the optional not-before time. Corresponds to the {@code nbf} 166 * claim. 167 * 168 * @return The not-before time, {@code null} if not specified. 169 */ 170 public Date getNotBeforeTime() { 171 172 return nbf; 173 } 174 175 176 /** 177 * Returns the optional assertion identifier, as a JWT ID. Corresponds 178 * to the {@code jti} claim. 179 * 180 * @see #getID() 181 * 182 * @return The optional JWT ID, {@code null} if not specified. 183 */ 184 public JWTID getJWTID() { 185 186 Identifier id = getID(); 187 return id != null ? new JWTID(id.getValue()) : null; 188 } 189 190 191 /** 192 * Returns the custom claims. 193 * 194 * @return The custom claims, {@code null} if not specified. 195 */ 196 public Map<String,Object> getCustomClaims() { 197 198 return other; 199 } 200 201 202 /** 203 * Returns a JSON object representation of this JWT bearer assertion 204 * details. 205 * 206 * @return The JSON object. 207 */ 208 public JSONObject toJSONObject() { 209 210 JSONObject o = new JSONObject(); 211 212 o.put("iss", getIssuer().getValue()); 213 o.put("sub", getSubject().getValue()); 214 o.put("aud", Audience.toStringList(getAudience())); 215 o.put("exp", DateUtils.toSecondsSinceEpoch(getExpirationTime())); 216 217 if (nbf != null) 218 o.put("nbf", DateUtils.toSecondsSinceEpoch(nbf)); 219 220 if (getIssueTime() != null) 221 o.put("iat", DateUtils.toSecondsSinceEpoch(getIssueTime())); 222 223 if (getID() != null) 224 o.put("jti", getID().getValue()); 225 226 if (other != null) { 227 o.putAll(other); 228 } 229 230 return o; 231 } 232 233 234 /** 235 * Returns a JSON Web Token (JWT) claims set representation of this 236 * JWT bearer assertion details. 237 * 238 * @return The JWT claims set. 239 */ 240 public JWTClaimsSet toJWTClaimsSet() { 241 242 JWTClaimsSet.Builder builder = new JWTClaimsSet.Builder() 243 .issuer(getIssuer().getValue()) 244 .subject(getSubject().getValue()) 245 .audience(Audience.toStringList(getAudience())) 246 .expirationTime(getExpirationTime()) 247 .notBeforeTime(nbf) // optional 248 .issueTime(getIssueTime()) // optional 249 .jwtID(getID() != null ? getJWTID().getValue() : null); // optional 250 251 // Append custom claims if any 252 if (other != null) { 253 for (Map.Entry<String,?> entry: other.entrySet()) { 254 builder = builder.claim(entry.getKey(), entry.getValue()); 255 } 256 } 257 258 return builder.build(); 259 } 260 261 262 /** 263 * Parses a JWT bearer assertion details (claims set) instance from the 264 * specified JSON object. 265 * 266 * @param jsonObject The JSON object. Must not be {@code null}. 267 * 268 * @return The JWT bearer assertion details. 269 * 270 * @throws ParseException If the JSON object couldn't be parsed to a 271 * JWT bearer assertion details instance. 272 */ 273 public static JWTAssertionDetails parse(final JSONObject jsonObject) 274 throws ParseException { 275 276 // Parse required claims 277 Issuer iss = new Issuer(JSONObjectUtils.getString(jsonObject, "iss")); 278 Subject sub = new Subject(JSONObjectUtils.getString(jsonObject, "sub")); 279 280 List<Audience> aud; 281 282 if (jsonObject.get("aud") instanceof String) { 283 aud = new Audience(JSONObjectUtils.getString(jsonObject, "aud")).toSingleAudienceList(); 284 } else { 285 aud = Audience.create(JSONObjectUtils.getStringList(jsonObject, "aud")); 286 } 287 288 Date exp = DateUtils.fromSecondsSinceEpoch(JSONObjectUtils.getLong(jsonObject, "exp")); 289 290 291 // Parse optional claims 292 293 Date nbf = null; 294 295 if (jsonObject.containsKey("nbf")) 296 nbf = DateUtils.fromSecondsSinceEpoch(JSONObjectUtils.getLong(jsonObject, "nbf")); 297 298 Date iat = null; 299 300 if (jsonObject.containsKey("iat")) 301 iat = DateUtils.fromSecondsSinceEpoch(JSONObjectUtils.getLong(jsonObject, "iat")); 302 303 JWTID jti = null; 304 305 if (jsonObject.containsKey("jti")) 306 jti = new JWTID(JSONObjectUtils.getString(jsonObject, "jti")); 307 308 // Parse custom claims 309 Map<String,Object> other = null; 310 311 Set<String> customClaimNames = jsonObject.keySet(); 312 if (customClaimNames.removeAll(reservedClaimsNames)) { 313 other = new LinkedHashMap<>(); 314 for (String claim: customClaimNames) { 315 other.put(claim, jsonObject.get(claim)); 316 } 317 } 318 319 return new JWTAssertionDetails(iss, sub, aud, exp, nbf, iat, jti, other); 320 } 321 322 323 /** 324 * Parses a JWT bearer assertion details instance from the specified 325 * JWT claims set. 326 * 327 * @param jwtClaimsSet The JWT claims set. Must not be {@code null}. 328 * 329 * @return The JWT bearer assertion details. 330 * 331 * @throws ParseException If the JWT claims set couldn't be parsed to a 332 * JWT bearer assertion details instance. 333 */ 334 public static JWTAssertionDetails parse(final JWTClaimsSet jwtClaimsSet) 335 throws ParseException { 336 337 return parse(jwtClaimsSet.toJSONObject()); 338 } 339}