io.fusionauth.jwt.domain.JWT Maven / Gradle / Ivy
/*
* Copyright (c) 2016-2023, FusionAuth, All Rights Reserved
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing,
* software distributed under the License is distributed on an
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND,
* either express or implied. See the License for the specific
* language governing permissions and limitations under the License.
*/
package io.fusionauth.jwt.domain;
import com.fasterxml.jackson.annotation.JsonAnyGetter;
import com.fasterxml.jackson.annotation.JsonAnySetter;
import com.fasterxml.jackson.annotation.JsonIgnore;
import com.fasterxml.jackson.annotation.JsonProperty;
import com.fasterxml.jackson.databind.annotation.JsonDeserialize;
import com.fasterxml.jackson.databind.annotation.JsonSerialize;
import io.fusionauth.jwt.JWTDecoder;
import io.fusionauth.jwt.JWTEncoder;
import io.fusionauth.jwt.TimeMachineJWTDecoder;
import io.fusionauth.jwt.json.Mapper;
import io.fusionauth.jwt.json.ZonedDateTimeDeserializer;
import io.fusionauth.jwt.json.ZonedDateTimeSerializer;
import java.math.BigDecimal;
import java.math.BigInteger;
import java.time.Instant;
import java.time.ZoneOffset;
import java.time.ZonedDateTime;
import java.util.HashMap;
import java.util.LinkedHashMap;
import java.util.List;
import java.util.Map;
import java.util.Objects;
/**
* JSON Web Token (JWT) as defined by RFC 7519.
*
* From RFC 7519 Section 1. Introduction:
* The suggested pronunciation of JWT is the same as the English word "jot".
*
* The JWT is not Thread-Safe and should not be re-used.
*
* @author Daniel DeGroff
*/
public class JWT {
/**
* The decoded JWT header. This is not considered part of the JWT payload, but is available here for convenience.
*/
@JsonIgnore
public Header header;
/**
* Registered Claim aud
as defined by RFC 7519 Section 4.1.3. Use of this claim is OPTIONAL.
*
* The audience claim identifies the recipients that the JWT is intended for. This may be an array of strings or a
* single string, in either case if the string value contains a :
it must be a URI.
*/
@JsonProperty("aud")
public Object audience;
/**
* Registered Claim exp
as defined by RFC 7519 Section 4.1.4. Use of this claim is OPTIONAL.
*
* The expiration time claim identifies the expiration time on or after which the JWT MUST NOT be accepted for
* processing. The expiration time is expected to provided in UNIX time, or the number of seconds since Epoch.
*/
@JsonProperty("exp")
@JsonDeserialize(using = ZonedDateTimeDeserializer.class)
@JsonSerialize(using = ZonedDateTimeSerializer.class)
public ZonedDateTime expiration;
/**
* Registered Claim iat
as defined by RFC 7519 Section 4.1.6. Use of this claim is OPTIONAL.
*
* The issued at claim identifies the time at which the JWT was issued. The issued at time is expected to provided in
* UNIX time, or the number of seconds since Epoch.
*/
@JsonProperty("iat")
@JsonDeserialize(using = ZonedDateTimeDeserializer.class)
@JsonSerialize(using = ZonedDateTimeSerializer.class)
public ZonedDateTime issuedAt;
/**
* Registered Claim iss
as defined by RFC 7519 Section 4.1.1. Use of this claim is OPTIONAL.
*
* The issuer claim identifies the principal that issued the JWT. If the value contains a :
it must be a
* URI.
*/
@JsonProperty("iss")
public String issuer;
/**
* Registered Claim nbf
as defined by RFC 7519 Section 4.1.5. Use of this claim is OPTIONAL.
*
* This claim identifies the time before which the JWT MUST NOT be accepted for processing. The not before value is
* expected to be provided in UNIX time, or the number of seconds since Epoch.
*/
@JsonProperty("nbf")
@JsonDeserialize(using = ZonedDateTimeDeserializer.class)
@JsonSerialize(using = ZonedDateTimeSerializer.class)
public ZonedDateTime notBefore;
/**
* This Map will contain all the claims that aren't specifically defined in the specification. These still might be
* IANA registered claims, but are not known JWT specification claims.
*/
@JsonAnySetter
public Map otherClaims = new LinkedHashMap<>();
/**
* Registered Claim sub
as defined by RFC 7519 Section 4.1.2. Use of this claim is OPTIONAL.
*
* The subject claim identifies the principal that is the subject of the JWT. If the value contains a :
* it must be a URI.
*/
@JsonProperty("sub")
public String subject;
/**
* Registered Claim jti
as defined by RFC 7519 Section 4.1.7. Use of this claim is OPTIONAL.
*
* The JWT unique ID claim provides a unique identifier for the JWT.
*/
@JsonProperty("jti")
public String uniqueId;
/**
* Return an instance of the JWT Decoder.
*
* @return a JWT decoder.
*/
public static JWTDecoder getDecoder() {
return new JWTDecoder();
}
/**
* Return a JWT Decoder that allows you to go back or of forward in time. Use this at your own risk.
*
* Generally speaking, there should not be a use for this in production code since 'now' should always be 'now',
* but it may come in handy in a test.
*
* @param now a 'now' that can be in the past, present or future.
* @return a JWT decoder with time machine capability.
*/
public static JWTDecoder getTimeMachineDecoder(ZonedDateTime now) {
return new TimeMachineJWTDecoder(now);
}
/**
* Return an instance of the JWT encoder.
*
* @return a JWT encoder.
*/
public static JWTEncoder getEncoder() {
return new JWTEncoder();
}
@JsonIgnore
public Object getHeaderClaim(String name) {
return header != null ? header.get(name) : null;
}
/**
* Add a claim to this JWT. This claim can be public or private, it is up to the caller to properly name the claim as
* to avoid collision.
*
* @param name The name of the JWT claim.
* @param value The value of the JWT claim. This value is an object and is expected to properly serialize.
* @return this.
*/
public JWT addClaim(String name, Object value) {
if (value == null) {
return this;
}
switch (name) {
case "aud":
this.audience = value;
break;
case "exp":
this.expiration = toZonedDateTime("exp", value);
break;
case "iat":
this.issuedAt = toZonedDateTime("iat", value);
break;
case "iss":
this.issuer = (String) value;
break;
case "jti":
this.uniqueId = (String) value;
break;
case "nbf":
this.notBefore = toZonedDateTime("nbf", value);
break;
case "sub":
this.subject = (String) value;
break;
default:
if (value instanceof Double || value instanceof Float) {
value = BigDecimal.valueOf(((Number) value).doubleValue());
} else if (value instanceof Integer || value instanceof Long) {
value = BigInteger.valueOf(((Number) value).longValue());
}
otherClaims.put(name, value);
break;
}
return this;
}
@Override
public boolean equals(Object o) {
if (this == o) return true;
if (o == null || getClass() != o.getClass()) return false;
JWT jwt = (JWT) o;
return Objects.equals(audience, jwt.audience) &&
Objects.equals(otherClaims, jwt.otherClaims) &&
Objects.equals(expiration, jwt.expiration) &&
Objects.equals(issuedAt, jwt.issuedAt) &&
Objects.equals(issuer, jwt.issuer) &&
Objects.equals(notBefore, jwt.notBefore) &&
Objects.equals(subject, jwt.subject) &&
Objects.equals(uniqueId, jwt.uniqueId);
}
/**
* @return Returns all the claims as cool Java types like ZonedDateTime (where appropriate of course). This will
* contain the otherClaims and the known JWT claims.
*/
@JsonIgnore
public Map getAllClaims() {
Map rawClaims = new HashMap<>(otherClaims);
if (audience != null) {
rawClaims.put("aud", audience);
}
if (expiration != null) {
rawClaims.put("exp", expiration);
}
if (issuedAt != null) {
rawClaims.put("iat", issuedAt);
}
if (issuer != null) {
rawClaims.put("iss", issuer);
}
if (notBefore != null) {
rawClaims.put("nbf", notBefore);
}
if (subject != null) {
rawClaims.put("sub", subject);
}
if (uniqueId != null) {
rawClaims.put("jti", uniqueId);
}
return rawClaims;
}
public BigDecimal getBigDecimal(String key) {
return (BigDecimal) lookupClaim(key);
}
public BigInteger getBigInteger(String key) {
return (BigInteger) lookupClaim(key);
}
public Boolean getBoolean(String key) {
return (Boolean) lookupClaim(key);
}
public Double getDouble(String key) {
BigDecimal value = (BigDecimal) lookupClaim(key);
if (value == null) {
return null;
}
return value.doubleValue();
}
public Float getFloat(String key) {
BigDecimal value = (BigDecimal) lookupClaim(key);
if (value == null) {
return null;
}
return value.floatValue();
}
public Integer getInteger(String key) {
BigInteger value = (BigInteger) lookupClaim(key);
if (value == null) {
return null;
}
return value.intValue();
}
public List