@ -1,5 +1,5 @@
@@ -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;
@@ -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> {
@@ -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> {
@@ -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" ) ) ;
}
/ * *