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