com.auth0.jwt.JWTVerifier Maven / Gradle / Ivy
Show all versions of java-jwt Show documentation
package com.auth0.jwt;
import com.auth0.jwt.algorithms.Algorithm;
import com.auth0.jwt.exceptions.*;
import com.auth0.jwt.impl.JWTParser;
import com.auth0.jwt.interfaces.Claim;
import com.auth0.jwt.interfaces.DecodedJWT;
import com.auth0.jwt.impl.ExpectedCheckHolder;
import com.auth0.jwt.interfaces.Verification;
import java.time.Clock;
import java.time.Duration;
import java.time.Instant;
import java.time.temporal.ChronoUnit;
import java.util.*;
import java.util.function.BiPredicate;
/**
* The JWTVerifier class holds the verify method to assert that a given Token has not only a proper JWT format,
* but also its signature matches.
*
* This class is thread-safe.
*
* @see com.auth0.jwt.interfaces.JWTVerifier
*/
public final class JWTVerifier implements com.auth0.jwt.interfaces.JWTVerifier {
private final Algorithm algorithm;
final List expectedChecks;
private final JWTParser parser;
JWTVerifier(Algorithm algorithm, List expectedChecks) {
this.algorithm = algorithm;
this.expectedChecks = Collections.unmodifiableList(expectedChecks);
this.parser = new JWTParser();
}
/**
* Initialize a {@link Verification} instance using the given Algorithm.
*
* @param algorithm the Algorithm to use on the JWT verification.
* @return a {@link Verification} instance to configure.
* @throws IllegalArgumentException if the provided algorithm is null.
*/
static Verification init(Algorithm algorithm) throws IllegalArgumentException {
return new BaseVerification(algorithm);
}
/**
* {@link Verification} implementation that accepts all the expected Claim values for verification, and
* builds a {@link com.auth0.jwt.interfaces.JWTVerifier} used to verify a JWT's signature and expected claims.
*
* Note that this class is not thread-safe. Calling {@link #build()} returns an instance of
* {@link com.auth0.jwt.interfaces.JWTVerifier} which can be reused.
*/
public static class BaseVerification implements Verification {
private final Algorithm algorithm;
private final List expectedChecks;
private long defaultLeeway;
private final Map customLeeways;
private boolean ignoreIssuedAt;
private Clock clock;
BaseVerification(Algorithm algorithm) throws IllegalArgumentException {
if (algorithm == null) {
throw new IllegalArgumentException("The Algorithm cannot be null.");
}
this.algorithm = algorithm;
this.expectedChecks = new ArrayList<>();
this.customLeeways = new HashMap<>();
this.defaultLeeway = 0;
}
@Override
public Verification withIssuer(String... issuer) {
List value = isNullOrEmpty(issuer) ? null : Arrays.asList(issuer);
addCheck(RegisteredClaims.ISSUER, ((claim, decodedJWT) -> {
if (verifyNull(claim, value)) {
return true;
}
if (value == null || !value.contains(claim.asString())) {
throw new IncorrectClaimException(
"The Claim 'iss' value doesn't match the required issuer.", RegisteredClaims.ISSUER, claim);
}
return true;
}));
return this;
}
@Override
public Verification withSubject(String subject) {
addCheck(RegisteredClaims.SUBJECT, (claim, decodedJWT) ->
verifyNull(claim, subject) || subject.equals(claim.asString()));
return this;
}
@Override
public Verification withAudience(String... audience) {
List value = isNullOrEmpty(audience) ? null : Arrays.asList(audience);
addCheck(RegisteredClaims.AUDIENCE, ((claim, decodedJWT) -> {
if (verifyNull(claim, value)) {
return true;
}
if (!assertValidAudienceClaim(decodedJWT.getAudience(), value, true)) {
throw new IncorrectClaimException("The Claim 'aud' value doesn't contain the required audience.",
RegisteredClaims.AUDIENCE, claim);
}
return true;
}));
return this;
}
@Override
public Verification withAnyOfAudience(String... audience) {
List value = isNullOrEmpty(audience) ? null : Arrays.asList(audience);
addCheck(RegisteredClaims.AUDIENCE, ((claim, decodedJWT) -> {
if (verifyNull(claim, value)) {
return true;
}
if (!assertValidAudienceClaim(decodedJWT.getAudience(), value, false)) {
throw new IncorrectClaimException("The Claim 'aud' value doesn't contain the required audience.",
RegisteredClaims.AUDIENCE, claim);
}
return true;
}));
return this;
}
@Override
public Verification acceptLeeway(long leeway) throws IllegalArgumentException {
assertPositive(leeway);
this.defaultLeeway = leeway;
return this;
}
@Override
public Verification acceptExpiresAt(long leeway) throws IllegalArgumentException {
assertPositive(leeway);
customLeeways.put(RegisteredClaims.EXPIRES_AT, leeway);
return this;
}
@Override
public Verification acceptNotBefore(long leeway) throws IllegalArgumentException {
assertPositive(leeway);
customLeeways.put(RegisteredClaims.NOT_BEFORE, leeway);
return this;
}
@Override
public Verification acceptIssuedAt(long leeway) throws IllegalArgumentException {
assertPositive(leeway);
customLeeways.put(RegisteredClaims.ISSUED_AT, leeway);
return this;
}
@Override
public Verification ignoreIssuedAt() {
this.ignoreIssuedAt = true;
return this;
}
@Override
public Verification withJWTId(String jwtId) {
addCheck(RegisteredClaims.JWT_ID, ((claim, decodedJWT) ->
verifyNull(claim, jwtId) || jwtId.equals(claim.asString())));
return this;
}
@Override
public Verification withClaimPresence(String name) throws IllegalArgumentException {
assertNonNull(name);
//since addCheck already checks presence, we just return true
withClaim(name, ((claim, decodedJWT) -> true));
return this;
}
@Override
public Verification withNullClaim(String name) throws IllegalArgumentException {
assertNonNull(name);
withClaim(name, ((claim, decodedJWT) -> claim.isNull()));
return this;
}
@Override
public Verification withClaim(String name, Boolean value) throws IllegalArgumentException {
assertNonNull(name);
addCheck(name, ((claim, decodedJWT) -> verifyNull(claim, value)
|| value.equals(claim.asBoolean())));
return this;
}
@Override
public Verification withClaim(String name, Integer value) throws IllegalArgumentException {
assertNonNull(name);
addCheck(name, ((claim, decodedJWT) -> verifyNull(claim, value)
|| value.equals(claim.asInt())));
return this;
}
@Override
public Verification withClaim(String name, Long value) throws IllegalArgumentException {
assertNonNull(name);
addCheck(name, ((claim, decodedJWT) -> verifyNull(claim, value)
|| value.equals(claim.asLong())));
return this;
}
@Override
public Verification withClaim(String name, Double value) throws IllegalArgumentException {
assertNonNull(name);
addCheck(name, ((claim, decodedJWT) -> verifyNull(claim, value)
|| value.equals(claim.asDouble())));
return this;
}
@Override
public Verification withClaim(String name, String value) throws IllegalArgumentException {
assertNonNull(name);
addCheck(name, ((claim, decodedJWT) -> verifyNull(claim, value)
|| value.equals(claim.asString())));
return this;
}
@Override
public Verification withClaim(String name, Date value) throws IllegalArgumentException {
return withClaim(name, value != null ? value.toInstant() : null);
}
@Override
public Verification withClaim(String name, Instant value) throws IllegalArgumentException {
assertNonNull(name);
// Since date-time claims are serialized as epoch seconds,
// we need to compare them with only seconds-granularity
addCheck(name,
((claim, decodedJWT) -> verifyNull(claim, value)
|| value.truncatedTo(ChronoUnit.SECONDS).equals(claim.asInstant())));
return this;
}
@Override
public Verification withClaim(String name, BiPredicate predicate)
throws IllegalArgumentException {
assertNonNull(name);
addCheck(name, ((claim, decodedJWT) -> verifyNull(claim, predicate)
|| predicate.test(claim, decodedJWT)));
return this;
}
@Override
public Verification withArrayClaim(String name, String... items) throws IllegalArgumentException {
assertNonNull(name);
addCheck(name, ((claim, decodedJWT) -> verifyNull(claim, items)
|| assertValidCollectionClaim(claim, items)));
return this;
}
@Override
public Verification withArrayClaim(String name, Integer... items) throws IllegalArgumentException {
assertNonNull(name);
addCheck(name, ((claim, decodedJWT) -> verifyNull(claim, items)
|| assertValidCollectionClaim(claim, items)));
return this;
}
@Override
public Verification withArrayClaim(String name, Long... items) throws IllegalArgumentException {
assertNonNull(name);
addCheck(name, ((claim, decodedJWT) -> verifyNull(claim, items)
|| assertValidCollectionClaim(claim, items)));
return this;
}
@Override
public JWTVerifier build() {
return this.build(Clock.systemUTC());
}
/**
* Creates a new and reusable instance of the JWTVerifier with the configuration already provided.
* ONLY FOR TEST PURPOSES.
*
* @param clock the instance that will handle the current time.
* @return a new JWTVerifier instance with a custom {@link java.time.Clock}
*/
public JWTVerifier build(Clock clock) {
this.clock = clock;
addMandatoryClaimChecks();
return new JWTVerifier(algorithm, expectedChecks);
}
/**
* Fetches the Leeway set for claim or returns the {@link BaseVerification#defaultLeeway}.
*
* @param name Claim for which leeway is fetched
* @return Leeway value set for the claim
*/
public long getLeewayFor(String name) {
return customLeeways.getOrDefault(name, defaultLeeway);
}
private void addMandatoryClaimChecks() {
long expiresAtLeeway = getLeewayFor(RegisteredClaims.EXPIRES_AT);
long notBeforeLeeway = getLeewayFor(RegisteredClaims.NOT_BEFORE);
long issuedAtLeeway = getLeewayFor(RegisteredClaims.ISSUED_AT);
expectedChecks.add(constructExpectedCheck(RegisteredClaims.EXPIRES_AT, (claim, decodedJWT) ->
assertValidInstantClaim(RegisteredClaims.EXPIRES_AT, claim, expiresAtLeeway, true)));
expectedChecks.add(constructExpectedCheck(RegisteredClaims.NOT_BEFORE, (claim, decodedJWT) ->
assertValidInstantClaim(RegisteredClaims.NOT_BEFORE, claim, notBeforeLeeway, false)));
if (!ignoreIssuedAt) {
expectedChecks.add(constructExpectedCheck(RegisteredClaims.ISSUED_AT, (claim, decodedJWT) ->
assertValidInstantClaim(RegisteredClaims.ISSUED_AT, claim, issuedAtLeeway, false)));
}
}
private boolean assertValidCollectionClaim(Claim claim, Object[] expectedClaimValue) {
List