mirror of
https://github.com/spring-projects/spring-security.git
synced 2025-05-30 16:52:13 +00:00
Support Requiring exp and nbf in JwtTimestampsValidator
Closes gh-17004 Signed-off-by: Ferenc Kemeny <ferenc.kemeny79+oss@gmail.com>
This commit is contained in:
parent
91b21663db
commit
bf05b8b430
@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright 2002-2018 the original author or authors.
|
||||
* Copyright 2002-2025 the original author or authors.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
@ -29,6 +29,7 @@ import org.springframework.security.oauth2.core.OAuth2ErrorCodes;
|
||||
import org.springframework.security.oauth2.core.OAuth2TokenValidator;
|
||||
import org.springframework.security.oauth2.core.OAuth2TokenValidatorResult;
|
||||
import org.springframework.util.Assert;
|
||||
import org.springframework.util.ObjectUtils;
|
||||
|
||||
/**
|
||||
* An implementation of {@link OAuth2TokenValidator} for verifying claims in a Jwt-based
|
||||
@ -54,6 +55,10 @@ public final class JwtTimestampValidator implements OAuth2TokenValidator<Jwt> {
|
||||
|
||||
private final Duration clockSkew;
|
||||
|
||||
private boolean allowEmptyExpiryClaim = true;
|
||||
|
||||
private boolean allowEmptyNotBeforeClaim = true;
|
||||
|
||||
private Clock clock = Clock.systemUTC();
|
||||
|
||||
/**
|
||||
@ -68,30 +73,54 @@ public final class JwtTimestampValidator implements OAuth2TokenValidator<Jwt> {
|
||||
this.clockSkew = clockSkew;
|
||||
}
|
||||
|
||||
/**
|
||||
* Whether to allow the {@code exp} header to be empty. The default value is
|
||||
* {@code true}
|
||||
*
|
||||
* @since 7.0
|
||||
*/
|
||||
public void setAllowEmptyExpiryClaim(boolean allowEmptyExpiryClaim) {
|
||||
this.allowEmptyExpiryClaim = allowEmptyExpiryClaim;
|
||||
}
|
||||
|
||||
/**
|
||||
* Whether to allow the {@code nbf} header to be empty. The default value is
|
||||
* {@code true}
|
||||
*
|
||||
* @since 7.0
|
||||
*/
|
||||
public void setAllowEmptyNotBeforeClaim(boolean allowEmptyNotBeforeClaim) {
|
||||
this.allowEmptyNotBeforeClaim = allowEmptyNotBeforeClaim;
|
||||
}
|
||||
|
||||
@Override
|
||||
public OAuth2TokenValidatorResult validate(Jwt jwt) {
|
||||
Assert.notNull(jwt, "jwt cannot be null");
|
||||
Instant expiry = jwt.getExpiresAt();
|
||||
if (!this.allowEmptyExpiryClaim && ObjectUtils.isEmpty(expiry)) {
|
||||
return createOAuth2Error("exp is required");
|
||||
}
|
||||
if (expiry != null) {
|
||||
if (Instant.now(this.clock).minus(this.clockSkew).isAfter(expiry)) {
|
||||
OAuth2Error oAuth2Error = createOAuth2Error(String.format("Jwt expired at %s", jwt.getExpiresAt()));
|
||||
return OAuth2TokenValidatorResult.failure(oAuth2Error);
|
||||
return createOAuth2Error(String.format("Jwt expired at %s", jwt.getExpiresAt()));
|
||||
}
|
||||
}
|
||||
Instant notBefore = jwt.getNotBefore();
|
||||
if (!this.allowEmptyNotBeforeClaim && ObjectUtils.isEmpty(notBefore)) {
|
||||
return createOAuth2Error("nbf is required");
|
||||
}
|
||||
if (notBefore != null) {
|
||||
if (Instant.now(this.clock).plus(this.clockSkew).isBefore(notBefore)) {
|
||||
OAuth2Error oAuth2Error = createOAuth2Error(String.format("Jwt used before %s", jwt.getNotBefore()));
|
||||
return OAuth2TokenValidatorResult.failure(oAuth2Error);
|
||||
return createOAuth2Error(String.format("Jwt used before %s", jwt.getNotBefore()));
|
||||
}
|
||||
}
|
||||
return OAuth2TokenValidatorResult.success();
|
||||
}
|
||||
|
||||
private OAuth2Error createOAuth2Error(String reason) {
|
||||
private OAuth2TokenValidatorResult createOAuth2Error(String reason) {
|
||||
this.logger.debug(reason);
|
||||
return new OAuth2Error(OAuth2ErrorCodes.INVALID_TOKEN, reason,
|
||||
"https://tools.ietf.org/html/rfc6750#section-3.1");
|
||||
return OAuth2TokenValidatorResult.failure(new OAuth2Error(OAuth2ErrorCodes.INVALID_TOKEN, reason,
|
||||
"https://tools.ietf.org/html/rfc6750#section-3.1"));
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright 2002-2021 the original author or authors.
|
||||
* Copyright 2002-2025 the original author or authors.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
@ -158,6 +158,22 @@ public class JwtTimestampValidatorTests {
|
||||
assertThat(jwtValidator.validate(jwt).hasErrors()).isFalse();
|
||||
}
|
||||
|
||||
@Test
|
||||
public void validateWhenNotAllowEmptyExpiryClaimAndNotBeforeIsValidAndExpiryIsNotSpecifiedThenReturnsSuccessfulResult() {
|
||||
Jwt jwt = TestJwts.jwt().claims((c) -> c.remove(JwtClaimNames.EXP)).notBefore(Instant.MIN).build();
|
||||
JwtTimestampValidator jwtValidator = new JwtTimestampValidator();
|
||||
jwtValidator.setAllowEmptyExpiryClaim(false);
|
||||
assertThat(jwtValidator.validate(jwt).hasErrors()).isTrue();
|
||||
}
|
||||
|
||||
@Test
|
||||
public void validateWhenNotAllowEmptyNotBeforeClaimAndNotBeforeIsNotSpecifiedThenReturnsSuccessfulResult() {
|
||||
Jwt jwt = TestJwts.jwt().claims((c) -> c.remove(JwtClaimNames.NBF)).build();
|
||||
JwtTimestampValidator jwtValidator = new JwtTimestampValidator();
|
||||
jwtValidator.setAllowEmptyNotBeforeClaim(false);
|
||||
assertThat(jwtValidator.validate(jwt).hasErrors()).isTrue();
|
||||
}
|
||||
|
||||
@Test
|
||||
public void validateWhenExpiryIsValidAndNotBeforeIsNotSpecifiedThenReturnsSuccessfulResult() {
|
||||
Jwt jwt = TestJwts.jwt().build();
|
||||
|
Loading…
x
Reference in New Issue
Block a user