com.nimbusds.jwt.JWTClaimsSet Maven / Gradle / Ivy
Show all versions of nimbus-jose-jwt Show documentation
package com.nimbusds.jwt;
import java.io.Serializable;
import java.text.ParseException;
import java.util.*;
import com.nimbusds.jose.util.JSONObjectUtils;
import com.nimbusds.jose.util.DateUtils;
import net.jcip.annotations.Immutable;
import net.minidev.json.JSONArray;
import net.minidev.json.JSONObject;
/**
* JSON Web Token (JWT) claims set. This class is immutable.
*
* Supports all {@link #getRegisteredNames()} registered claims} of the JWT
* specification:
*
*
* - iss - Issuer
*
- sub - Subject
*
- aud - Audience
*
- exp - Expiration Time
*
- nbf - Not Before
*
- iat - Issued At
*
- jti - JWT ID
*
*
* The set may also contain custom claims; these will be serialised and
* parsed along the registered ones.
*
*
Example JWT claims set:
*
*
* {
* "sub" : "joe",
* "exp" : 1300819380,
* "http://example.com/is_root" : true
* }
*
*
* Example usage:
*
*
* JWTClaimsSet claimsSet = new JWTClaimsSet.Builder()
* .subject("joe")
* .expirationDate(new Date(1300819380 * 1000l)
* .claim("http://example.com/is_root", true)
* .build();
*
*
* @author Vladimir Dzhuvinov
* @author Justin Richer
* @version 2016-04-10
*/
@Immutable
public final class JWTClaimsSet implements Serializable {
private static final long serialVersionUID = 1L;
private static final String ISSUER_CLAIM = "iss";
private static final String SUBJECT_CLAIM = "sub";
private static final String AUDIENCE_CLAIM = "aud";
private static final String EXPIRATION_TIME_CLAIM = "exp";
private static final String NOT_BEFORE_CLAIM = "nbf";
private static final String ISSUED_AT_CLAIM = "iat";
private static final String JWT_ID_CLAIM = "jti";
/**
* The registered claim names.
*/
private static final Set REGISTERED_CLAIM_NAMES;
/**
* Initialises the registered claim name set.
*/
static {
Set n = new HashSet<>();
n.add(ISSUER_CLAIM);
n.add(SUBJECT_CLAIM);
n.add(AUDIENCE_CLAIM);
n.add(EXPIRATION_TIME_CLAIM);
n.add(NOT_BEFORE_CLAIM);
n.add(ISSUED_AT_CLAIM);
n.add(JWT_ID_CLAIM);
REGISTERED_CLAIM_NAMES = Collections.unmodifiableSet(n);
}
/**
* Builder for constructing JSON Web Token (JWT) claims sets.
*
* Example usage:
*
*
* JWTClaimsSet claimsSet = new JWTClaimsSet.Builder()
* .subject("joe")
* .expirationDate(new Date(1300819380 * 1000l)
* .claim("http://example.com/is_root", true)
* .build();
*
*/
public static class Builder {
/**
* The claims.
*/
private final Map claims = new LinkedHashMap<>();
/**
* Creates a new builder.
*/
public Builder() {
// Nothing to do
}
/**
* Creates a new builder with the claims from the specified
* set.
*
* @param jwtClaimsSet The JWT claims set to use. Must not be
* {@code null}.
*/
public Builder(final JWTClaimsSet jwtClaimsSet) {
claims.putAll(jwtClaimsSet.claims);
}
/**
* Sets the issuer ({@code iss}) claim.
*
* @param iss The issuer claim, {@code null} if not specified.
*
* @return This builder.
*/
public Builder issuer(final String iss) {
claims.put(ISSUER_CLAIM, iss);
return this;
}
/**
* Sets the subject ({@code sub}) claim.
*
* @param sub The subject claim, {@code null} if not specified.
*
* @return This builder.
*/
public Builder subject(final String sub) {
claims.put(SUBJECT_CLAIM, sub);
return this;
}
/**
* Sets the audience ({@code aud}) claim.
*
* @param aud The audience claim, {@code null} if not
* specified.
*
* @return This builder.
*/
public Builder audience(final List aud) {
claims.put(AUDIENCE_CLAIM, aud);
return this;
}
/**
* Sets a single-valued audience ({@code aud}) claim.
*
* @param aud The audience claim, {@code null} if not
* specified.
*
* @return This builder.
*/
public Builder audience(final String aud) {
if (aud == null) {
claims.put(AUDIENCE_CLAIM, null);
} else {
claims.put(AUDIENCE_CLAIM, Collections.singletonList(aud));
}
return this;
}
/**
* Sets the expiration time ({@code exp}) claim.
*
* @param exp The expiration time, {@code null} if not
* specified.
*
* @return This builder.
*/
public Builder expirationTime(final Date exp) {
claims.put(EXPIRATION_TIME_CLAIM, exp);
return this;
}
/**
* Sets the not-before ({@code nbf}) claim.
*
* @param nbf The not-before claim, {@code null} if not
* specified.
*
* @return This builder.
*/
public Builder notBeforeTime(final Date nbf) {
claims.put(NOT_BEFORE_CLAIM, nbf);
return this;
}
/**
* Sets the issued-at ({@code iat}) claim.
*
* @param iat The issued-at claim, {@code null} if not
* specified.
*
* @return This builder.
*/
public Builder issueTime(final Date iat) {
claims.put(ISSUED_AT_CLAIM, iat);
return this;
}
/**
* Sets the JWT ID ({@code jti}) claim.
*
* @param jti The JWT ID claim, {@code null} if not specified.
*
* @return This builder.
*/
public Builder jwtID(final String jti) {
claims.put(JWT_ID_CLAIM, jti);
return this;
}
/**
* Sets the specified claim (registered or custom).
*
* @param name The name of the claim to set. Must not be
* {@code null}.
* @param value The value of the claim to set, {@code null} if
* not specified. Should map to a JSON entity.
*
* @return This builder.
*/
public Builder claim(final String name, final Object value) {
claims.put(name, value);
return this;
}
/**
* Builds a new JWT claims set.
*
* @return The JWT claims set.
*/
public JWTClaimsSet build() {
return new JWTClaimsSet(claims);
}
}
/**
* The claims map.
*/
private final Map claims = new LinkedHashMap<>();
/**
* Creates a new JWT claims set.
*
* @param claims The JWT claims set as a map. Must not be {@code null}.
*/
private JWTClaimsSet(final Map claims) {
this.claims.putAll(claims);
}
/**
* Gets the registered JWT claim names.
*
* @return The registered claim names, as a unmodifiable set.
*/
public static Set getRegisteredNames() {
return REGISTERED_CLAIM_NAMES;
}
/**
* Gets the issuer ({@code iss}) claim.
*
* @return The issuer claim, {@code null} if not specified.
*/
public String getIssuer() {
try {
return getStringClaim(ISSUER_CLAIM);
} catch (ParseException e) {
return null;
}
}
/**
* Gets the subject ({@code sub}) claim.
*
* @return The subject claim, {@code null} if not specified.
*/
public String getSubject() {
try {
return getStringClaim(SUBJECT_CLAIM);
} catch (ParseException e) {
return null;
}
}
/**
* Gets the audience ({@code aud}) clam.
*
* @return The audience claim, empty list if not specified.
*/
public List getAudience() {
List aud;
try {
aud = getStringListClaim(AUDIENCE_CLAIM);
} catch (ParseException e) {
return Collections.emptyList();
}
return aud != null ? Collections.unmodifiableList(aud) : Collections.emptyList();
}
/**
* Gets the expiration time ({@code exp}) claim.
*
* @return The expiration time, {@code null} if not specified.
*/
public Date getExpirationTime() {
try {
return getDateClaim(EXPIRATION_TIME_CLAIM);
} catch (ParseException e) {
return null;
}
}
/**
* Gets the not-before ({@code nbf}) claim.
*
* @return The not-before claim, {@code null} if not specified.
*/
public Date getNotBeforeTime() {
try {
return getDateClaim(NOT_BEFORE_CLAIM);
} catch (ParseException e) {
return null;
}
}
/**
* Gets the issued-at ({@code iat}) claim.
*
* @return The issued-at claim, {@code null} if not specified.
*/
public Date getIssueTime() {
try {
return getDateClaim(ISSUED_AT_CLAIM);
} catch (ParseException e) {
return null;
}
}
/**
* Gets the JWT ID ({@code jti}) claim.
*
* @return The JWT ID claim, {@code null} if not specified.
*/
public String getJWTID() {
try {
return getStringClaim(JWT_ID_CLAIM);
} catch (ParseException e) {
return null;
}
}
/**
* Gets the specified claim (registered or custom).
*
* @param name The name of the claim. Must not be {@code null}.
*
* @return The value of the claim, {@code null} if not specified.
*/
public Object getClaim(final String name) {
return claims.get(name);
}
/**
* Gets the specified claim (registered or custom) as
* {@link java.lang.String}.
*
* @param name The name of the claim. Must not be {@code null}.
*
* @return The value of the claim, {@code null} if not specified.
*
* @throws ParseException If the claim value is not of the required
* type.
*/
public String getStringClaim(final String name)
throws ParseException {
Object value = getClaim(name);
if (value == null || value instanceof String) {
return (String)value;
} else {
throw new ParseException("The \"" + name + "\" claim is not a String", 0);
}
}
/**
* Gets the specified claims (registered or custom) as a
* {@link java.lang.String} array.
*
* @param name The name of the claim. Must not be {@code null}.
*
* @return The value of the claim, {@code null} if not specified.
*
* @throws ParseException If the claim value is not of the required
* type.
*/
public String[] getStringArrayClaim(final String name)
throws ParseException {
Object value = getClaim(name);
if (value == null) {
return null;
}
List> list;
try {
list = (List>)getClaim(name);
} catch (ClassCastException e) {
throw new ParseException("The \"" + name + "\" claim is not a list / JSON array", 0);
}
String[] stringArray = new String[list.size()];
for (int i=0; i < stringArray.length; i++) {
try {
stringArray[i] = (String)list.get(i);
} catch (ClassCastException e) {
throw new ParseException("The \"" + name + "\" claim is not a list / JSON array of strings", 0);
}
}
return stringArray;
}
/**
* Gets the specified claims (registered or custom) as a
* {@link java.util.List} list of strings.
*
* @param name The name of the claim. Must not be {@code null}.
*
* @return The value of the claim, {@code null} if not specified.
*
* @throws ParseException If the claim value is not of the required
* type.
*/
public List getStringListClaim(final String name)
throws ParseException {
String[] stringArray = getStringArrayClaim(name);
if (stringArray == null) {
return null;
}
return Collections.unmodifiableList(Arrays.asList(stringArray));
}
/**
* Gets the specified claim (registered or custom) as
* {@link java.lang.Boolean}.
*
* @param name The name of the claim. Must not be {@code null}.
*
* @return The value of the claim, {@code null} if not specified.
*
* @throws ParseException If the claim value is not of the required
* type.
*/
public Boolean getBooleanClaim(final String name)
throws ParseException {
Object value = getClaim(name);
if (value == null || value instanceof Boolean) {
return (Boolean)value;
} else {
throw new ParseException("The \"" + name + "\" claim is not a Boolean", 0);
}
}
/**
* Gets the specified claim (registered or custom) as
* {@link java.lang.Integer}.
*
* @param name The name of the claim. Must not be {@code null}.
*
* @return The value of the claim, {@code null} if not specified.
*
* @throws ParseException If the claim value is not of the required
* type.
*/
public Integer getIntegerClaim(final String name)
throws ParseException {
Object value = getClaim(name);
if (value == null) {
return null;
} else if (value instanceof Number) {
return ((Number)value).intValue();
} else {
throw new ParseException("The \"" + name + "\" claim is not an Integer", 0);
}
}
/**
* Gets the specified claim (registered or custom) as
* {@link java.lang.Long}.
*
* @param name The name of the claim. Must not be {@code null}.
*
* @return The value of the claim, {@code null} if not specified.
*
* @throws ParseException If the claim value is not of the required
* type.
*/
public Long getLongClaim(final String name)
throws ParseException {
Object value = getClaim(name);
if (value == null) {
return null;
} else if (value instanceof Number) {
return ((Number)value).longValue();
} else {
throw new ParseException("The \"" + name + "\" claim is not a Number", 0);
}
}
/**
* Gets the specified claim (registered or custom) as
* {@link java.util.Date}. The claim may be represented by a Date
* object or a number of a seconds since the Unix epoch.
*
* @param name The name of the claim. Must not be {@code null}.
*
* @return The value of the claim, {@code null} if not specified.
*
* @throws ParseException If the claim value is not of the required
* type.
*/
public Date getDateClaim(final String name)
throws ParseException {
Object value = getClaim(name);
if (value == null) {
return null;
} else if (value instanceof Date) {
return (Date)value;
} else if (value instanceof Number) {
return DateUtils.fromSecondsSinceEpoch(((Number)value).longValue());
} else {
throw new ParseException("The \"" + name + "\" claim is not a Date", 0);
}
}
/**
* Gets the specified claim (registered or custom) as
* {@link java.lang.Float}.
*
* @param name The name of the claim. Must not be {@code null}.
*
* @return The value of the claim, {@code null} if not specified.
*
* @throws ParseException If the claim value is not of the required
* type.
*/
public Float getFloatClaim(final String name)
throws ParseException {
Object value = getClaim(name);
if (value == null) {
return null;
} else if (value instanceof Number) {
return ((Number)value).floatValue();
} else {
throw new ParseException("The \"" + name + "\" claim is not a Float", 0);
}
}
/**
* Gets the specified claim (registered or custom) as
* {@link java.lang.Double}.
*
* @param name The name of the claim. Must not be {@code null}.
*
* @return The value of the claim, {@code null} if not specified.
*
* @throws ParseException If the claim value is not of the required
* type.
*/
public Double getDoubleClaim(final String name)
throws ParseException {
Object value = getClaim(name);
if (value == null) {
return null;
} else if (value instanceof Number) {
return ((Number)value).doubleValue();
} else {
throw new ParseException("The \"" + name + "\" claim is not a Double", 0);
}
}
/**
* Gets the specified claim (registered or custom) as a
* {@link net.minidev.json.JSONObject}.
*
* @param name The name of the claim. Must not be {@code null}.
*
* @return The value of the claim, {@code null} if not specified.
*
* @throws ParseException If the claim value is not of the required
* type.
*/
public JSONObject getJSONObjectClaim(final String name)
throws ParseException {
Object value = getClaim(name);
if (value == null) {
return null;
} else if (value instanceof JSONObject) {
return (JSONObject)value;
} else if (value instanceof Map) {
JSONObject jsonObject = new JSONObject();
Map,?> map = (Map,?>)value;
for (Map.Entry,?> entry: map.entrySet()) {
if (entry.getKey() instanceof String) {
jsonObject.put((String)entry.getKey(), entry.getValue());
}
}
return jsonObject;
} else {
throw new ParseException("The \"" + name + "\" claim is not a JSON object or Map", 0);
}
}
/**
* Gets the claims (registered and custom).
*
* Note that the registered claims Expiration-Time ({@code exp}),
* Not-Before-Time ({@code nbf}) and Issued-At ({@code iat}) will be
* returned as {@code java.util.Date} instances.
*
* @return The claims, as an unmodifiable map, empty map if none.
*/
public Map getClaims() {
return Collections.unmodifiableMap(claims);
}
/**
* Returns the JSON object representation of the claims set. The claims
* are serialised according to their insertion order.
*
* @return The JSON object representation.
*/
public JSONObject toJSONObject() {
JSONObject o = new JSONObject();
for (Map.Entry claim: claims.entrySet()) {
if (claim.getValue() instanceof Date) {
// Transform dates to Unix timestamps
Date dateValue = (Date) claim.getValue();
o.put(claim.getKey(), DateUtils.toSecondsSinceEpoch(dateValue));
} else if (AUDIENCE_CLAIM.equals(claim.getKey())) {
// Serialise single audience list and string
List audList = getAudience();
if (audList != null && ! audList.isEmpty()) {
if (audList.size() == 1) {
o.put(AUDIENCE_CLAIM, audList.get(0));
} else {
JSONArray audArray = new JSONArray();
audArray.addAll(audList);
o.put(AUDIENCE_CLAIM, audArray);
}
}
} else if (claim.getValue() != null) {
// Do not output claims with null values!
o.put(claim.getKey(), claim.getValue());
}
}
return o;
}
@Override
public String toString() {
return toJSONObject().toJSONString();
}
/**
* Returns a transformation of this JWT claims set.
*
* @param Type of the result.
* @param transformer The JWT claims set transformer. Must not be
* {@code null}.
*
* @return The transformed JWT claims set.
*/
public T toType(final JWTClaimsSetTransformer transformer) {
return transformer.transform(this);
}
/**
* Parses a JSON Web Token (JWT) claims set from the specified JSON
* object representation.
*
* @param json The JSON object to parse. Must not be {@code null}.
*
* @return The JWT claims set.
*
* @throws ParseException If the specified JSON object doesn't
* represent a valid JWT claims set.
*/
public static JWTClaimsSet parse(final JSONObject json)
throws ParseException {
JWTClaimsSet.Builder builder = new JWTClaimsSet.Builder();
// Parse registered + custom params
for (final String name: json.keySet()) {
if (name.equals(ISSUER_CLAIM)) {
builder.issuer(JSONObjectUtils.getString(json, ISSUER_CLAIM));
} else if (name.equals(SUBJECT_CLAIM)) {
builder.subject(JSONObjectUtils.getString(json, SUBJECT_CLAIM));
} else if (name.equals(AUDIENCE_CLAIM)) {
Object audValue = json.get(AUDIENCE_CLAIM);
if (audValue instanceof String) {
List singleAud = new ArrayList<>();
singleAud.add(JSONObjectUtils.getString(json, AUDIENCE_CLAIM));
builder.audience(singleAud);
} else if (audValue instanceof List) {
builder.audience(JSONObjectUtils.getStringList(json, AUDIENCE_CLAIM));
}
} else if (name.equals(EXPIRATION_TIME_CLAIM)) {
builder.expirationTime(new Date(JSONObjectUtils.getLong(json, EXPIRATION_TIME_CLAIM) * 1000));
} else if (name.equals(NOT_BEFORE_CLAIM)) {
builder.notBeforeTime(new Date(JSONObjectUtils.getLong(json, NOT_BEFORE_CLAIM) * 1000));
} else if (name.equals(ISSUED_AT_CLAIM)) {
builder.issueTime(new Date(JSONObjectUtils.getLong(json, ISSUED_AT_CLAIM) * 1000));
} else if (name.equals(JWT_ID_CLAIM)) {
builder.jwtID(JSONObjectUtils.getString(json, JWT_ID_CLAIM));
} else {
builder.claim(name, json.get(name));
}
}
return builder.build();
}
/**
* Parses a JSON Web Token (JWT) claims set from the specified JSON
* object string representation.
*
* @param s The JSON object string to parse. Must not be {@code null}.
*
* @return The JWT claims set.
*
* @throws ParseException If the specified JSON object string doesn't
* represent a valid JWT claims set.
*/
public static JWTClaimsSet parse(final String s)
throws ParseException {
return parse(JSONObjectUtils.parse(s));
}
}