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 net.jcip.annotations.Immutable;
028
029import com.nimbusds.jwt.util.DateUtils;
030import com.nimbusds.jose.util.JSONArrayUtils;
031import com.nimbusds.jose.util.JSONObjectUtils;
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 2021-02-02
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 ? 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 JSON object.
761         *
762         * @param name The name of the claim. Must not be {@code null}.
763         *
764         * @return The value of the claim, {@code null} if not specified.
765         *
766         * @throws ParseException If the claim value is not of the required
767         *                        type.
768         */
769        public Map<String, Object> getJSONObjectClaim(final String name)
770                throws ParseException {
771
772                Object value = getClaim(name);
773
774                if (value == null) {
775                        return null;
776                } else if (value instanceof Map) {
777                        Map<String, Object> jsonObject = JSONObjectUtils.newJSONObject();
778                        Map<?,?> map = (Map<?,?>)value;
779                        for (Map.Entry<?,?> entry: map.entrySet()) {
780                                if (entry.getKey() instanceof String) {
781                                        jsonObject.put((String)entry.getKey(), entry.getValue());
782                                }
783                        }
784                        return jsonObject;
785                } else {
786                        throw new ParseException("The \"" + name + "\" claim is not a JSON object or Map", 0);
787                }
788        }
789
790
791        /**
792         * Gets the claims (registered and custom).
793         *
794         * <p>Note that the registered claims Expiration-Time ({@code exp}),
795         * Not-Before-Time ({@code nbf}) and Issued-At ({@code iat}) will be
796         * returned as {@code java.util.Date} instances.
797         *
798         * @return The claims, as an unmodifiable map, empty map if none.
799         */
800        public Map<String,Object> getClaims() {
801
802                return Collections.unmodifiableMap(claims);
803        }
804
805
806        /**
807         * Returns the JSON object representation of this claims set. The
808         * claims are serialised according to their insertion order. Claims
809         * with {@code null} values are not output.
810         *
811         * @return The JSON object representation.
812         */
813        public Map<String, Object> toJSONObject() {
814
815                return toJSONObject(false);
816        }
817        
818        
819        /**
820         * Returns the JSON object representation of this claims set. The
821         * claims are serialised according to their insertion order.
822         *
823         * @param includeClaimsWithNullValues If {@code true} claims with
824         *                                    {@code null} values will also be
825         *                                    output.
826         *
827         * @return The JSON object representation.
828         */
829        public Map<String, Object> toJSONObject(final boolean includeClaimsWithNullValues) {
830                
831                Map<String, Object> o = JSONObjectUtils.newJSONObject();
832                
833                for (Map.Entry<String,Object> claim: claims.entrySet()) {
834                        
835                        if (claim.getValue() instanceof Date) {
836                                
837                                // Transform dates to Unix timestamps
838                                Date dateValue = (Date) claim.getValue();
839                                o.put(claim.getKey(), DateUtils.toSecondsSinceEpoch(dateValue));
840                                
841                        } else if (AUDIENCE_CLAIM.equals(claim.getKey())) {
842                                
843                                // Serialise single audience list and string
844                                List<String> audList = getAudience();
845                                
846                                if (audList != null && ! audList.isEmpty()) {
847                                        if (audList.size() == 1) {
848                                                o.put(AUDIENCE_CLAIM, audList.get(0));
849                                        } else {
850                                                List<Object> audArray = JSONArrayUtils.newJSONArray();
851                                                audArray.addAll(audList);
852                                                o.put(AUDIENCE_CLAIM, audArray);
853                                        }
854                                } else if (includeClaimsWithNullValues) {
855                                        o.put(AUDIENCE_CLAIM, null);
856                                }
857                                
858                        } else if (claim.getValue() != null) {
859                                o.put(claim.getKey(), claim.getValue());
860                        } else if (includeClaimsWithNullValues) {
861                                o.put(claim.getKey(), null);
862                        }
863                }
864                
865                return o;
866        }
867        
868        
869        /**
870         * Returns a JSON object string representation of this claims set. The
871         * claims are serialised according to their insertion order. Claims
872         * with {@code null} values are not output.
873         *
874         * @return The JSON object string representation.
875         */
876        @Override
877        public String toString() {
878
879                return JSONObjectUtils.toJSONString(toJSONObject());
880        }
881        
882        
883        /**
884         * Returns a JSON object string representation of this claims set. The
885         * claims are serialised according to their insertion order.
886         *
887         * @param includeClaimsWithNullValues If {@code true} claims with
888         *                                    {@code null} values will also be
889         *                                    output.
890         *
891         * @return The JSON object string representation.
892         */
893        public String toString(final boolean includeClaimsWithNullValues) {
894
895                return JSONObjectUtils.toJSONString(toJSONObject(includeClaimsWithNullValues));
896        }
897
898        
899        /**
900         * Returns a transformation of this JWT claims set.
901         *
902         * @param <T> Type of the result.
903         * @param transformer The JWT claims set transformer. Must not be
904         *                    {@code null}.
905         *
906         * @return The transformed JWT claims set.
907         */
908        public <T> T toType(final JWTClaimsSetTransformer<T> transformer) {
909
910                return transformer.transform(this);
911        }
912
913
914        /**
915         * Parses a JSON Web Token (JWT) claims set from the specified JSON
916         * object representation.
917         *
918         * @param json The JSON object to parse. Must not be {@code null}.
919         *
920         * @return The JWT claims set.
921         *
922         * @throws ParseException If the specified JSON object doesn't 
923         *                        represent a valid JWT claims set.
924         */
925        public static JWTClaimsSet parse(final Map<String, Object> json)
926                throws ParseException {
927
928                JWTClaimsSet.Builder builder = new JWTClaimsSet.Builder();
929
930                // Parse registered + custom params
931                for (final String name: json.keySet()) {
932                        
933                        switch (name) {
934                                case ISSUER_CLAIM:
935                                        builder.issuer(JSONObjectUtils.getString(json, ISSUER_CLAIM));
936                                        break;
937                                case SUBJECT_CLAIM:
938                                        builder.subject(JSONObjectUtils.getString(json, SUBJECT_CLAIM));
939                                        break;
940                                case AUDIENCE_CLAIM:
941                                        Object audValue = json.get(AUDIENCE_CLAIM);
942                                        if (audValue instanceof String) {
943                                                List<String> singleAud = new ArrayList<>();
944                                                singleAud.add(JSONObjectUtils.getString(json, AUDIENCE_CLAIM));
945                                                builder.audience(singleAud);
946                                        } else if (audValue instanceof List) {
947                                                builder.audience(JSONObjectUtils.getStringList(json, AUDIENCE_CLAIM));
948                                        } else if (audValue == null) {
949                                                builder.audience((String) null);
950                                        }
951                                        break;
952                                case EXPIRATION_TIME_CLAIM:
953                                        builder.expirationTime(new Date(JSONObjectUtils.getLong(json, EXPIRATION_TIME_CLAIM) * 1000));
954                                        break;
955                                case NOT_BEFORE_CLAIM:
956                                        builder.notBeforeTime(new Date(JSONObjectUtils.getLong(json, NOT_BEFORE_CLAIM) * 1000));
957                                        break;
958                                case ISSUED_AT_CLAIM:
959                                        builder.issueTime(new Date(JSONObjectUtils.getLong(json, ISSUED_AT_CLAIM) * 1000));
960                                        break;
961                                case JWT_ID_CLAIM:
962                                        builder.jwtID(JSONObjectUtils.getString(json, JWT_ID_CLAIM));
963                                        break;
964                                default:
965                                        builder.claim(name, json.get(name));
966                                        break;
967                        }
968                }
969
970                return builder.build();
971        }
972
973
974        /**
975         * Parses a JSON Web Token (JWT) claims set from the specified JSON
976         * object string representation.
977         *
978         * @param s The JSON object string to parse. Must not be {@code null}.
979         *
980         * @return The JWT claims set.
981         *
982         * @throws ParseException If the specified JSON object string doesn't
983         *                        represent a valid JWT claims set.
984         */
985        public static JWTClaimsSet parse(final String s)
986                throws ParseException {
987
988                return parse(JSONObjectUtils.parse(s));
989        }
990
991        
992        @Override
993        public boolean equals(Object o) {
994                if (this == o) return true;
995                if (!(o instanceof JWTClaimsSet)) return false;
996                JWTClaimsSet that = (JWTClaimsSet) o;
997                return Objects.equals(claims, that.claims);
998        }
999
1000        
1001        @Override
1002        public int hashCode() {
1003                return Objects.hash(claims);
1004        }
1005}