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