001/*
002 * nimbus-jose-jwt
003 *
004 * Copyright 2012-2016, Connect2id Ltd.
005 *
006 * Licensed under the Apache License, Version 2.0 (the "License"); you may not use
007 * this file except in compliance with the License. You may obtain a copy of the
008 * License at
009 *
010 *    http://www.apache.org/licenses/LICENSE-2.0
011 *
012 * Unless required by applicable law or agreed to in writing, software distributed
013 * under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR
014 * CONDITIONS OF ANY KIND, either express or implied. See the License for the
015 * specific language governing permissions and limitations under the License.
016 */
017
018package com.nimbusds.jwt;
019
020
021import java.io.Serializable;
022import java.net.URI;
023import java.net.URISyntaxException;
024import java.text.ParseException;
025import java.util.*;
026
027import com.nimbusds.jose.util.JSONObjectUtils;
028import com.nimbusds.jose.util.DateUtils;
029import net.jcip.annotations.Immutable;
030import net.minidev.json.JSONArray;
031import net.minidev.json.JSONObject;
032
033
034/**
035 * JSON Web Token (JWT) claims set. This class is immutable.
036 *
037 * <p>Supports all {@link #getRegisteredNames()}  registered claims} of the JWT
038 * specification:
039 *
040 * <ul>
041 *     <li>iss - Issuer
042 *     <li>sub - Subject
043 *     <li>aud - Audience
044 *     <li>exp - Expiration Time
045 *     <li>nbf - Not Before
046 *     <li>iat - Issued At
047 *     <li>jti - JWT ID
048 * </ul>
049 *
050 * <p>The set may also contain custom claims; these will be serialised and
051 * parsed along the registered ones.
052 *
053 * <p>Example JWT claims set:
054 *
055 * <pre>
056 * {
057 *   "sub"                        : "joe",
058 *   "exp"                        : 1300819380,
059 *   "http://example.com/is_root" : true
060 * }
061 * </pre>
062 *
063 * <p>Example usage:
064 *
065 * <pre>
066 * JWTClaimsSet claimsSet = new JWTClaimsSet.Builder()
067 *     .subject("joe")
068 *     .expirationDate(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 2017-05-29
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                 * Builds a new JWT claims set.
301                 *
302                 * @return The JWT claims set.
303                 */
304                public JWTClaimsSet build() {
305
306                        return new JWTClaimsSet(claims);
307                }
308        }
309
310
311        /**
312         * The claims map.
313         */
314        private final Map<String,Object> claims = new LinkedHashMap<>();
315
316
317        /**
318         * Creates a new JWT claims set.
319         *
320         * @param claims The JWT claims set as a map. Must not be {@code null}.
321         */
322        private JWTClaimsSet(final Map<String,Object> claims) {
323                
324                this.claims.putAll(claims);
325        }
326
327
328        /**
329         * Gets the registered JWT claim names.
330         *
331         * @return The registered claim names, as a unmodifiable set.
332         */
333        public static Set<String> getRegisteredNames() {
334
335                return REGISTERED_CLAIM_NAMES;
336        }
337
338
339        /**
340         * Gets the issuer ({@code iss}) claim.
341         *
342         * @return The issuer claim, {@code null} if not specified.
343         */
344        public String getIssuer() {
345
346                try {
347                        return getStringClaim(ISSUER_CLAIM);
348                } catch (ParseException e) {
349                        return null;
350                }
351        }
352
353
354        /**
355         * Gets the subject ({@code sub}) claim.
356         *
357         * @return The subject claim, {@code null} if not specified.
358         */
359        public String getSubject() {
360
361                try {
362                        return getStringClaim(SUBJECT_CLAIM);
363                } catch (ParseException e) {
364                        return null;
365                }
366        }
367
368
369        /**
370         * Gets the audience ({@code aud}) clam.
371         *
372         * @return The audience claim, empty list if not specified.
373         */
374        public List<String> getAudience() {
375
376                List<String> aud;
377                try {
378                        aud = getStringListClaim(AUDIENCE_CLAIM);
379                } catch (ParseException e) {
380                        return Collections.emptyList();
381                }
382                return aud != null ? Collections.unmodifiableList(aud) : Collections.<String>emptyList();
383        }
384
385
386        /**
387         * Gets the expiration time ({@code exp}) claim.
388         *
389         * @return The expiration time, {@code null} if not specified.
390         */
391        public Date getExpirationTime() {
392
393                try {
394                        return getDateClaim(EXPIRATION_TIME_CLAIM);
395                } catch (ParseException e) {
396                        return null;
397                }
398        }
399
400
401        /**
402         * Gets the not-before ({@code nbf}) claim.
403         *
404         * @return The not-before claim, {@code null} if not specified.
405         */
406        public Date getNotBeforeTime() {
407
408                try {
409                        return getDateClaim(NOT_BEFORE_CLAIM);
410                } catch (ParseException e) {
411                        return null;
412                }
413        }
414
415
416        /**
417         * Gets the issued-at ({@code iat}) claim.
418         *
419         * @return The issued-at claim, {@code null} if not specified.
420         */
421        public Date getIssueTime() {
422
423                try {
424                        return getDateClaim(ISSUED_AT_CLAIM);
425                } catch (ParseException e) {
426                        return null;
427                }
428        }
429
430
431        /**
432         * Gets the JWT ID ({@code jti}) claim.
433         *
434         * @return The JWT ID claim, {@code null} if not specified.
435         */
436        public String getJWTID() {
437
438                try {
439                        return getStringClaim(JWT_ID_CLAIM);
440                } catch (ParseException e) {
441                        return null;
442                }
443        }
444
445
446        /**
447         * Gets the specified claim (registered or custom).
448         *
449         * @param name The name of the claim. Must not be {@code null}.
450         *
451         * @return The value of the claim, {@code null} if not specified.
452         */
453        public Object getClaim(final String name) {
454
455                return claims.get(name);
456        }
457
458
459        /**
460         * Gets the specified claim (registered or custom) as
461         * {@link java.lang.String}.
462         *
463         * @param name The name of the claim. Must not be {@code null}.
464         *
465         * @return The value of the claim, {@code null} if not specified.
466         *
467         * @throws ParseException If the claim value is not of the required
468         *                        type.
469         */
470        public String getStringClaim(final String name)
471                throws ParseException {
472                
473                Object value = getClaim(name);
474                
475                if (value == null || value instanceof String) {
476                        return (String)value;
477                } else {
478                        throw new ParseException("The \"" + name + "\" claim is not a String", 0);
479                }
480        }
481
482
483        /**
484         * Gets the specified claims (registered or custom) as a
485         * {@link java.lang.String} array.
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[] getStringArrayClaim(final String name)
495                throws ParseException {
496
497                Object value = getClaim(name);
498
499                if (value == null) {
500                        return null;
501                }
502
503                List<?> list;
504
505                try {
506                        list = (List<?>)getClaim(name);
507
508                } catch (ClassCastException e) {
509                        throw new ParseException("The \"" + name + "\" claim is not a list / JSON array", 0);
510                }
511
512                String[] stringArray = new String[list.size()];
513
514                for (int i=0; i < stringArray.length; i++) {
515
516                        try {
517                                stringArray[i] = (String)list.get(i);
518                        } catch (ClassCastException e) {
519                                throw new ParseException("The \"" + name + "\" claim is not a list / JSON array of strings", 0);
520                        }
521                }
522
523                return stringArray;
524        }
525
526
527        /**
528         * Gets the specified claims (registered or custom) as a
529         * {@link java.util.List} list of strings.
530         *
531         * @param name The name of the claim. Must not be {@code null}.
532         *
533         * @return The value of the claim, {@code null} if not specified.
534         *
535         * @throws ParseException If the claim value is not of the required
536         *                        type.
537         */
538        public List<String> getStringListClaim(final String name)
539                throws ParseException {
540
541                String[] stringArray = getStringArrayClaim(name);
542
543                if (stringArray == null) {
544                        return null;
545                }
546
547                return Collections.unmodifiableList(Arrays.asList(stringArray));
548        }
549        
550        
551        /**
552         * Gets the specified claim (registered or custom) as a
553         * {@link java.net.URI}.
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 couldn't be parsed to a URI.
560         */
561        public URI getURIClaim(final String name)
562                throws ParseException {
563                
564                String uriString = getStringClaim(name);
565                
566                if (uriString == null) {
567                        return null;
568                }
569                
570                try {
571                        return new URI(uriString);
572                } catch (URISyntaxException e) {
573                        throw new ParseException("The \"" + name + "\" claim is not a URI: " + e.getMessage(), 0);
574                }
575        }
576
577
578        /**
579         * Gets the specified claim (registered or custom) as
580         * {@link java.lang.Boolean}.
581         *
582         * @param name The name of the claim. Must not be {@code null}.
583         *
584         * @return The value of the claim, {@code null} if not specified.
585         *
586         * @throws ParseException If the claim value is not of the required
587         *                        type.
588         */
589        public Boolean getBooleanClaim(final String name)
590                throws ParseException {
591                
592                Object value = getClaim(name);
593                
594                if (value == null || value instanceof Boolean) {
595                        return (Boolean)value;
596                } else {
597                        throw new ParseException("The \"" + name + "\" claim is not a Boolean", 0);
598                }
599        }
600
601
602        /**
603         * Gets the specified claim (registered or custom) as
604         * {@link java.lang.Integer}.
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 Integer getIntegerClaim(final String name)
614                throws ParseException {
615                
616                Object value = getClaim(name);
617                
618                if (value == null) {
619                        return null;
620                } else if (value instanceof Number) {
621                        return ((Number)value).intValue();
622                } else {
623                        throw new ParseException("The \"" + name + "\" claim is not an Integer", 0);
624                }
625        }
626
627
628        /**
629         * Gets the specified claim (registered or custom) as
630         * {@link java.lang.Long}.
631         *
632         * @param name The name of the claim. Must not be {@code null}.
633         *
634         * @return The value of the claim, {@code null} if not specified.
635         *
636         * @throws ParseException If the claim value is not of the required
637         *                        type.
638         */
639        public Long getLongClaim(final String name)
640                throws ParseException {
641                
642                Object value = getClaim(name);
643                
644                if (value == null) {
645                        return null;
646                } else if (value instanceof Number) {
647                        return ((Number)value).longValue();
648                } else {
649                        throw new ParseException("The \"" + name + "\" claim is not a Number", 0);
650                }
651        }
652
653
654        /**
655         * Gets the specified claim (registered or custom) as
656         * {@link java.util.Date}. The claim may be represented by a Date
657         * object or a number of a seconds since the Unix epoch.
658         *
659         * @param name The name of the claim. Must not be {@code null}.
660         *
661         * @return The value of the claim, {@code null} if not specified.
662         *
663         * @throws ParseException If the claim value is not of the required
664         *                        type.
665         */
666        public Date getDateClaim(final String name)
667                throws ParseException {
668
669                Object value = getClaim(name);
670
671                if (value == null) {
672                        return null;
673                } else if (value instanceof Date) {
674                        return (Date)value;
675                } else if (value instanceof Number) {
676                        return DateUtils.fromSecondsSinceEpoch(((Number)value).longValue());
677                } else {
678                        throw new ParseException("The \"" + name + "\" claim is not a Date", 0);
679                }
680        }
681
682
683        /**
684         * Gets the specified claim (registered or custom) as
685         * {@link java.lang.Float}.
686         *
687         * @param name The name of the claim. Must not be {@code null}.
688         *
689         * @return The value of the claim, {@code null} if not specified.
690         *
691         * @throws ParseException If the claim value is not of the required
692         *                        type.
693         */
694        public Float getFloatClaim(final String name)
695                throws ParseException {
696                
697                Object value = getClaim(name);
698                
699                if (value == null) {
700                        return null;
701                } else if (value instanceof Number) {
702                        return ((Number)value).floatValue();
703                } else {
704                        throw new ParseException("The \"" + name + "\" claim is not a Float", 0);
705                }
706        }
707
708
709        /**
710         * Gets the specified claim (registered or custom) as
711         * {@link java.lang.Double}.
712         *
713         * @param name The name of the claim. Must not be {@code null}.
714         *
715         * @return The value of the claim, {@code null} if not specified.
716         *
717         * @throws ParseException If the claim value is not of the required
718         *                        type.
719         */
720        public Double getDoubleClaim(final String name)
721                throws ParseException {
722                
723                Object value = getClaim(name);
724                
725                if (value == null) {
726                        return null;
727                } else if (value instanceof Number) {
728                        return ((Number)value).doubleValue();
729                } else {
730                        throw new ParseException("The \"" + name + "\" claim is not a Double", 0);
731                }
732        }
733
734
735        /**
736         * Gets the specified claim (registered or custom) as a
737         * {@link net.minidev.json.JSONObject}.
738         *
739         * @param name The name of the claim. Must not be {@code null}.
740         *
741         * @return The value of the claim, {@code null} if not specified.
742         *
743         * @throws ParseException If the claim value is not of the required
744         *                        type.
745         */
746        public JSONObject getJSONObjectClaim(final String name)
747                throws ParseException {
748
749                Object value = getClaim(name);
750
751                if (value == null) {
752                        return null;
753                } else if (value instanceof JSONObject) {
754                        return (JSONObject)value;
755                } else if (value instanceof Map) {
756                        JSONObject jsonObject = new JSONObject();
757                        Map<?,?> map = (Map<?,?>)value;
758                        for (Map.Entry<?,?> entry: map.entrySet()) {
759                                if (entry.getKey() instanceof String) {
760                                        jsonObject.put((String)entry.getKey(), entry.getValue());
761                                }
762                        }
763                        return jsonObject;
764                } else {
765                        throw new ParseException("The \"" + name + "\" claim is not a JSON object or Map", 0);
766                }
767        }
768
769
770        /**
771         * Gets the claims (registered and custom).
772         *
773         * <p>Note that the registered claims Expiration-Time ({@code exp}),
774         * Not-Before-Time ({@code nbf}) and Issued-At ({@code iat}) will be
775         * returned as {@code java.util.Date} instances.
776         *
777         * @return The claims, as an unmodifiable map, empty map if none.
778         */
779        public Map<String,Object> getClaims() {
780
781                return Collections.unmodifiableMap(claims);
782        }
783
784
785        /**
786         * Returns the JSON object representation of the claims set. The claims
787         * are serialised according to their insertion order.
788         *
789         * @return The JSON object representation.
790         */
791        public JSONObject toJSONObject() {
792
793                JSONObject o = new JSONObject();
794
795                for (Map.Entry<String,Object> claim: claims.entrySet()) {
796
797                        if (claim.getValue() instanceof Date) {
798
799                                // Transform dates to Unix timestamps
800                                Date dateValue = (Date) claim.getValue();
801                                o.put(claim.getKey(), DateUtils.toSecondsSinceEpoch(dateValue));
802
803                        } else if (AUDIENCE_CLAIM.equals(claim.getKey())) {
804
805                                // Serialise single audience list and string
806                                List<String> audList = getAudience();
807
808                                if (audList != null && ! audList.isEmpty()) {
809                                        if (audList.size() == 1) {
810                                                o.put(AUDIENCE_CLAIM, audList.get(0));
811                                        } else {
812                                                JSONArray audArray = new JSONArray();
813                                                audArray.addAll(audList);
814                                                o.put(AUDIENCE_CLAIM, audArray);
815                                        }
816                                }
817
818                        } else if (claim.getValue() != null) {
819                                // Do not output claims with null values!
820                                o.put(claim.getKey(), claim.getValue());
821                        }
822                }
823
824                return o;
825        }
826
827
828        @Override
829        public String toString() {
830
831                return toJSONObject().toJSONString();
832        }
833
834
835        /**
836         * Returns a transformation of this JWT claims set.
837         *
838         * @param <T> Type of the result.
839         * @param transformer The JWT claims set transformer. Must not be
840         *                    {@code null}.
841         *
842         * @return The transformed JWT claims set.
843         */
844        public <T> T toType(final JWTClaimsSetTransformer<T> transformer) {
845
846                return transformer.transform(this);
847        }
848
849
850        /**
851         * Parses a JSON Web Token (JWT) claims set from the specified JSON
852         * object representation.
853         *
854         * @param json The JSON object to parse. Must not be {@code null}.
855         *
856         * @return The JWT claims set.
857         *
858         * @throws ParseException If the specified JSON object doesn't 
859         *                        represent a valid JWT claims set.
860         */
861        public static JWTClaimsSet parse(final JSONObject json)
862                throws ParseException {
863
864                JWTClaimsSet.Builder builder = new JWTClaimsSet.Builder();
865
866                // Parse registered + custom params
867                for (final String name: json.keySet()) {
868
869                        if (name.equals(ISSUER_CLAIM)) {
870
871                                builder.issuer(JSONObjectUtils.getString(json, ISSUER_CLAIM));
872
873                        } else if (name.equals(SUBJECT_CLAIM)) {
874
875                                builder.subject(JSONObjectUtils.getString(json, SUBJECT_CLAIM));
876
877                        } else if (name.equals(AUDIENCE_CLAIM)) {
878
879                                Object audValue = json.get(AUDIENCE_CLAIM);
880
881                                if (audValue instanceof String) {
882                                        List<String> singleAud = new ArrayList<>();
883                                        singleAud.add(JSONObjectUtils.getString(json, AUDIENCE_CLAIM));
884                                        builder.audience(singleAud);
885                                } else if (audValue instanceof List) {
886                                        builder.audience(JSONObjectUtils.getStringList(json, AUDIENCE_CLAIM));
887                                }
888
889                        } else if (name.equals(EXPIRATION_TIME_CLAIM)) {
890
891                                builder.expirationTime(new Date(JSONObjectUtils.getLong(json, EXPIRATION_TIME_CLAIM) * 1000));
892
893                        } else if (name.equals(NOT_BEFORE_CLAIM)) {
894
895                                builder.notBeforeTime(new Date(JSONObjectUtils.getLong(json, NOT_BEFORE_CLAIM) * 1000));
896
897                        } else if (name.equals(ISSUED_AT_CLAIM)) {
898
899                                builder.issueTime(new Date(JSONObjectUtils.getLong(json, ISSUED_AT_CLAIM) * 1000));
900
901                        } else if (name.equals(JWT_ID_CLAIM)) {
902
903                                builder.jwtID(JSONObjectUtils.getString(json, JWT_ID_CLAIM));
904
905                        } else {
906                                builder.claim(name, json.get(name));
907                        }
908                }
909
910                return builder.build();
911        }
912
913
914        /**
915         * Parses a JSON Web Token (JWT) claims set from the specified JSON
916         * object string representation.
917         *
918         * @param s The JSON object string to parse. Must not be {@code null}.
919         *
920         * @return The JWT claims set.
921         *
922         * @throws ParseException If the specified JSON object string doesn't
923         *                        represent a valid JWT claims set.
924         */
925        public static JWTClaimsSet parse(final String s)
926                throws ParseException {
927
928                return parse(JSONObjectUtils.parse(s));
929        }
930}