Provide configurable Clock in OAuth2AuthorizedClientProvider impls

Fixes gh-7114
This commit is contained in:
Joe Grandja 2019-08-23 16:10:11 -04:00
parent 052256db0a
commit bc38a4a3cc
10 changed files with 152 additions and 12 deletions

View File

@ -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<OAuth2ClientCredentialsGrantRequest> 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;
}
}

View File

@ -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<OAuth2ClientCredentialsGrantRequest> 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;
}
}

View File

@ -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<OAuth2RefreshTokenGrantRequest> 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<OAuth2ClientCredentialsGrantRequest> 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;
}
}

View File

@ -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<OAuth2RefreshTokenGrantRequest> 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<OAuth2ClientCredentialsGrantRequest> 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;
}
}

View File

@ -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<OAuth2RefreshTokenGrantRequest> 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;
}
}

View File

@ -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<OAuth2RefreshTokenGrantRequest> 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;
}
}

View File

@ -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))

View File

@ -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())

View File

@ -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))

View File

@ -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())