Provide configuration for refresh token generator

Closes gh-377
This commit is contained in:
Joe Grandja
2021-07-28 05:15:32 -04:00
parent 06ad211fce
commit 3ea7d8c9b6
4 changed files with 122 additions and 5 deletions

View File

@@ -16,15 +16,21 @@
package org.springframework.security.oauth2.server.authorization.authentication;
import java.security.Principal;
import java.time.Duration;
import java.time.Instant;
import java.util.Base64;
import java.util.Collections;
import java.util.HashMap;
import java.util.Map;
import java.util.Set;
import java.util.function.Supplier;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.security.authentication.AuthenticationProvider;
import org.springframework.security.core.Authentication;
import org.springframework.security.core.AuthenticationException;
import org.springframework.security.crypto.keygen.Base64StringKeyGenerator;
import org.springframework.security.crypto.keygen.StringKeyGenerator;
import org.springframework.security.oauth2.core.AuthorizationGrantType;
import org.springframework.security.oauth2.core.OAuth2AccessToken;
import org.springframework.security.oauth2.core.OAuth2AuthenticationException;
@@ -74,9 +80,12 @@ public final class OAuth2AuthorizationCodeAuthenticationProvider implements Auth
new OAuth2TokenType(OAuth2ParameterNames.CODE);
private static final OAuth2TokenType ID_TOKEN_TOKEN_TYPE =
new OAuth2TokenType(OidcParameterNames.ID_TOKEN);
private static final StringKeyGenerator DEFAULT_REFRESH_TOKEN_GENERATOR =
new Base64StringKeyGenerator(Base64.getUrlEncoder().withoutPadding(), 96);
private final OAuth2AuthorizationService authorizationService;
private final JwtEncoder jwtEncoder;
private OAuth2TokenCustomizer<JwtEncodingContext> jwtCustomizer = (context) -> {};
private Supplier<String> refreshTokenGenerator = DEFAULT_REFRESH_TOKEN_GENERATOR::generateKey;
private ProviderSettings providerSettings;
/**
@@ -97,6 +106,16 @@ public final class OAuth2AuthorizationCodeAuthenticationProvider implements Auth
this.jwtCustomizer = jwtCustomizer;
}
/**
* Sets the {@code Supplier<String>} that generates the value for the {@link OAuth2RefreshToken}.
*
* @param refreshTokenGenerator the {@code Supplier<String>} that generates the value for the {@link OAuth2RefreshToken}
*/
public void setRefreshTokenGenerator(Supplier<String> refreshTokenGenerator) {
Assert.notNull(refreshTokenGenerator, "refreshTokenGenerator cannot be null");
this.refreshTokenGenerator = refreshTokenGenerator;
}
@Autowired(required = false)
protected void setProviderSettings(ProviderSettings providerSettings) {
this.providerSettings = providerSettings;
@@ -173,8 +192,7 @@ public final class OAuth2AuthorizationCodeAuthenticationProvider implements Auth
OAuth2RefreshToken refreshToken = null;
if (registeredClient.getAuthorizationGrantTypes().contains(AuthorizationGrantType.REFRESH_TOKEN)) {
refreshToken = OAuth2RefreshTokenAuthenticationProvider.generateRefreshToken(
registeredClient.getTokenSettings().getRefreshTokenTimeToLive());
refreshToken = generateRefreshToken(registeredClient.getTokenSettings().getRefreshTokenTimeToLive());
}
Jwt jwtIdToken = null;
@@ -250,4 +268,10 @@ public final class OAuth2AuthorizationCodeAuthenticationProvider implements Auth
return OAuth2AuthorizationCodeAuthenticationToken.class.isAssignableFrom(authentication);
}
private OAuth2RefreshToken generateRefreshToken(Duration tokenTimeToLive) {
Instant issuedAt = Instant.now();
Instant expiresAt = issuedAt.plus(tokenTimeToLive);
return new OAuth2RefreshToken(this.refreshTokenGenerator.get(), issuedAt, expiresAt);
}
}

View File

