diff --git a/oauth2/oauth2-client/src/main/java/org/springframework/security/oauth2/client/ClientCredentialsOAuth2AuthorizedClientProvider.java b/oauth2/oauth2-client/src/main/java/org/springframework/security/oauth2/client/ClientCredentialsOAuth2AuthorizedClientProvider.java index 36dcb6a484..ef6396435c 100644 --- a/oauth2/oauth2-client/src/main/java/org/springframework/security/oauth2/client/ClientCredentialsOAuth2AuthorizedClientProvider.java +++ b/oauth2/oauth2-client/src/main/java/org/springframework/security/oauth2/client/ClientCredentialsOAuth2AuthorizedClientProvider.java @@ -25,6 +25,7 @@ import org.springframework.security.oauth2.core.AuthorizationGrantType; import org.springframework.security.oauth2.core.endpoint.OAuth2AccessTokenResponse; import org.springframework.util.Assert; +import java.time.Clock; import java.time.Duration; import java.time.Instant; @@ -41,6 +42,7 @@ public final class ClientCredentialsOAuth2AuthorizedClientProvider implements OA private OAuth2AccessTokenResponseClient accessTokenResponseClient = new DefaultClientCredentialsTokenResponseClient(); private Duration clockSkew = Duration.ofSeconds(60); + private Clock clock = Clock.systemUTC(); /** * Attempt to authorize (or re-authorize) the {@link OAuth2AuthorizationContext#getClientRegistration() client} in the provided {@code context}. @@ -84,7 +86,7 @@ public final class ClientCredentialsOAuth2AuthorizedClientProvider implements OA } private boolean hasTokenExpired(AbstractOAuth2Token token) { - return token.getExpiresAt().isBefore(Instant.now().minus(this.clockSkew)); + return token.getExpiresAt().isBefore(Instant.now(this.clock).minus(this.clockSkew)); } /** @@ -100,7 +102,7 @@ public final class ClientCredentialsOAuth2AuthorizedClientProvider implements OA /** * Sets the maximum acceptable clock skew, which is used when checking the * {@link OAuth2AuthorizedClient#getAccessToken() access token} expiry. The default is 60 seconds. - * An access token is considered expired if it's before {@code Instant.now() - clockSkew}. + * An access token is considered expired if it's before {@code Instant.now(this.clock) - clockSkew}. * * @param clockSkew the maximum acceptable clock skew */ @@ -109,4 +111,14 @@ public final class ClientCredentialsOAuth2AuthorizedClientProvider implements OA Assert.isTrue(clockSkew.getSeconds() >= 0, "clockSkew must be >= 0"); this.clockSkew = clockSkew; } + + /** + * Sets the {@link Clock} used in {@link Instant#now(Clock)} when checking the access token expiry. + * + * @param clock the clock + */ + public void setClock(Clock clock) { + Assert.notNull(clock, "clock cannot be null"); + this.clock = clock; + } } diff --git a/oauth2/oauth2-client/src/main/java/org/springframework/security/oauth2/client/ClientCredentialsReactiveOAuth2AuthorizedClientProvider.java b/oauth2/oauth2-client/src/main/java/org/springframework/security/oauth2/client/ClientCredentialsReactiveOAuth2AuthorizedClientProvider.java index 4b31b4269a..c219a0f376 100644 --- a/oauth2/oauth2-client/src/main/java/org/springframework/security/oauth2/client/ClientCredentialsReactiveOAuth2AuthorizedClientProvider.java +++ b/oauth2/oauth2-client/src/main/java/org/springframework/security/oauth2/client/ClientCredentialsReactiveOAuth2AuthorizedClientProvider.java @@ -24,6 +24,7 @@ import org.springframework.security.oauth2.core.AuthorizationGrantType; import org.springframework.util.Assert; import reactor.core.publisher.Mono; +import java.time.Clock; import java.time.Duration; import java.time.Instant; @@ -40,6 +41,7 @@ public final class ClientCredentialsReactiveOAuth2AuthorizedClientProvider imple private ReactiveOAuth2AccessTokenResponseClient accessTokenResponseClient = new WebClientReactiveClientCredentialsTokenResponseClient(); private Duration clockSkew = Duration.ofSeconds(60); + private Clock clock = Clock.systemUTC(); /** * Attempt to authorize (or re-authorize) the {@link OAuth2AuthorizationContext#getClientRegistration() client} in the provided {@code context}. @@ -80,7 +82,7 @@ public final class ClientCredentialsReactiveOAuth2AuthorizedClientProvider imple } private boolean hasTokenExpired(AbstractOAuth2Token token) { - return token.getExpiresAt().isBefore(Instant.now().minus(this.clockSkew)); + return token.getExpiresAt().isBefore(Instant.now(this.clock).minus(this.clockSkew)); } /** @@ -96,7 +98,7 @@ public final class ClientCredentialsReactiveOAuth2AuthorizedClientProvider imple /** * Sets the maximum acceptable clock skew, which is used when checking the * {@link OAuth2AuthorizedClient#getAccessToken() access token} expiry. The default is 60 seconds. - * An access token is considered expired if it's before {@code Instant.now() - clockSkew}. + * An access token is considered expired if it's before {@code Instant.now(this.clock) - clockSkew}. * * @param clockSkew the maximum acceptable clock skew */ @@ -105,4 +107,14 @@ public final class ClientCredentialsReactiveOAuth2AuthorizedClientProvider imple Assert.isTrue(clockSkew.getSeconds() >= 0, "clockSkew must be >= 0"); this.clockSkew = clockSkew; } + + /** + * Sets the {@link Clock} used in {@link Instant#now(Clock)} when checking the access token expiry. + * + * @param clock the clock + */ + public void setClock(Clock clock) { + Assert.notNull(clock, "clock cannot be null"); + this.clock = clock; + } } diff --git a/oauth2/oauth2-client/src/main/java/org/springframework/security/oauth2/client/OAuth2AuthorizedClientProviderBuilder.java b/oauth2/oauth2-client/src/main/java/org/springframework/security/oauth2/client/OAuth2AuthorizedClientProviderBuilder.java index 6405e3bc13..fd67555e94 100644 --- a/oauth2/oauth2-client/src/main/java/org/springframework/security/oauth2/client/OAuth2AuthorizedClientProviderBuilder.java +++ b/oauth2/oauth2-client/src/main/java/org/springframework/security/oauth2/client/OAuth2AuthorizedClientProviderBuilder.java @@ -20,7 +20,9 @@ import org.springframework.security.oauth2.client.endpoint.OAuth2ClientCredentia import org.springframework.security.oauth2.client.endpoint.OAuth2RefreshTokenGrantRequest; import org.springframework.util.Assert; +import java.time.Clock; import java.time.Duration; +import java.time.Instant; import java.util.LinkedHashMap; import java.util.List; import java.util.Map; @@ -128,6 +130,7 @@ public final class OAuth2AuthorizedClientProviderBuilder { public class RefreshTokenGrantBuilder implements Builder { private OAuth2AccessTokenResponseClient accessTokenResponseClient; private Duration clockSkew; + private Clock clock; private RefreshTokenGrantBuilder() { } @@ -145,7 +148,7 @@ public final class OAuth2AuthorizedClientProviderBuilder { /** * Sets the maximum acceptable clock skew, which is used when checking the access token expiry. - * An access token is considered expired if it's before {@code Instant.now() - clockSkew}. + * An access token is considered expired if it's before {@code Instant.now(this.clock) - clockSkew}. * * @param clockSkew the maximum acceptable clock skew * @return the {@link RefreshTokenGrantBuilder} @@ -155,6 +158,17 @@ public final class OAuth2AuthorizedClientProviderBuilder { return this; } + /** + * Sets the {@link Clock} used in {@link Instant#now(Clock)} when checking the access token expiry. + * + * @param clock the clock + * @return the {@link RefreshTokenGrantBuilder} + */ + public RefreshTokenGrantBuilder clock(Clock clock) { + this.clock = clock; + return this; + } + /** * Builds an instance of {@link RefreshTokenOAuth2AuthorizedClientProvider}. * @@ -169,6 +183,9 @@ public final class OAuth2AuthorizedClientProviderBuilder { if (this.clockSkew != null) { authorizedClientProvider.setClockSkew(this.clockSkew); } + if (this.clock != null) { + authorizedClientProvider.setClock(this.clock); + } return authorizedClientProvider; } } @@ -202,6 +219,7 @@ public final class OAuth2AuthorizedClientProviderBuilder { public class ClientCredentialsGrantBuilder implements Builder { private OAuth2AccessTokenResponseClient accessTokenResponseClient; private Duration clockSkew; + private Clock clock; private ClientCredentialsGrantBuilder() { } @@ -219,7 +237,7 @@ public final class OAuth2AuthorizedClientProviderBuilder { /** * Sets the maximum acceptable clock skew, which is used when checking the access token expiry. - * An access token is considered expired if it's before {@code Instant.now() - clockSkew}. + * An access token is considered expired if it's before {@code Instant.now(this.clock) - clockSkew}. * * @param clockSkew the maximum acceptable clock skew * @return the {@link ClientCredentialsGrantBuilder} @@ -229,6 +247,17 @@ public final class OAuth2AuthorizedClientProviderBuilder { return this; } + /** + * Sets the {@link Clock} used in {@link Instant#now(Clock)} when checking the access token expiry. + * + * @param clock the clock + * @return the {@link ClientCredentialsGrantBuilder} + */ + public ClientCredentialsGrantBuilder clock(Clock clock) { + this.clock = clock; + return this; + } + /** * Builds an instance of {@link ClientCredentialsOAuth2AuthorizedClientProvider}. * @@ -243,6 +272,9 @@ public final class OAuth2AuthorizedClientProviderBuilder { if (this.clockSkew != null) { authorizedClientProvider.setClockSkew(this.clockSkew); } + if (this.clock != null) { + authorizedClientProvider.setClock(this.clock); + } return authorizedClientProvider; } } diff --git a/oauth2/oauth2-client/src/main/java/org/springframework/security/oauth2/client/ReactiveOAuth2AuthorizedClientProviderBuilder.java b/oauth2/oauth2-client/src/main/java/org/springframework/security/oauth2/client/ReactiveOAuth2AuthorizedClientProviderBuilder.java index 22b438d42e..a5fe54211f 100644 --- a/oauth2/oauth2-client/src/main/java/org/springframework/security/oauth2/client/ReactiveOAuth2AuthorizedClientProviderBuilder.java +++ b/oauth2/oauth2-client/src/main/java/org/springframework/security/oauth2/client/ReactiveOAuth2AuthorizedClientProviderBuilder.java @@ -20,7 +20,9 @@ import org.springframework.security.oauth2.client.endpoint.OAuth2RefreshTokenGra import org.springframework.security.oauth2.client.endpoint.ReactiveOAuth2AccessTokenResponseClient; import org.springframework.util.Assert; +import java.time.Clock; import java.time.Duration; +import java.time.Instant; import java.util.LinkedHashMap; import java.util.List; import java.util.Map; @@ -128,6 +130,7 @@ public final class ReactiveOAuth2AuthorizedClientProviderBuilder { public class RefreshTokenGrantBuilder implements Builder { private ReactiveOAuth2AccessTokenResponseClient accessTokenResponseClient; private Duration clockSkew; + private Clock clock; private RefreshTokenGrantBuilder() { } @@ -145,7 +148,7 @@ public final class ReactiveOAuth2AuthorizedClientProviderBuilder { /** * Sets the maximum acceptable clock skew, which is used when checking the access token expiry. - * An access token is considered expired if it's before {@code Instant.now() - clockSkew}. + * An access token is considered expired if it's before {@code Instant.now(this.clock) - clockSkew}. * * @param clockSkew the maximum acceptable clock skew * @return the {@link RefreshTokenGrantBuilder} @@ -155,6 +158,17 @@ public final class ReactiveOAuth2AuthorizedClientProviderBuilder { return this; } + /** + * Sets the {@link Clock} used in {@link Instant#now(Clock)} when checking the access token expiry. + * + * @param clock the clock + * @return the {@link RefreshTokenGrantBuilder} + */ + public RefreshTokenGrantBuilder clock(Clock clock) { + this.clock = clock; + return this; + } + /** * Builds an instance of {@link RefreshTokenReactiveOAuth2AuthorizedClientProvider}. * @@ -169,6 +183,9 @@ public final class ReactiveOAuth2AuthorizedClientProviderBuilder { if (this.clockSkew != null) { authorizedClientProvider.setClockSkew(this.clockSkew); } + if (this.clock != null) { + authorizedClientProvider.setClock(this.clock); + } return authorizedClientProvider; } } @@ -202,6 +219,7 @@ public final class ReactiveOAuth2AuthorizedClientProviderBuilder { public class ClientCredentialsGrantBuilder implements Builder { private ReactiveOAuth2AccessTokenResponseClient accessTokenResponseClient; private Duration clockSkew; + private Clock clock; private ClientCredentialsGrantBuilder() { } @@ -219,7 +237,7 @@ public final class ReactiveOAuth2AuthorizedClientProviderBuilder { /** * Sets the maximum acceptable clock skew, which is used when checking the access token expiry. - * An access token is considered expired if it's before {@code Instant.now() - clockSkew}. + * An access token is considered expired if it's before {@code Instant.now(this.clock) - clockSkew}. * * @param clockSkew the maximum acceptable clock skew * @return the {@link ClientCredentialsGrantBuilder} @@ -229,6 +247,17 @@ public final class ReactiveOAuth2AuthorizedClientProviderBuilder { return this; } + /** + * Sets the {@link Clock} used in {@link Instant#now(Clock)} when checking the access token expiry. + * + * @param clock the clock + * @return the {@link ClientCredentialsGrantBuilder} + */ + public ClientCredentialsGrantBuilder clock(Clock clock) { + this.clock = clock; + return this; + } + /** * Builds an instance of {@link ClientCredentialsReactiveOAuth2AuthorizedClientProvider}. * @@ -243,6 +272,9 @@ public final class ReactiveOAuth2AuthorizedClientProviderBuilder { if (this.clockSkew != null) { authorizedClientProvider.setClockSkew(this.clockSkew); } + if (this.clock != null) { + authorizedClientProvider.setClock(this.clock); + } return authorizedClientProvider; } } diff --git a/oauth2/oauth2-client/src/main/java/org/springframework/security/oauth2/client/RefreshTokenOAuth2AuthorizedClientProvider.java b/oauth2/oauth2-client/src/main/java/org/springframework/security/oauth2/client/RefreshTokenOAuth2AuthorizedClientProvider.java index 3604611894..10ba0bfbbe 100644 --- a/oauth2/oauth2-client/src/main/java/org/springframework/security/oauth2/client/RefreshTokenOAuth2AuthorizedClientProvider.java +++ b/oauth2/oauth2-client/src/main/java/org/springframework/security/oauth2/client/RefreshTokenOAuth2AuthorizedClientProvider.java @@ -24,6 +24,7 @@ import org.springframework.security.oauth2.core.AuthorizationGrantType; import org.springframework.security.oauth2.core.endpoint.OAuth2AccessTokenResponse; import org.springframework.util.Assert; +import java.time.Clock; import java.time.Duration; import java.time.Instant; import java.util.Arrays; @@ -44,6 +45,7 @@ public final class RefreshTokenOAuth2AuthorizedClientProvider implements OAuth2A private OAuth2AccessTokenResponseClient accessTokenResponseClient = new DefaultRefreshTokenTokenResponseClient(); private Duration clockSkew = Duration.ofSeconds(60); + private Clock clock = Clock.systemUTC(); /** * Attempt to re-authorize the {@link OAuth2AuthorizationContext#getClientRegistration() client} in the provided {@code context}. @@ -92,7 +94,7 @@ public final class RefreshTokenOAuth2AuthorizedClientProvider implements OAuth2A } private boolean hasTokenExpired(AbstractOAuth2Token token) { - return token.getExpiresAt().isBefore(Instant.now().minus(this.clockSkew)); + return token.getExpiresAt().isBefore(Instant.now(this.clock).minus(this.clockSkew)); } /** @@ -108,7 +110,7 @@ public final class RefreshTokenOAuth2AuthorizedClientProvider implements OAuth2A /** * Sets the maximum acceptable clock skew, which is used when checking the * {@link OAuth2AuthorizedClient#getAccessToken() access token} expiry. The default is 60 seconds. - * An access token is considered expired if it's before {@code Instant.now() - clockSkew}. + * An access token is considered expired if it's before {@code Instant.now(this.clock) - clockSkew}. * * @param clockSkew the maximum acceptable clock skew */ @@ -117,4 +119,14 @@ public final class RefreshTokenOAuth2AuthorizedClientProvider implements OAuth2A Assert.isTrue(clockSkew.getSeconds() >= 0, "clockSkew must be >= 0"); this.clockSkew = clockSkew; } + + /** + * Sets the {@link Clock} used in {@link Instant#now(Clock)} when checking the access token expiry. + * + * @param clock the clock + */ + public void setClock(Clock clock) { + Assert.notNull(clock, "clock cannot be null"); + this.clock = clock; + } } diff --git a/oauth2/oauth2-client/src/main/java/org/springframework/security/oauth2/client/RefreshTokenReactiveOAuth2AuthorizedClientProvider.java b/oauth2/oauth2-client/src/main/java/org/springframework/security/oauth2/client/RefreshTokenReactiveOAuth2AuthorizedClientProvider.java index 000b03c98a..d8abe492aa 100644 --- a/oauth2/oauth2-client/src/main/java/org/springframework/security/oauth2/client/RefreshTokenReactiveOAuth2AuthorizedClientProvider.java +++ b/oauth2/oauth2-client/src/main/java/org/springframework/security/oauth2/client/RefreshTokenReactiveOAuth2AuthorizedClientProvider.java @@ -24,6 +24,7 @@ import org.springframework.security.oauth2.core.AuthorizationGrantType; import org.springframework.util.Assert; import reactor.core.publisher.Mono; +import java.time.Clock; import java.time.Duration; import java.time.Instant; import java.util.Arrays; @@ -44,6 +45,7 @@ public final class RefreshTokenReactiveOAuth2AuthorizedClientProvider implements private ReactiveOAuth2AccessTokenResponseClient accessTokenResponseClient = new WebClientReactiveRefreshTokenTokenResponseClient(); private Duration clockSkew = Duration.ofSeconds(60); + private Clock clock = Clock.systemUTC(); /** * Attempt to re-authorize the {@link OAuth2AuthorizationContext#getClientRegistration() client} in the provided {@code context}. @@ -91,7 +93,7 @@ public final class RefreshTokenReactiveOAuth2AuthorizedClientProvider implements } private boolean hasTokenExpired(AbstractOAuth2Token token) { - return token.getExpiresAt().isBefore(Instant.now().minus(this.clockSkew)); + return token.getExpiresAt().isBefore(Instant.now(this.clock).minus(this.clockSkew)); } /** @@ -107,7 +109,7 @@ public final class RefreshTokenReactiveOAuth2AuthorizedClientProvider implements /** * Sets the maximum acceptable clock skew, which is used when checking the * {@link OAuth2AuthorizedClient#getAccessToken() access token} expiry. The default is 60 seconds. - * An access token is considered expired if it's before {@code Instant.now() - clockSkew}. + * An access token is considered expired if it's before {@code Instant.now(this.clock) - clockSkew}. * * @param clockSkew the maximum acceptable clock skew */ @@ -116,4 +118,14 @@ public final class RefreshTokenReactiveOAuth2AuthorizedClientProvider implements Assert.isTrue(clockSkew.getSeconds() >= 0, "clockSkew must be >= 0"); this.clockSkew = clockSkew; } + + /** + * Sets the {@link Clock} used in {@link Instant#now(Clock)} when checking the access token expiry. + * + * @param clock the clock + */ + public void setClock(Clock clock) { + Assert.notNull(clock, "clock cannot be null"); + this.clock = clock; + } } diff --git a/oauth2/oauth2-client/src/test/java/org/springframework/security/oauth2/client/ClientCredentialsOAuth2AuthorizedClientProviderTests.java b/oauth2/oauth2-client/src/test/java/org/springframework/security/oauth2/client/ClientCredentialsOAuth2AuthorizedClientProviderTests.java index 10acb7cdac..84924fd59b 100644 --- a/oauth2/oauth2-client/src/test/java/org/springframework/security/oauth2/client/ClientCredentialsOAuth2AuthorizedClientProviderTests.java +++ b/oauth2/oauth2-client/src/test/java/org/springframework/security/oauth2/client/ClientCredentialsOAuth2AuthorizedClientProviderTests.java @@ -78,6 +78,13 @@ public class ClientCredentialsOAuth2AuthorizedClientProviderTests { .hasMessage("clockSkew must be >= 0"); } + @Test + public void setClockWhenNullThenThrowIllegalArgumentException() { + assertThatThrownBy(() -> this.authorizedClientProvider.setClock(null)) + .isInstanceOf(IllegalArgumentException.class) + .hasMessage("clock cannot be null"); + } + @Test public void authorizeWhenContextIsNullThenThrowIllegalArgumentException() { assertThatThrownBy(() -> this.authorizedClientProvider.authorize(null)) diff --git a/oauth2/oauth2-client/src/test/java/org/springframework/security/oauth2/client/ClientCredentialsReactiveOAuth2AuthorizedClientProviderTests.java b/oauth2/oauth2-client/src/test/java/org/springframework/security/oauth2/client/ClientCredentialsReactiveOAuth2AuthorizedClientProviderTests.java index f7a4fe1295..9426337877 100644 --- a/oauth2/oauth2-client/src/test/java/org/springframework/security/oauth2/client/ClientCredentialsReactiveOAuth2AuthorizedClientProviderTests.java +++ b/oauth2/oauth2-client/src/test/java/org/springframework/security/oauth2/client/ClientCredentialsReactiveOAuth2AuthorizedClientProviderTests.java @@ -79,6 +79,13 @@ public class ClientCredentialsReactiveOAuth2AuthorizedClientProviderTests { .hasMessage("clockSkew must be >= 0"); } + @Test + public void setClockWhenNullThenThrowIllegalArgumentException() { + assertThatThrownBy(() -> this.authorizedClientProvider.setClock(null)) + .isInstanceOf(IllegalArgumentException.class) + .hasMessage("clock cannot be null"); + } + @Test public void authorizeWhenContextIsNullThenThrowIllegalArgumentException() { assertThatThrownBy(() -> this.authorizedClientProvider.authorize(null).block()) diff --git a/oauth2/oauth2-client/src/test/java/org/springframework/security/oauth2/client/RefreshTokenOAuth2AuthorizedClientProviderTests.java b/oauth2/oauth2-client/src/test/java/org/springframework/security/oauth2/client/RefreshTokenOAuth2AuthorizedClientProviderTests.java index 5482c3bc26..72cc9c16d1 100644 --- a/oauth2/oauth2-client/src/test/java/org/springframework/security/oauth2/client/RefreshTokenOAuth2AuthorizedClientProviderTests.java +++ b/oauth2/oauth2-client/src/test/java/org/springframework/security/oauth2/client/RefreshTokenOAuth2AuthorizedClientProviderTests.java @@ -87,6 +87,13 @@ public class RefreshTokenOAuth2AuthorizedClientProviderTests { .hasMessage("clockSkew must be >= 0"); } + @Test + public void setClockWhenNullThenThrowIllegalArgumentException() { + assertThatThrownBy(() -> this.authorizedClientProvider.setClock(null)) + .isInstanceOf(IllegalArgumentException.class) + .hasMessage("clock cannot be null"); + } + @Test public void authorizeWhenContextIsNullThenThrowIllegalArgumentException() { assertThatThrownBy(() -> this.authorizedClientProvider.authorize(null)) diff --git a/oauth2/oauth2-client/src/test/java/org/springframework/security/oauth2/client/RefreshTokenReactiveOAuth2AuthorizedClientProviderTests.java b/oauth2/oauth2-client/src/test/java/org/springframework/security/oauth2/client/RefreshTokenReactiveOAuth2AuthorizedClientProviderTests.java index 97bc688a5d..f2c0d459fd 100644 --- a/oauth2/oauth2-client/src/test/java/org/springframework/security/oauth2/client/RefreshTokenReactiveOAuth2AuthorizedClientProviderTests.java +++ b/oauth2/oauth2-client/src/test/java/org/springframework/security/oauth2/client/RefreshTokenReactiveOAuth2AuthorizedClientProviderTests.java @@ -89,6 +89,13 @@ public class RefreshTokenReactiveOAuth2AuthorizedClientProviderTests { .hasMessage("clockSkew must be >= 0"); } + @Test + public void setClockWhenNullThenThrowIllegalArgumentException() { + assertThatThrownBy(() -> this.authorizedClientProvider.setClock(null)) + .isInstanceOf(IllegalArgumentException.class) + .hasMessage("clock cannot be null"); + } + @Test public void authorizeWhenContextIsNullThenThrowIllegalArgumentException() { assertThatThrownBy(() -> this.authorizedClientProvider.authorize(null).block())