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