@@ -23,6 +23,7 @@ import java.util.Collections;
import java.util.HashMap;
import java.util.Map;
import java.util.Set;
import java.util.function.Supplier;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.security.authentication.AuthenticationProvider;
@@ -73,10 +74,12 @@ import static org.springframework.security.oauth2.server.authorization.authentic
*/
public final class OAuth2RefreshTokenAuthenticationProvider implements AuthenticationProvider {
private static final OAuth2TokenType ID_TOKEN_TOKEN_TYPE = new OAuth2TokenType(OidcParameterNames.ID_TOKEN);
private static final StringKeyGenerator TOKEN_GENERATOR = new Base64StringKeyGenerator(Base64.getUrlEncoder().withoutPadding(), 96);
private static final StringKeyGenerator DEFAULT_REFRESH_TOKEN_GENERATOR =
new Base64StringKeyGenerator(Base64.getUrlEncoder().withoutPadding(), 96);
private final OAuth2AuthorizationService authorizationService;
private final JwtEncoder jwtEncoder;
private OAuth2TokenCustomizer<JwtEncodingContext> jwtCustomizer = (context) -> {};
private Supplier<String> refreshTokenGenerator = DEFAULT_REFRESH_TOKEN_GENERATOR::generateKey;
private ProviderSettings providerSettings;
/**
@@ -98,6 +101,16 @@ public final class OAuth2RefreshTokenAuthenticationProvider implements Authentic
this.jwtCustomizer = jwtCustomizer;
}
/**
* Sets the {@code Supplier<String>} that generates the value for the {@link OAuth2RefreshToken}.
*
* @param refreshTokenGenerator the {@code Supplier<String>} that generates the value for the {@link OAuth2RefreshToken}
*/
public void setRefreshTokenGenerator(Supplier<String> refreshTokenGenerator) {
Assert.notNull(refreshTokenGenerator, "refreshTokenGenerator cannot be null");
this.refreshTokenGenerator = refreshTokenGenerator;
}
@Autowired(required = false)
protected void setProviderSettings(ProviderSettings providerSettings) {
this.providerSettings = providerSettings;
@@ -246,9 +259,9 @@ public final class OAuth2RefreshTokenAuthenticationProvider implements Authentic
return OAuth2RefreshTokenAuthenticationToken.class.isAssignableFrom(authentication);
}
static OAuth2RefreshToken generateRefreshToken(Duration tokenTimeToLive) {
private OAuth2RefreshToken generateRefreshToken(Duration tokenTimeToLive) {
Instant issuedAt = Instant.now();
Instant expiresAt = issuedAt.plus(tokenTimeToLive);
return new OAuth2RefreshToken(TOKEN_GENERATOR.generateKey(), issuedAt, expiresAt);
return new OAuth2RefreshToken(this.refreshTokenGenerator.get(), issuedAt, expiresAt);
}
}

View File

@@ -23,6 +23,7 @@ import java.util.HashMap;
import java.util.HashSet;
import java.util.Map;
import java.util.Set;
import java.util.function.Supplier;
import org.junit.Before;
import org.junit.Test;
@@ -61,6 +62,7 @@ import static org.assertj.core.api.Assertions.entry;
import static org.mockito.ArgumentMatchers.any;
import static org.mockito.ArgumentMatchers.eq;
import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.spy;
import static org.mockito.Mockito.times;
import static org.mockito.Mockito.verify;
import static org.mockito.Mockito.when;
@@ -110,6 +112,13 @@ public class OAuth2AuthorizationCodeAuthenticationProviderTests {
.hasMessage("jwtCustomizer cannot be null");
}
@Test
public void setRefreshTokenGeneratorWhenNullThenThrowIllegalArgumentException() {
assertThatThrownBy(() -> this.authenticationProvider.setRefreshTokenGenerator(null))
.isInstanceOf(IllegalArgumentException.class)
.hasMessage("refreshTokenGenerator cannot be null");
}
@Test
public void supportsWhenTypeOAuth2AuthorizationCodeAuthenticationTokenThenReturnTrue() {
assertThat(this.authenticationProvider.supports(OAuth2AuthorizationCodeAuthenticationToken.class)).isTrue();
@@ -440,6 +449,37 @@ public class OAuth2AuthorizationCodeAuthenticationProviderTests {
assertThat(accessTokenAuthentication.getRefreshToken()).isNull();
}
@Test
public void authenticateWhenCustomRefreshTokenGeneratorThenUsed() {
RegisteredClient registeredClient = TestRegisteredClients.registeredClient().build();
OAuth2Authorization authorization = TestOAuth2Authorizations.authorization(registeredClient).build();
when(this.authorizationService.findByToken(eq(AUTHORIZATION_CODE), eq(AUTHORIZATION_CODE_TOKEN_TYPE)))
.thenReturn(authorization);
when(this.jwtEncoder.encode(any(), any())).thenReturn(createJwt());
@SuppressWarnings("unchecked")
Supplier<String> refreshTokenGenerator = spy(new Supplier<String>() {
@Override
public String get() {
return "custom-refresh-token";
}
});
this.authenticationProvider.setRefreshTokenGenerator(refreshTokenGenerator);
OAuth2ClientAuthenticationToken clientPrincipal = new OAuth2ClientAuthenticationToken(registeredClient);
OAuth2AuthorizationRequest authorizationRequest = authorization.getAttribute(
OAuth2AuthorizationRequest.class.getName());
OAuth2AuthorizationCodeAuthenticationToken authentication =
new OAuth2AuthorizationCodeAuthenticationToken(AUTHORIZATION_CODE, clientPrincipal, authorizationRequest.getRedirectUri(), null);
OAuth2AccessTokenAuthenticationToken accessTokenAuthentication =
(OAuth2AccessTokenAuthenticationToken) this.authenticationProvider.authenticate(authentication);
verify(refreshTokenGenerator).get();
assertThat(accessTokenAuthentication.getRefreshToken().getTokenValue()).isEqualTo(refreshTokenGenerator.get());
}
private static Jwt createJwt() {
Instant issuedAt = Instant.now();
Instant expiresAt = issuedAt.plus(1, ChronoUnit.HOURS);

View File

@@ -23,6 +23,7 @@ import java.util.HashMap;
import java.util.HashSet;
import java.util.Map;
import java.util.Set;
import java.util.function.Supplier;
import org.junit.Before;
import org.junit.Test;
@@ -59,6 +60,7 @@ import static org.assertj.core.api.AssertionsForInterfaceTypes.assertThat;
import static org.mockito.ArgumentMatchers.any;
import static org.mockito.ArgumentMatchers.eq;
import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.spy;
import static org.mockito.Mockito.times;
import static org.mockito.Mockito.verify;
import static org.mockito.Mockito.when;
@@ -111,6 +113,13 @@ public class OAuth2RefreshTokenAuthenticationProviderTests {
.hasMessage("jwtCustomizer cannot be null");
}
@Test
public void setRefreshTokenGeneratorWhenNullThenThrowIllegalArgumentException() {
assertThatThrownBy(() -> this.authenticationProvider.setRefreshTokenGenerator(null))
.isInstanceOf(IllegalArgumentException.class)
.hasMessage("refreshTokenGenerator cannot be null");
}
@Test
public void supportsWhenSupportedAuthenticationThenTrue() {
assertThat(this.authenticationProvider.supports(OAuth2RefreshTokenAuthenticationToken.class)).isTrue();
@@ -281,6 +290,37 @@ public class OAuth2RefreshTokenAuthenticationProviderTests {
assertThat(accessTokenAuthentication.getAccessToken().getScopes()).isEqualTo(requestedScopes);
}
@Test
public void authenticateWhenCustomRefreshTokenGeneratorThenUsed() {
RegisteredClient registeredClient = TestRegisteredClients.registeredClient()
.tokenSettings(TokenSettings.builder().reuseRefreshTokens(false).build())
.build();
OAuth2Authorization authorization = TestOAuth2Authorizations.authorization(registeredClient).build();
when(this.authorizationService.findByToken(
eq(authorization.getRefreshToken().getToken().getTokenValue()),
eq(OAuth2TokenType.REFRESH_TOKEN)))
.thenReturn(authorization);
@SuppressWarnings("unchecked")
Supplier<String> refreshTokenGenerator = spy(new Supplier<String>() {
@Override
public String get() {
return "custom-refresh-token";
}
});
this.authenticationProvider.setRefreshTokenGenerator(refreshTokenGenerator);
OAuth2ClientAuthenticationToken clientPrincipal = new OAuth2ClientAuthenticationToken(registeredClient);
OAuth2RefreshTokenAuthenticationToken authentication = new OAuth2RefreshTokenAuthenticationToken(
authorization.getRefreshToken().getToken().getTokenValue(), clientPrincipal, null, null);
OAuth2AccessTokenAuthenticationToken accessTokenAuthentication =
(OAuth2AccessTokenAuthenticationToken) this.authenticationProvider.authenticate(authentication);
verify(refreshTokenGenerator).get();
assertThat(accessTokenAuthentication.getRefreshToken().getTokenValue()).isEqualTo(refreshTokenGenerator.get());
}
@Test
public void authenticateWhenRequestedScopesNotAuthorizedThenThrowOAuth2AuthenticationException() {
RegisteredClient registeredClient = TestRegisteredClients.registeredClient().build();