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