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" : "https://client.example.com", 050 * "sub" : "https://client.example.com", 051 * "aud" : "https://server.c2id.com", 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) 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, typically the authorisation server issuer 122 * URI. Must not be {@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 a singleton list with the 138 * authorisation server issuer URI. 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 List<String> audStringList = Audience.toStringList(getAudience()); 215 if (audStringList != null) { 216 if (audStringList.size() == 1) { 217 o.put("aud", audStringList.get(0)); 218 } else { 219 o.put("aud", audStringList); 220 } 221 } 222 o.put("exp", DateUtils.toSecondsSinceEpoch(getExpirationTime())); 223 224 if (nbf != null) 225 o.put("nbf", DateUtils.toSecondsSinceEpoch(nbf)); 226 227 if (getIssueTime() != null) 228 o.put("iat", DateUtils.toSecondsSinceEpoch(getIssueTime())); 229 230 if (getID() != null) 231 o.put("jti", getID().getValue()); 232 233 if (other != null) { 234 o.putAll(other); 235 } 236 237 return o; 238 } 239 240 241 /** 242 * Returns a JSON Web Token (JWT) claims set representation of this 243 * JWT bearer assertion details. 244 * 245 * @return The JWT claims set. 246 */ 247 public JWTClaimsSet toJWTClaimsSet() { 248 249 JWTClaimsSet.Builder builder = new JWTClaimsSet.Builder() 250 .issuer(getIssuer().getValue()) 251 .subject(getSubject().getValue()) 252 .audience(Audience.toStringList(getAudience())) 253 .expirationTime(getExpirationTime()) 254 .notBeforeTime(nbf) // optional 255 .issueTime(getIssueTime()) // optional 256 .jwtID(getID() != null ? getJWTID().getValue() : null); // optional 257 258 // Append custom claims if any 259 if (other != null) { 260 for (Map.Entry<String,?> entry: other.entrySet()) { 261 builder = builder.claim(entry.getKey(), entry.getValue()); 262 } 263 } 264 265 return builder.build(); 266 } 267 268 269 /** 270 * Parses a JWT bearer assertion details (claims set) instance from the 271 * specified JSON object. 272 * 273 * @param jsonObject The JSON object. Must not be {@code null}. 274 * 275 * @return The JWT bearer assertion details. 276 * 277 * @throws ParseException If the JSON object couldn't be parsed to a 278 * JWT bearer assertion details instance. 279 */ 280 public static JWTAssertionDetails parse(final JSONObject jsonObject) 281 throws ParseException { 282 283 // Parse required claims 284 Issuer iss = new Issuer(JSONObjectUtils.getNonBlankString(jsonObject, "iss")); 285 Subject sub = new Subject(JSONObjectUtils.getNonBlankString(jsonObject, "sub")); 286 287 List<Audience> aud; 288 289 if (jsonObject.get("aud") instanceof String) { 290 aud = new Audience(JSONObjectUtils.getNonBlankString(jsonObject, "aud")).toSingleAudienceList(); 291 } else { 292 aud = Audience.create(JSONObjectUtils.getStringList(jsonObject, "aud")); 293 } 294 295 Date exp = DateUtils.fromSecondsSinceEpoch(JSONObjectUtils.getLong(jsonObject, "exp")); 296 297 298 // Parse optional claims 299 300 Date nbf = null; 301 302 if (jsonObject.containsKey("nbf")) 303 nbf = DateUtils.fromSecondsSinceEpoch(JSONObjectUtils.getLong(jsonObject, "nbf")); 304 305 Date iat = null; 306 307 if (jsonObject.containsKey("iat")) 308 iat = DateUtils.fromSecondsSinceEpoch(JSONObjectUtils.getLong(jsonObject, "iat")); 309 310 JWTID jti = null; 311 312 if (jsonObject.containsKey("jti")) 313 jti = new JWTID(JSONObjectUtils.getNonBlankString(jsonObject, "jti")); 314 315 // Parse custom claims 316 Map<String,Object> other = null; 317 318 Set<String> customClaimNames = jsonObject.keySet(); 319 if (customClaimNames.removeAll(reservedClaimsNames)) { 320 other = new LinkedHashMap<>(); 321 for (String claim: customClaimNames) { 322 other.put(claim, jsonObject.get(claim)); 323 } 324 } 325 326 return new JWTAssertionDetails(iss, sub, aud, exp, nbf, iat, jti, other); 327 } 328 329 330 /** 331 * Parses a JWT bearer assertion details instance from the specified 332 * JWT claims set. 333 * 334 * @param jwtClaimsSet The JWT claims set. Must not be {@code null}. 335 * 336 * @return The JWT bearer assertion details. 337 * 338 * @throws ParseException If the JWT claims set couldn't be parsed to a 339 * JWT bearer assertion details instance. 340 */ 341 public static JWTAssertionDetails parse(final JWTClaimsSet jwtClaimsSet) 342 throws ParseException { 343 344 return parse(JSONObjectUtils.toJSONObject(jwtClaimsSet)); 345 } 346}