001/*
002 * nimbus-jose-jwt
003 *
004 * Copyright 2012-2016, Connect2id Ltd.
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.jwt;
019
020
021import java.io.Serializable;
022import java.net.URI;
023import java.net.URISyntaxException;
024import java.text.ParseException;
025import java.util.*;
026
027import com.nimbusds.jose.util.DateUtils;
028import com.nimbusds.jose.util.JSONObjectUtils;
029import net.jcip.annotations.Immutable;
030import net.minidev.json.JSONArray;
031import net.minidev.json.JSONObject;
032
033
034/**
035 * JSON Web Token (JWT) claims set. This class is immutable.
036 *
037 * <p>Supports all {@link #getRegisteredNames()}  registered claims} of the JWT
038 * specification:
039 *
040 * <ul>
041 *     <li>iss - Issuer
042 *     <li>sub - Subject
043 *     <li>aud - Audience
044 *     <li>exp - Expiration Time
045 *     <li>nbf - Not Before
046 *     <li>iat - Issued At
047 *     <li>jti - JWT ID
048 * </ul>
049 *
050 * <p>The set may also contain custom claims; these will be serialised and
051 * parsed along the registered ones.
052 *
053 * <p>Example JWT claims set:
054 *
055 * <pre>
056 * {
057 *   "sub"                        : "joe",
058 *   "exp"                        : 1300819380,
059 *   "http://example.com/is_root" : true
060 * }
061 * </pre>
062 *
063 * <p>Example usage:
064 *
065 * <pre>
066 * JWTClaimsSet claimsSet = new JWTClaimsSet.Builder()
067 *     .subject("joe")
068 *     .expirationTime(new Date(1300819380 * 1000l)
069 *     .claim("http://example.com/is_root", true)
070 *     .build();
071 * </pre>
072 *
073 * @author Vladimir Dzhuvinov
074 * @author Justin Richer
075 * @version 2019-12-21
076 */
077@Immutable
078public final class JWTClaimsSet implements Serializable {
079
080
081        private static final long serialVersionUID = 1L;
082
083
084        private static final String ISSUER_CLAIM = "iss";
085        private static final String SUBJECT_CLAIM = "sub";
086        private static final String AUDIENCE_CLAIM = "aud";
087        private static final String EXPIRATION_TIME_CLAIM = "exp";
088        private static final String NOT_BEFORE_CLAIM = "nbf";
089        private static final String ISSUED_AT_CLAIM = "iat";
090        private static final String JWT_ID_CLAIM = "jti";
091
092
093        /**
094         * The registered claim names.
095         */
096        private static final Set<String> REGISTERED_CLAIM_NAMES;
097
098
099        /**
100         * Initialises the registered claim name set.
101         */
102        static {
103                Set<String> n = new HashSet<>();
104
105                n.add(ISSUER_CLAIM);
106                n.add(SUBJECT_CLAIM);
107                n.add(AUDIENCE_CLAIM);
108                n.add(EXPIRATION_TIME_CLAIM);
109                n.add(NOT_BEFORE_CLAIM);
110                n.add(ISSUED_AT_CLAIM);
111                n.add(JWT_ID_CLAIM);
112
113                REGISTERED_CLAIM_NAMES = Collections.unmodifiableSet(n);
114        }
115
116
117        /**
118         * Builder for constructing JSON Web Token (JWT) claims sets.
119         *
120         * <p>Example usage:
121         *
122         * <pre>
123         * JWTClaimsSet claimsSet = new JWTClaimsSet.Builder()
124         *     .subject("joe")
125         *     .expirationDate(new Date(1300819380 * 1000l)
126         *     .claim("http://example.com/is_root", true)
127         *     .build();
128         * </pre>
129         */
130        public static class Builder {
131
132
133                /**
134                 * The claims.
135                 */
136                private final Map<String,Object> claims = new LinkedHashMap<>();
137
138
139                /**
140                 * Creates a new builder.
141                 */
142                public Builder() {
143
144                        // Nothing to do
145                }
146
147
148                /**
149                 * Creates a new builder with the claims from the specified
150                 * set.
151                 *
152                 * @param jwtClaimsSet The JWT claims set to use. Must not be
153                 *                     {@code null}.
154                 */
155                public Builder(final JWTClaimsSet jwtClaimsSet) {
156
157                        claims.putAll(jwtClaimsSet.claims);
158                }
159
160
161                /**
162                 * Sets the issuer ({@code iss}) claim.
163                 *
164                 * @param iss The issuer claim, {@code null} if not specified.
165                 *
166                 * @return This builder.
167                 */
168                public Builder issuer(final String iss) {
169
170                        claims.put(ISSUER_CLAIM, iss);
171                        return this;
172                }
173
174
175                /**
176                 * Sets the subject ({@code sub}) claim.
177                 *
178                 * @param sub The subject claim, {@code null} if not specified.
179                 *
180                 * @return This builder.
181                 */
182                public Builder subject(final String sub) {
183
184                        claims.put(SUBJECT_CLAIM, sub);
185                        return this;
186                }
187
188
189                /**
190                 * Sets the audience ({@code aud}) claim.
191                 *
192                 * @param aud The audience claim, {@code null} if not
193                 *            specified.
194                 *
195                 * @return This builder.
196                 */
197                public Builder audience(final List<String> aud) {
198
199                        claims.put(AUDIENCE_CLAIM, aud);
200                        return this;
201                }
202
203
204                /**
205                 * Sets a single-valued audience ({@code aud}) claim.
206                 *
207                 * @param aud The audience claim, {@code null} if not
208                 *            specified.
209                 *
210                 * @return This builder.
211                 */
212                public Builder audience(final String aud) {
213
214                        if (aud == null) {
215                                claims.put(AUDIENCE_CLAIM, null);
216                        } else {
217                                claims.put(AUDIENCE_CLAIM, Collections.singletonList(aud));
218                        }
219                        return this;
220                }
221
222
223                /**
224                 * Sets the expiration time ({@code exp}) claim.
225                 *
226                 * @param exp The expiration time, {@code null} if not
227                 *            specified.
228                 *
229                 * @return This builder.
230                 */
231                public Builder expirationTime(final Date exp) {
232
233                        claims.put(EXPIRATION_TIME_CLAIM, exp);
234                        return this;
235                }
236
237
238                /**
239                 * Sets the not-before ({@code nbf}) claim.
240                 *
241                 * @param nbf The not-before claim, {@code null} if not
242                 *            specified.
243                 *
244                 * @return This builder.
245                 */
246                public Builder notBeforeTime(final Date nbf) {
247
248                        claims.put(NOT_BEFORE_CLAIM, nbf);
249                        return this;
250                }
251
252
253                /**
254                 * Sets the issued-at ({@code iat}) claim.
255                 *
256                 * @param iat The issued-at claim, {@code null} if not
257                 *            specified.
258                 *
259                 * @return This builder.
260                 */
261                public Builder issueTime(final Date iat) {
262
263                        claims.put(ISSUED_AT_CLAIM, iat);
264                        return this;
265                }
266
267
268                /**
269                 * Sets the JWT ID ({@code jti}) claim.
270                 *
271                 * @param jti The JWT ID claim, {@code null} if not specified.
272                 *
273                 * @return This builder.
274                 */
275                public Builder jwtID(final String jti) {
276
277                        claims.put(JWT_ID_CLAIM, jti);
278                        return this;
279                }
280
281
282                /**
283                 * Sets the specified claim (registered or custom).
284                 *
285                 * @param name  The name of the claim to set. Must not be
286                 *              {@code null}.
287                 * @param value The value of the claim to set, {@code null} if
288                 *              not specified. Should map to a JSON entity.
289                 *
290                 * @return This builder.
291                 */
292                public Builder claim(final String name, final Object value) {
293
294                        claims.put(name, value);
295                        return this;
296                }
297                
298                
299                /**
300                 * Gets the claims (registered and custom).
301                 *
302                 * <p>Note that the registered claims Expiration-Time
303                 * ({@code exp}), Not-Before-Time ({@code nbf}) and Issued-At
304                 * ({@code iat}) will be returned as {@code java.util.Date}
305                 * instances.
306                 *
307                 * @return The claims, as an unmodifiable map, empty map if
308                 *         none.
309                 */
310                public Map<String,Object> getClaims() {
311                        
312                        return Collections.unmodifiableMap(claims);
313                }
314
315
316                /**
317                 * Builds a new JWT claims set.
318                 *
319                 * @return The JWT claims set.
320                 */
321                public JWTClaimsSet build() {
322
323                        return new JWTClaimsSet(claims);
324                }
325        }
326
327
328        /**
329         * The claims map.
330         */
331        private final Map<String,Object> claims = new LinkedHashMap<>();
332
333
334        /**
335         * Creates a new JWT claims set.
336         *
337         * @param claims The JWT claims set as a map. Must not be {@code null}.
338         */
339        private JWTClaimsSet(final Map<String,Object> claims) {
340                
341                this.claims.putAll(claims);
342        }
343
344
345        /**
346         * Gets the registered JWT claim names.
347         *
348         * @return The registered claim names, as a unmodifiable set.
349         */
350        public static Set<String> getRegisteredNames() {
351
352                return REGISTERED_CLAIM_NAMES;
353        }
354
355
356        /**
357         * Gets the issuer ({@code iss}) claim.
358         *
359         * @return The issuer claim, {@code null} if not specified.
360         */
361        public String getIssuer() {
362
363                try {
364                        return getStringClaim(ISSUER_CLAIM);
365                } catch (ParseException e) {
366                        return null;
367                }
368        }
369
370
371        /**
372         * Gets the subject ({@code sub}) claim.
373         *
374         * @return The subject claim, {@code null} if not specified.
375         */
376        public String getSubject() {
377
378                try {
379                        return getStringClaim(SUBJECT_CLAIM);
380                } catch (ParseException e) {
381                        return null;
382                }
383        }
384
385
386        /**
387         * Gets the audience ({@code aud}) claim.
388         *
389         * @return The audience claim, empty list if not specified.
390         */
391        public List<String> getAudience() {
392
393                Object audValue = getClaim(AUDIENCE_CLAIM);
394                
395                if (audValue instanceof String) {
396                        // Special case
397                        return Collections.singletonList((String)audValue);
398                }
399                
400                List<String> aud;
401                try {
402                        aud = getStringListClaim(AUDIENCE_CLAIM);
403                } catch (ParseException e) {
404                        return Collections.emptyList();
405                }
406                return aud != null ? Collections.unmodifiableList(aud) : Collections.<String>emptyList();
407        }
408
409
410        /**
411         * Gets the expiration time ({@code exp}) claim.
412         *
413         * @return The expiration time, {@code null} if not specified.
414         */
415        public Date getExpirationTime() {
416
417                try {
418                        return getDateClaim(EXPIRATION_TIME_CLAIM);
419                } catch (ParseException e) {
420                        return null;
421                }
422        }
423
424
425        /**
426         * Gets the not-before ({@code nbf}) claim.
427         *
428         * @return The not-before claim, {@code null} if not specified.
429         */
430        public Date getNotBeforeTime() {
431
432                try {
433                        return getDateClaim(NOT_BEFORE_CLAIM);
434                } catch (ParseException e) {
435                        return null;
436                }
437        }
438
439
440        /**
441         * Gets the issued-at ({@code iat}) claim.
442         *
443         * @return The issued-at claim, {@code null} if not specified.
444         */
445        public Date getIssueTime() {
446
447                try {
448                        return getDateClaim(ISSUED_AT_CLAIM);
449                } catch (ParseException e) {
450                        return null;
451                }
452        }
453
454
455        /**
456         * Gets the JWT ID ({@code jti}) claim.
457         *
458         * @return The JWT ID claim, {@code null} if not specified.
459         */
460        public String getJWTID() {
461
462                try {
463                        return getStringClaim(JWT_ID_CLAIM);
464                } catch (ParseException e) {
465                        return null;
466                }
467        }
468
469
470        /**
471         * Gets the specified claim (registered or custom).
472         *
473         * @param name The name of the claim. Must not be {@code null}.
474         *
475         * @return The value of the claim, {@code null} if not specified.
476         */
477        public Object getClaim(final String name) {
478
479                return claims.get(name);
480        }
481
482
483        /**
484         * Gets the specified claim (registered or custom) as
485         * {@link java.lang.String}.
486         *
487         * @param name The name of the claim. Must not be {@code null}.
488         *
489         * @return The value of the claim, {@code null} if not specified.
490         *
491         * @throws ParseException If the claim value is not of the required
492         *                        type.
493         */
494        public String getStringClaim(final String name)
495                throws ParseException {
496                
497                Object value = getClaim(name);
498                
499                if (value == null || value instanceof String) {
500                        return (String)value;
501                } else {
502                        throw new ParseException("The \"" + name + "\" claim is not a String", 0);
503                }
504        }
505
506
507        /**
508         * Gets the specified claims (registered or custom) as a
509         * {@link java.lang.String} array.
510         *
511         * @param name The name of the claim. Must not be {@code null}.
512         *
513         * @return The value of the claim, {@code null} if not specified.
514         *
515         * @throws ParseException If the claim value is not of the required
516         *                        type.
517         */
518        public String[] getStringArrayClaim(final String name)
519                throws ParseException {
520
521                Object value = getClaim(name);
522
523                if (value == null) {
524                        return null;
525                }
526
527                List<?> list;
528
529                try {
530                        list = (List<?>)getClaim(name);
531
532                } catch (ClassCastException e) {
533                        throw new ParseException("The \"" + name + "\" claim is not a list / JSON array", 0);
534                }
535
536                String[] stringArray = new String[list.size()];
537
538                for (int i=0; i < stringArray.length; i++) {
539
540                        try {
541                                stringArray[i] = (String)list.get(i);
542                        } catch (ClassCastException e) {
543                                throw new ParseException("The \"" + name + "\" claim is not a list / JSON array of strings", 0);
544                        }
545                }
546
547                return stringArray;
548        }
549
550
551        /**
552         * Gets the specified claims (registered or custom) as a
553         * {@link java.util.List} list of strings.
554         *
555         * @param name The name of the claim. Must not be {@code null}.
556         *
557         * @return The value of the claim, {@code null} if not specified.
558         *
559         * @throws ParseException If the claim value is not of the required
560         *                        type.
561         */
562        public List<String> getStringListClaim(final String name)
563                throws ParseException {
564
565                String[] stringArray = getStringArrayClaim(name);
566
567                if (stringArray == null) {
568                        return null;
569                }
570
571                return Collections.unmodifiableList(Arrays.asList(stringArray));
572        }
573        
574        
575        /**
576         * Gets the specified claim (registered or custom) as a
577         * {@link java.net.URI}.
578         *
579         * @param name The name of the claim. Must not be {@code null}.
580         *
581         * @return The value of the claim, {@code null} if not specified.
582         *
583         * @throws ParseException If the claim couldn't be parsed to a URI.
584         */
585        public URI getURIClaim(final String name)
586                throws ParseException {
587                
588                String uriString = getStringClaim(name);
589                
590                if (uriString == null) {
591                        return null;
592                }
593                
594                try {
595                        return new URI(uriString);
596                } catch (URISyntaxException e) {
597                        throw new ParseException("The \"" + name + "\" claim is not a URI: " + e.getMessage(), 0);
598                }
599        }
600
601
602        /**
603         * Gets the specified claim (registered or custom) as
604         * {@link java.lang.Boolean}.
605         *
606         * @param name The name of the claim. Must not be {@code null}.
607         *
608         * @return The value of the claim, {@code null} if not specified.
609         *
610         * @throws ParseException If the claim value is not of the required
611         *                        type.
612         */
613        public Boolean getBooleanClaim(final String name)
614                throws ParseException {
615                
616                Object value = getClaim(name);
617                
618                if (value == null || value instanceof Boolean) {
619                        return (Boolean)value;
620                } else {
621                        throw new ParseException("The \"" + name + "\" claim is not a Boolean", 0);
622                }
623        }
624
625
626        /**
627         * Gets the specified claim (registered or custom) as
628         * {@link java.lang.Integer}.
629         *
630         * @param name The name of the claim. Must not be {@code null}.
631         *
632         * @return The value of the claim, {@code null} if not specified.
633         *
634         * @throws ParseException If the claim value is not of the required
635         *                        type.
636         */
637        public Integer getIntegerClaim(final String name)
638                throws ParseException {
639                
640                Object value = getClaim(name);
641                
642                if (value == null) {
643                        return null;
644                } else if (value instanceof Number) {
645                        return ((Number)value).intValue();
646                } else {
647                        throw new ParseException("The \"" + name + "\" claim is not an Integer", 0);
648                }
649        }
650
651
652        /**
653         * Gets the specified claim (registered or custom) as
654         * {@link java.lang.Long}.
655         *
656         * @param name The name of the claim. Must not be {@code null}.
657         *
658         * @return The value of the claim, {@code null} if not specified.
659         *
660         * @throws ParseException If the claim value is not of the required
661         *                        type.
662         */
663        public Long getLongClaim(final String name)
664                throws ParseException {
665                
666                Object value = getClaim(name);
667                
668                if (value == null) {
669                        return null;
670                } else if (value instanceof Number) {
671                        return ((Number)value).longValue();
672                } else {
673                        throw new ParseException("The \"" + name + "\" claim is not a Number", 0);
674                }
675        }
676
677
678        /**
679         * Gets the specified claim (registered or custom) as
680         * {@link java.util.Date}. The claim may be represented by a Date
681         * object or a number of a seconds since the Unix epoch.
682         *
683         * @param name The name of the claim. Must not be {@code null}.
684         *
685         * @return The value of the claim, {@code null} if not specified.
686         *
687         * @throws ParseException If the claim value is not of the required
688         *                        type.
689         */
690        public Date getDateClaim(final String name)
691                throws ParseException {
692
693                Object value = getClaim(name);
694
695                if (value == null) {
696                        return null;
697                } else if (value instanceof Date) {
698                        return (Date)value;
699                } else if (value instanceof Number) {
700                        return DateUtils.fromSecondsSinceEpoch(((Number)value).longValue());
701                } else {
702                        throw new ParseException("The \"" + name + "\" claim is not a Date", 0);
703                }
704        }
705
706
707        /**
708         * Gets the specified claim (registered or custom) as
709         * {@link java.lang.Float}.
710         *
711         * @param name The name of the claim. Must not be {@code null}.
712         *
713         * @return The value of the claim, {@code null} if not specified.
714         *
715         * @throws ParseException If the claim value is not of the required
716         *                        type.
717         */
718        public Float getFloatClaim(final String name)
719                throws ParseException {
720                
721                Object value = getClaim(name);
722                
723                if (value == null) {
724                        return null;
725                } else if (value instanceof Number) {
726                        return ((Number)value).floatValue();
727                } else {
728                        throw new ParseException("The \"" + name + "\" claim is not a Float", 0);
729                }
730        }
731
732
733        /**
734         * Gets the specified claim (registered or custom) as
735         * {@link java.lang.Double}.
736         *
737         * @param name The name of the claim. Must not be {@code null}.
738         *
739         * @return The value of the claim, {@code null} if not specified.
740         *
741         * @throws ParseException If the claim value is not of the required
742         *                        type.
743         */
744        public Double getDoubleClaim(final String name)
745                throws ParseException {
746                
747                Object value = getClaim(name);
748                
749                if (value == null) {
750                        return null;
751                } else if (value instanceof Number) {
752                        return ((Number)value).doubleValue();
753                } else {
754                        throw new ParseException("The \"" + name + "\" claim is not a Double", 0);
755                }
756        }
757
758
759        /**
760         * Gets the specified claim (registered or custom) as a
761         * {@link net.minidev.json.JSONObject}.
762         *
763         * @param name The name of the claim. Must not be {@code null}.
764         *
765         * @return The value of the claim, {@code null} if not specified.
766         *
767         * @throws ParseException If the claim value is not of the required
768         *                        type.
769         */
770        public JSONObject getJSONObjectClaim(final String name)
771                throws ParseException {
772
773                Object value = getClaim(name);
774
775                if (value == null) {
776                        return null;
777                } else if (value instanceof JSONObject) {
778                        return (JSONObject)value;
779                } else if (value instanceof Map) {
780                        JSONObject jsonObject = new JSONObject();
781                        Map<?,?> map = (Map<?,?>)value;
782                        for (Map.Entry<?,?> entry: map.entrySet()) {
783                                if (entry.getKey() instanceof String) {
784                                        jsonObject.put((String)entry.getKey(), entry.getValue());
785                                }
786                        }
787                        return jsonObject;
788                } else {
789                        throw new ParseException("The \"" + name + "\" claim is not a JSON object or Map", 0);
790                }
791        }
792
793
794        /**
795         * Gets the claims (registered and custom).
796         *
797         * <p>Note that the registered claims Expiration-Time ({@code exp}),
798         * Not-Before-Time ({@code nbf}) and Issued-At ({@code iat}) will be
799         * returned as {@code java.util.Date} instances.
800         *
801         * @return The claims, as an unmodifiable map, empty map if none.
802         */
803        public Map<String,Object> getClaims() {
804
805                return Collections.unmodifiableMap(claims);
806        }
807
808
809        /**
810         * Returns the JSON object representation of the claims set. The claims
811         * are serialised according to their insertion order. Claims with
812         * {@code null} values are not output.
813         *
814         * @return The JSON object representation.
815         */
816        public JSONObject toJSONObject() {
817
818                return toJSONObject(false);
819        }
820        
821        
822        /**
823         * Returns the JSON object representation of the claims set. The claims
824         * are serialised according to their insertion order.
825         *
826         * @param includeClaimsWithNullValues If {@code true} claims with
827         *                                    {@code null} values will also be
828         *                                    output.
829         *
830         * @return The JSON object representation.
831         */
832        public JSONObject toJSONObject(final boolean includeClaimsWithNullValues) {
833                
834                JSONObject o = new JSONObject();
835                
836                for (Map.Entry<String,Object> claim: claims.entrySet()) {
837                        
838                        if (claim.getValue() instanceof Date) {
839                                
840                                // Transform dates to Unix timestamps
841                                Date dateValue = (Date) claim.getValue();
842                                o.put(claim.getKey(), DateUtils.toSecondsSinceEpoch(dateValue));
843                                
844                        } else if (AUDIENCE_CLAIM.equals(claim.getKey())) {
845                                
846                                // Serialise single audience list and string
847                                List<String> audList = getAudience();
848                                
849                                if (audList != null && ! audList.isEmpty()) {
850                                        if (audList.size() == 1) {
851                                                o.put(AUDIENCE_CLAIM, audList.get(0));
852                                        } else {
853                                                JSONArray audArray = new JSONArray();
854                                                audArray.addAll(audList);
855                                                o.put(AUDIENCE_CLAIM, audArray);
856                                        }
857                                } else if (includeClaimsWithNullValues) {
858                                        o.put(AUDIENCE_CLAIM, null);
859                                }
860                                
861                        } else if (claim.getValue() != null) {
862                                o.put(claim.getKey(), claim.getValue());
863                        } else if (includeClaimsWithNullValues) {
864                                o.put(claim.getKey(), null);
865                        }
866                }
867                
868                return o;
869        }
870
871
872        @Override
873        public String toString() {
874
875                return toJSONObject().toJSONString();
876        }
877
878
879        /**
880         * Returns a transformation of this JWT claims set.
881         *
882         * @param <T> Type of the result.
883         * @param transformer The JWT claims set transformer. Must not be
884         *                    {@code null}.
885         *
886         * @return The transformed JWT claims set.
887         */
888        public <T> T toType(final JWTClaimsSetTransformer<T> transformer) {
889
890                return transformer.transform(this);
891        }
892
893
894        /**
895         * Parses a JSON Web Token (JWT) claims set from the specified JSON
896         * object representation.
897         *
898         * @param json The JSON object to parse. Must not be {@code null}.
899         *
900         * @return The JWT claims set.
901         *
902         * @throws ParseException If the specified JSON object doesn't 
903         *                        represent a valid JWT claims set.
904         */
905        public static JWTClaimsSet parse(final JSONObject json)
906                throws ParseException {
907
908                JWTClaimsSet.Builder builder = new JWTClaimsSet.Builder();
909
910                // Parse registered + custom params
911                for (final String name: json.keySet()) {
912
913                        if (name.equals(ISSUER_CLAIM)) {
914
915                                builder.issuer(JSONObjectUtils.getString(json, ISSUER_CLAIM));
916
917                        } else if (name.equals(SUBJECT_CLAIM)) {
918
919                                builder.subject(JSONObjectUtils.getString(json, SUBJECT_CLAIM));
920
921                        } else if (name.equals(AUDIENCE_CLAIM)) {
922
923                                Object audValue = json.get(AUDIENCE_CLAIM);
924
925                                if (audValue instanceof String) {
926                                        List<String> singleAud = new ArrayList<>();
927                                        singleAud.add(JSONObjectUtils.getString(json, AUDIENCE_CLAIM));
928                                        builder.audience(singleAud);
929                                } else if (audValue instanceof List) {
930                                        builder.audience(JSONObjectUtils.getStringList(json, AUDIENCE_CLAIM));
931                                } else if (audValue == null) {
932                                        builder.audience((String)null);
933                                }
934
935                        } else if (name.equals(EXPIRATION_TIME_CLAIM)) {
936
937                                builder.expirationTime(new Date(JSONObjectUtils.getLong(json, EXPIRATION_TIME_CLAIM) * 1000));
938
939                        } else if (name.equals(NOT_BEFORE_CLAIM)) {
940
941                                builder.notBeforeTime(new Date(JSONObjectUtils.getLong(json, NOT_BEFORE_CLAIM) * 1000));
942
943                        } else if (name.equals(ISSUED_AT_CLAIM)) {
944
945                                builder.issueTime(new Date(JSONObjectUtils.getLong(json, ISSUED_AT_CLAIM) * 1000));
946
947                        } else if (name.equals(JWT_ID_CLAIM)) {
948
949                                builder.jwtID(JSONObjectUtils.getString(json, JWT_ID_CLAIM));
950
951                        } else {
952                                builder.claim(name, json.get(name));
953                        }
954                }
955
956                return builder.build();
957        }
958
959
960        /**
961         * Parses a JSON Web Token (JWT) claims set from the specified JSON
962         * object string representation.
963         *
964         * @param s The JSON object string to parse. Must not be {@code null}.
965         *
966         * @return The JWT claims set.
967         *
968         * @throws ParseException If the specified JSON object string doesn't
969         *                        represent a valid JWT claims set.
970         */
971        public static JWTClaimsSet parse(final String s)
972                throws ParseException {
973
974                return parse(JSONObjectUtils.parse(s));
975        }
976
977        
978        @Override
979        public boolean equals(Object o) {
980                if (this == o) return true;
981                if (!(o instanceof JWTClaimsSet)) return false;
982                JWTClaimsSet that = (JWTClaimsSet) o;
983                return Objects.equals(claims, that.claims);
984        }
985
986        
987        @Override
988        public int hashCode() {
989                return Objects.hash(claims);
990        }
991}