diff --git a/oauth2/oauth2-authorization-server/src/main/java/org/springframework/security/oauth2/server/authorization/token/JwtGenerator.java b/oauth2/oauth2-authorization-server/src/main/java/org/springframework/security/oauth2/server/authorization/token/JwtGenerator.java index fe94c4a8de..cc400919a8 100644 --- a/oauth2/oauth2-authorization-server/src/main/java/org/springframework/security/oauth2/server/authorization/token/JwtGenerator.java +++ b/oauth2/oauth2-authorization-server/src/main/java/org/springframework/security/oauth2/server/authorization/token/JwtGenerator.java @@ -16,6 +16,7 @@ package org.springframework.security.oauth2.server.authorization.token; +import java.time.Clock; import java.time.Instant; import java.time.temporal.ChronoUnit; import java.util.Collections; @@ -65,6 +66,8 @@ public final class JwtGenerator implements OAuth2TokenGenerator { private OAuth2TokenCustomizer jwtCustomizer; + private Clock clock = Clock.systemUTC(); + /** * Constructs a {@code JwtGenerator} using the provided parameters. * @param jwtEncoder the jwt encoder @@ -95,7 +98,7 @@ public final class JwtGenerator implements OAuth2TokenGenerator { } RegisteredClient registeredClient = context.getRegisteredClient(); - Instant issuedAt = Instant.now(); + Instant issuedAt = this.clock.instant(); Instant expiresAt; JwsAlgorithm jwsAlgorithm = SignatureAlgorithm.RS256; if (OidcParameterNames.ID_TOKEN.equals(context.getTokenType().getValue())) { @@ -208,4 +211,15 @@ public final class JwtGenerator implements OAuth2TokenGenerator { this.jwtCustomizer = jwtCustomizer; } + /** + * Sets the {@link Clock} used when obtaining the current instant via + * {@link Clock#instant()}. + * @param clock the {@link Clock} used when obtaining the current instant via + * {@link Clock#instant()} + */ + public void setClock(Clock clock) { + Assert.notNull(clock, "clock cannot be null"); + this.clock = clock; + } + } diff --git a/oauth2/oauth2-authorization-server/src/main/java/org/springframework/security/oauth2/server/authorization/token/OAuth2AccessTokenGenerator.java b/oauth2/oauth2-authorization-server/src/main/java/org/springframework/security/oauth2/server/authorization/token/OAuth2AccessTokenGenerator.java index 3d03a8bd75..669247019f 100644 --- a/oauth2/oauth2-authorization-server/src/main/java/org/springframework/security/oauth2/server/authorization/token/OAuth2AccessTokenGenerator.java +++ b/oauth2/oauth2-authorization-server/src/main/java/org/springframework/security/oauth2/server/authorization/token/OAuth2AccessTokenGenerator.java @@ -16,6 +16,7 @@ package org.springframework.security.oauth2.server.authorization.token; +import java.time.Clock; import java.time.Instant; import java.util.Base64; import java.util.Collections; @@ -56,6 +57,8 @@ public final class OAuth2AccessTokenGenerator implements OAuth2TokenGenerator accessTokenCustomizer; + private Clock clock = Clock.systemUTC(); + @Nullable @Override public OAuth2AccessToken generate(OAuth2TokenContext context) { @@ -72,7 +75,7 @@ public final class OAuth2AccessTokenGenerator implements OAuth2TokenGenerator claims; diff --git a/oauth2/oauth2-authorization-server/src/main/java/org/springframework/security/oauth2/server/authorization/token/OAuth2RefreshTokenGenerator.java b/oauth2/oauth2-authorization-server/src/main/java/org/springframework/security/oauth2/server/authorization/token/OAuth2RefreshTokenGenerator.java index f9e29634f5..06857c7f56 100644 --- a/oauth2/oauth2-authorization-server/src/main/java/org/springframework/security/oauth2/server/authorization/token/OAuth2RefreshTokenGenerator.java +++ b/oauth2/oauth2-authorization-server/src/main/java/org/springframework/security/oauth2/server/authorization/token/OAuth2RefreshTokenGenerator.java @@ -16,6 +16,7 @@ package org.springframework.security.oauth2.server.authorization.token; +import java.time.Clock; import java.time.Instant; import java.util.Base64; @@ -27,6 +28,7 @@ import org.springframework.security.oauth2.core.ClientAuthenticationMethod; import org.springframework.security.oauth2.core.OAuth2RefreshToken; import org.springframework.security.oauth2.server.authorization.OAuth2TokenType; import org.springframework.security.oauth2.server.authorization.authentication.OAuth2ClientAuthenticationToken; +import org.springframework.util.Assert; /** * An {@link OAuth2TokenGenerator} that generates an {@link OAuth2RefreshToken}. @@ -41,6 +43,8 @@ public final class OAuth2RefreshTokenGenerator implements OAuth2TokenGenerator this.jwtGenerator.setClock(null)) + .withMessage("clock cannot be null"); + } + @Test public void generateWhenUnsupportedTokenTypeThenReturnNull() { // @formatter:off @@ -158,7 +166,10 @@ public class JwtGeneratorTests { .build(); // @formatter:on - assertGeneratedTokenType(tokenContext); + Clock clock = Clock.offset(Clock.systemUTC(), Duration.ofMinutes(5)); + this.jwtGenerator.setClock(clock); + + assertGeneratedTokenType(tokenContext, clock); } @Test @@ -282,6 +293,10 @@ public class JwtGeneratorTests { } private void assertGeneratedTokenType(OAuth2TokenContext tokenContext) { + assertGeneratedTokenType(tokenContext, Clock.systemUTC()); + } + + private void assertGeneratedTokenType(OAuth2TokenContext tokenContext, Clock clock) { this.jwtGenerator.generate(tokenContext); ArgumentCaptor jwtEncodingContextCaptor = ArgumentCaptor.forClass(JwtEncodingContext.class); @@ -318,7 +333,7 @@ public class JwtGeneratorTests { assertThat(jwtClaimsSet.getSubject()).isEqualTo(tokenContext.getAuthorization().getPrincipalName()); assertThat(jwtClaimsSet.getAudience()).containsExactly(tokenContext.getRegisteredClient().getClientId()); - Instant issuedAt = Instant.now(); + Instant issuedAt = clock.instant(); Instant expiresAt; if (tokenContext.getTokenType().equals(OAuth2TokenType.ACCESS_TOKEN)) { expiresAt = issuedAt.plus(tokenContext.getRegisteredClient().getTokenSettings().getAccessTokenTimeToLive()); diff --git a/oauth2/oauth2-authorization-server/src/test/java/org/springframework/security/oauth2/server/authorization/token/OAuth2AccessTokenGeneratorTests.java b/oauth2/oauth2-authorization-server/src/test/java/org/springframework/security/oauth2/server/authorization/token/OAuth2AccessTokenGeneratorTests.java index f7d8d4679f..8d8ee3e8ae 100644 --- a/oauth2/oauth2-authorization-server/src/test/java/org/springframework/security/oauth2/server/authorization/token/OAuth2AccessTokenGeneratorTests.java +++ b/oauth2/oauth2-authorization-server/src/test/java/org/springframework/security/oauth2/server/authorization/token/OAuth2AccessTokenGeneratorTests.java @@ -17,6 +17,8 @@ package org.springframework.security.oauth2.server.authorization.token; import java.security.Principal; +import java.time.Clock; +import java.time.Duration; import java.time.Instant; import java.util.Collections; import java.util.Set; @@ -81,6 +83,13 @@ public class OAuth2AccessTokenGeneratorTests { .withMessage("accessTokenCustomizer cannot be null"); } + @Test + public void setClockWhenNullThenThrowIllegalArgumentException() { + assertThatExceptionOfType(IllegalArgumentException.class) + .isThrownBy(() -> this.accessTokenGenerator.setClock(null)) + .withMessage("clock cannot be null"); + } + @Test public void generateWhenUnsupportedTokenTypeThenReturnNull() { // @formatter:off @@ -150,10 +159,13 @@ public class OAuth2AccessTokenGeneratorTests { .build(); // @formatter:on + Clock clock = Clock.offset(Clock.systemUTC(), Duration.ofMinutes(5)); + this.accessTokenGenerator.setClock(clock); + OAuth2AccessToken accessToken = this.accessTokenGenerator.generate(tokenContext); assertThat(accessToken).isNotNull(); - Instant issuedAt = Instant.now(); + Instant issuedAt = clock.instant(); Instant expiresAt = issuedAt .plus(tokenContext.getRegisteredClient().getTokenSettings().getAccessTokenTimeToLive()); assertThat(accessToken.getIssuedAt()).isBetween(issuedAt.minusSeconds(1), issuedAt.plusSeconds(1)); diff --git a/oauth2/oauth2-authorization-server/src/test/java/org/springframework/security/oauth2/server/authorization/token/OAuth2RefreshTokenGeneratorTests.java b/oauth2/oauth2-authorization-server/src/test/java/org/springframework/security/oauth2/server/authorization/token/OAuth2RefreshTokenGeneratorTests.java index d873f9481b..0436d53c79 100644 --- a/oauth2/oauth2-authorization-server/src/test/java/org/springframework/security/oauth2/server/authorization/token/OAuth2RefreshTokenGeneratorTests.java +++ b/oauth2/oauth2-authorization-server/src/test/java/org/springframework/security/oauth2/server/authorization/token/OAuth2RefreshTokenGeneratorTests.java @@ -16,6 +16,8 @@ package org.springframework.security.oauth2.server.authorization.token; +import java.time.Clock; +import java.time.Duration; import java.time.Instant; import org.junit.jupiter.api.Test; @@ -26,6 +28,7 @@ import org.springframework.security.oauth2.server.authorization.client.Registere import org.springframework.security.oauth2.server.authorization.client.TestRegisteredClients; import static org.assertj.core.api.Assertions.assertThat; +import static org.assertj.core.api.Assertions.assertThatExceptionOfType; /** * Tests for {@link OAuth2RefreshTokenGenerator}. @@ -36,6 +39,12 @@ public class OAuth2RefreshTokenGeneratorTests { private final OAuth2RefreshTokenGenerator tokenGenerator = new OAuth2RefreshTokenGenerator(); + @Test + public void setClockWhenNullThenThrowIllegalArgumentException() { + assertThatExceptionOfType(IllegalArgumentException.class).isThrownBy(() -> this.tokenGenerator.setClock(null)) + .withMessage("clock cannot be null"); + } + @Test public void generateWhenUnsupportedTokenTypeThenReturnNull() { // @formatter:off @@ -58,10 +67,13 @@ public class OAuth2RefreshTokenGeneratorTests { .build(); // @formatter:on + Clock clock = Clock.offset(Clock.systemUTC(), Duration.ofMinutes(5)); + this.tokenGenerator.setClock(clock); + OAuth2RefreshToken refreshToken = this.tokenGenerator.generate(tokenContext); assertThat(refreshToken).isNotNull(); - Instant issuedAt = Instant.now(); + Instant issuedAt = clock.instant(); Instant expiresAt = issuedAt .plus(tokenContext.getRegisteredClient().getTokenSettings().getRefreshTokenTimeToLive()); assertThat(refreshToken.getIssuedAt()).isBetween(issuedAt.minusSeconds(1), issuedAt.plusSeconds(1));