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    }