Polish gh-128

This commit is contained in:
Joe Grandja 2020-10-29 20:45:11 -04:00
parent 78d4bd0bad
commit a2167a5091
14 changed files with 176 additions and 165 deletions

View File

@ -144,16 +144,18 @@ public final class OAuth2AuthorizationServerConfigurer<B extends HttpSecurityBui
jwtEncoder);
builder.authenticationProvider(postProcess(authorizationCodeAuthenticationProvider));
OAuth2RefreshTokenAuthenticationProvider refreshTokenAuthenticationProvider =
new OAuth2RefreshTokenAuthenticationProvider(
getAuthorizationService(builder),
jwtEncoder);
builder.authenticationProvider(postProcess(refreshTokenAuthenticationProvider));
OAuth2ClientCredentialsAuthenticationProvider clientCredentialsAuthenticationProvider =
new OAuth2ClientCredentialsAuthenticationProvider(
getAuthorizationService(builder),
jwtEncoder);
builder.authenticationProvider(postProcess(clientCredentialsAuthenticationProvider));
OAuth2RefreshTokenAuthenticationProvider refreshTokenAuthenticationProvider =
new OAuth2RefreshTokenAuthenticationProvider(getAuthorizationService(builder), jwtEncoder);
builder.authenticationProvider(postProcess(refreshTokenAuthenticationProvider));
OAuth2TokenRevocationAuthenticationProvider tokenRevocationAuthenticationProvider =
new OAuth2TokenRevocationAuthenticationProvider(
getAuthorizationService(builder));

View File

@ -66,16 +66,12 @@ public final class InMemoryOAuth2AuthorizationService implements OAuth2Authoriza
} else if (TokenType.AUTHORIZATION_CODE.equals(tokenType)) {
OAuth2AuthorizationCode authorizationCode = authorization.getTokens().getToken(OAuth2AuthorizationCode.class);
return authorizationCode != null && authorizationCode.getTokenValue().equals(token);
}
if (TokenType.ACCESS_TOKEN.equals(tokenType)) {
} else if (TokenType.ACCESS_TOKEN.equals(tokenType)) {
return authorization.getTokens().getAccessToken() != null &&
authorization.getTokens().getAccessToken().getTokenValue().equals(token);
}
if (TokenType.REFRESH_TOKEN.equals(tokenType)) {
} else if (TokenType.REFRESH_TOKEN.equals(tokenType)) {
return authorization.getTokens().getRefreshToken() != null &&
authorization.getTokens().getRefreshToken().getTokenValue().equals(token);
authorization.getTokens().getRefreshToken().getTokenValue().equals(token);
}
return false;

View File

@ -18,24 +18,25 @@ package org.springframework.security.oauth2.server.authorization.authentication;
import org.springframework.lang.Nullable;
import org.springframework.security.authentication.AbstractAuthenticationToken;
import org.springframework.security.core.Authentication;
import org.springframework.security.oauth2.server.authorization.Version;
import org.springframework.security.oauth2.core.OAuth2AccessToken;
import org.springframework.security.oauth2.core.OAuth2RefreshToken;
import org.springframework.security.oauth2.server.authorization.Version;
import org.springframework.security.oauth2.server.authorization.client.RegisteredClient;
import org.springframework.util.Assert;
import java.util.Collections;
/**
* An {@link Authentication} implementation used when issuing an OAuth 2.0 Access Token.
* An {@link Authentication} implementation used when issuing an
* OAuth 2.0 Access Token and (optional) Refresh Token.
*
* @author Joe Grandja
* @author Madhu Bhat
* @since 0.0.1
* @see AbstractAuthenticationToken
* @see OAuth2AuthorizationCodeAuthenticationProvider
* @see RegisteredClient
* @see OAuth2AccessToken
* @see OAuth2RefreshToken
* @see OAuth2ClientAuthenticationToken
*/
public class OAuth2AccessTokenAuthenticationToken extends AbstractAuthenticationToken {
@ -65,8 +66,8 @@ public class OAuth2AccessTokenAuthenticationToken extends AbstractAuthentication
* @param accessToken the access token
* @param refreshToken the refresh token
*/
public OAuth2AccessTokenAuthenticationToken(RegisteredClient registeredClient,
Authentication clientPrincipal, OAuth2AccessToken accessToken, @Nullable OAuth2RefreshToken refreshToken) {
public OAuth2AccessTokenAuthenticationToken(RegisteredClient registeredClient, Authentication clientPrincipal,
OAuth2AccessToken accessToken, @Nullable OAuth2RefreshToken refreshToken) {
super(Collections.emptyList());
Assert.notNull(registeredClient, "registeredClient cannot be null");
Assert.notNull(clientPrincipal, "clientPrincipal cannot be null");
@ -105,14 +106,13 @@ public class OAuth2AccessTokenAuthenticationToken extends AbstractAuthentication
return this.accessToken;
}
/**
* Returns the {@link OAuth2RefreshToken} if provided
* Returns the {@link OAuth2RefreshToken refresh token}.
*
* @return the {@link OAuth2RefreshToken}
* @return the {@link OAuth2RefreshToken} or {@code null} if not available
*/
@Nullable
public OAuth2RefreshToken getRefreshToken() {
return refreshToken;
return this.refreshToken;
}
}

View File

@ -97,9 +97,9 @@ public class OAuth2ClientCredentialsAuthenticationProvider implements Authentica
jwt.getTokenValue(), jwt.getIssuedAt(), jwt.getExpiresAt(), scopes);
OAuth2Authorization authorization = OAuth2Authorization.withRegisteredClient(registeredClient)
.attribute(OAuth2AuthorizationAttributeNames.ACCESS_TOKEN_ATTRIBUTES, jwt)
.principalName(clientPrincipal.getName())
.tokens(OAuth2Tokens.builder().accessToken(accessToken).build())
.attribute(OAuth2AuthorizationAttributeNames.ACCESS_TOKEN_ATTRIBUTES, jwt)
.build();
this.authorizationService.save(authorization);

View File

@ -13,12 +13,8 @@
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.springframework.security.oauth2.server.authorization.authentication;
import java.time.Instant;
import java.util.Set;
import org.springframework.security.authentication.AuthenticationProvider;
import org.springframework.security.core.Authentication;
import org.springframework.security.core.AuthenticationException;
@ -38,6 +34,9 @@ import org.springframework.security.oauth2.server.authorization.config.TokenSett
import org.springframework.security.oauth2.server.authorization.token.OAuth2Tokens;
import org.springframework.util.Assert;
import java.time.Instant;
import java.util.Set;
/**
* An {@link AuthenticationProvider} implementation for the OAuth 2.0 Refresh Token Grant.
*
@ -47,19 +46,23 @@ import org.springframework.util.Assert;
* @see OAuth2AccessTokenAuthenticationToken
* @see OAuth2AuthorizationService
* @see JwtEncoder
* @see <a target="_blank" href="https://tools.ietf.org/html/rfc6749#section-1.5">Section 1.5 Refresh Token</a>
* @see <a target="_blank" href="https://tools.ietf.org/html/rfc6749#section-1.5">Section 1.5 Refresh Token Grant</a>
* @see <a target="_blank" href="https://tools.ietf.org/html/rfc6749#section-6">Section 6 Refreshing an Access Token</a>
*/
public class OAuth2RefreshTokenAuthenticationProvider implements AuthenticationProvider {
private final OAuth2AuthorizationService authorizationService;
private final JwtEncoder jwtEncoder;
public OAuth2RefreshTokenAuthenticationProvider(OAuth2AuthorizationService authorizationService, JwtEncoder jwtEncoder) {
/**
* Constructs an {@code OAuth2RefreshTokenAuthenticationProvider} using the provided parameters.
*
* @param authorizationService the authorization service
* @param jwtEncoder the jwt encoder
*/
public OAuth2RefreshTokenAuthenticationProvider(OAuth2AuthorizationService authorizationService,
JwtEncoder jwtEncoder) {
Assert.notNull(authorizationService, "authorizationService cannot be null");
Assert.notNull(jwtEncoder, "jwtEncoder cannot be null");
this.authorizationService = authorizationService;
this.jwtEncoder = jwtEncoder;
}
@ -73,43 +76,45 @@ public class OAuth2RefreshTokenAuthenticationProvider implements AuthenticationP
if (OAuth2ClientAuthenticationToken.class.isAssignableFrom(refreshTokenAuthentication.getPrincipal().getClass())) {
clientPrincipal = (OAuth2ClientAuthenticationToken) refreshTokenAuthentication.getPrincipal();
}
if (clientPrincipal == null || !clientPrincipal.isAuthenticated()) {
throw new OAuth2AuthenticationException(new OAuth2Error(OAuth2ErrorCodes.INVALID_CLIENT));
}
RegisteredClient registeredClient = clientPrincipal.getRegisteredClient();
OAuth2Authorization authorization = this.authorizationService.findByToken(refreshTokenAuthentication.getRefreshToken(), TokenType.REFRESH_TOKEN);
OAuth2Authorization authorization = this.authorizationService.findByToken(
refreshTokenAuthentication.getRefreshToken(), TokenType.REFRESH_TOKEN);
if (authorization == null) {
throw new OAuth2AuthenticationException(new OAuth2Error(OAuth2ErrorCodes.INVALID_GRANT));
}
Instant refreshTokenExpiration = authorization.getTokens().getRefreshToken().getExpiresAt();
if (refreshTokenExpiration.isBefore(Instant.now())) {
// as per https://tools.ietf.org/html/rfc6749#section-5.2
// invalid_grant: The provided authorization grant (e.g., authorization
// code, resource owner credentials) or refresh token is invalid, expired, revoked [...].
if (!registeredClient.getId().equals(authorization.getRegisteredClientId())) {
throw new OAuth2AuthenticationException(new OAuth2Error(OAuth2ErrorCodes.INVALID_CLIENT));
}
Instant refreshTokenExpiresAt = authorization.getTokens().getRefreshToken().getExpiresAt();
if (refreshTokenExpiresAt.isBefore(Instant.now())) {
// As per https://tools.ietf.org/html/rfc6749#section-5.2
// invalid_grant: The provided authorization grant (e.g., authorization code,
// resource owner credentials) or refresh token is invalid, expired, revoked [...].
throw new OAuth2AuthenticationException(new OAuth2Error(OAuth2ErrorCodes.INVALID_GRANT));
}
RegisteredClient registeredClient = clientPrincipal.getRegisteredClient();
// https://tools.ietf.org/html/rfc6749#section-6
// As per https://tools.ietf.org/html/rfc6749#section-6
// The requested scope MUST NOT include any scope not originally granted by the resource owner,
// and if omitted is treated as equal to the scope originally granted by the resource owner.
Set<String> refreshTokenScopes = refreshTokenAuthentication.getScopes();
Set<String> scopes = refreshTokenAuthentication.getScopes();
Set<String> authorizedScopes = authorization.getAttribute(OAuth2AuthorizationAttributeNames.AUTHORIZED_SCOPES);
if (!authorizedScopes.containsAll(refreshTokenScopes)) {
if (!authorizedScopes.containsAll(scopes)) {
throw new OAuth2AuthenticationException(new OAuth2Error(OAuth2ErrorCodes.INVALID_SCOPE));
}
if (refreshTokenScopes.isEmpty()) {
refreshTokenScopes = authorizedScopes;
if (scopes.isEmpty()) {
scopes = authorizedScopes;
}
Jwt jwt = OAuth2TokenIssuerUtil
.issueJwtAccessToken(this.jwtEncoder, authorization.getPrincipalName(), registeredClient.getClientId(), refreshTokenScopes);
.issueJwtAccessToken(this.jwtEncoder, authorization.getPrincipalName(), registeredClient.getClientId(), scopes);
OAuth2AccessToken accessToken = new OAuth2AccessToken(OAuth2AccessToken.TokenType.BEARER,
jwt.getTokenValue(), jwt.getIssuedAt(), jwt.getExpiresAt(), refreshTokenScopes);
jwt.getTokenValue(), jwt.getIssuedAt(), jwt.getExpiresAt(), scopes);
TokenSettings tokenSettings = registeredClient.getTokenSettings();
OAuth2RefreshToken refreshToken;
@ -120,13 +125,13 @@ public class OAuth2RefreshTokenAuthenticationProvider implements AuthenticationP
}
authorization = OAuth2Authorization.from(authorization)
.attribute(OAuth2AuthorizationAttributeNames.ACCESS_TOKEN_ATTRIBUTES, jwt)
.tokens(OAuth2Tokens.builder().accessToken(accessToken).refreshToken(refreshToken).build())
.build();
.tokens(OAuth2Tokens.builder().accessToken(accessToken).refreshToken(refreshToken).build())
.attribute(OAuth2AuthorizationAttributeNames.ACCESS_TOKEN_ATTRIBUTES, jwt)
.build();
this.authorizationService.save(authorization);
return new OAuth2AccessTokenAuthenticationToken(registeredClient, clientPrincipal, accessToken, refreshToken);
return new OAuth2AccessTokenAuthenticationToken(
registeredClient, clientPrincipal, accessToken, refreshToken);
}
@Override

View File

@ -13,16 +13,16 @@
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.springframework.security.oauth2.server.authorization.authentication;
import java.util.Collections;
import java.util.Set;
import org.springframework.security.authentication.AbstractAuthenticationToken;
import org.springframework.security.core.Authentication;
import org.springframework.security.oauth2.server.authorization.Version;
import org.springframework.util.Assert;
import java.util.Collections;
import java.util.Set;
/**
* An {@link Authentication} implementation used for the OAuth 2.0 Refresh Token Grant.
*
@ -33,42 +33,37 @@ import org.springframework.util.Assert;
* @see OAuth2ClientAuthenticationToken
*/
public class OAuth2RefreshTokenAuthenticationToken extends AbstractAuthenticationToken {
private final Authentication clientPrincipal;
private static final long serialVersionUID = Version.SERIAL_VERSION_UID;
private final String refreshToken;
private final Authentication clientPrincipal;
private final Set<String> scopes;
/**
* Constructs an {@code OAuth2RefreshTokenAuthenticationToken} using the provided parameters.
*
* @param refreshToken refresh token value
* @param refreshToken the refresh token
* @param clientPrincipal the authenticated client principal
*/
public OAuth2RefreshTokenAuthenticationToken(String refreshToken, Authentication clientPrincipal) {
this(clientPrincipal, refreshToken, Collections.emptySet());
this(refreshToken, clientPrincipal, Collections.emptySet());
}
/**
* Constructs an {@code OAuth2RefreshTokenAuthenticationToken} using the provided parameters.
*
* @param refreshToken the refresh token
* @param clientPrincipal the authenticated client principal
* @param refreshToken refresh token value
* @param requestedScopes scopes requested by refresh token
* @param scopes the requested scope(s)
*/
public OAuth2RefreshTokenAuthenticationToken(Authentication clientPrincipal, String refreshToken, Set<String> requestedScopes) {
public OAuth2RefreshTokenAuthenticationToken(String refreshToken, Authentication clientPrincipal,
Set<String> scopes) {
super(Collections.emptySet());
Assert.hasText(refreshToken, "refreshToken cannot be empty");
Assert.notNull(clientPrincipal, "clientPrincipal cannot be null");
Assert.hasText(refreshToken, "refreshToken cannot be null or empty");
this.clientPrincipal = clientPrincipal;
Assert.notNull(scopes, "scopes cannot be null");
this.refreshToken = refreshToken;
this.scopes = requestedScopes;
}
@Override
public Object getCredentials() {
return "";
this.clientPrincipal = clientPrincipal;
this.scopes = scopes;
}
@Override
@ -76,6 +71,16 @@ public class OAuth2RefreshTokenAuthenticationToken extends AbstractAuthenticatio
return this.clientPrincipal;
}
@Override
public Object getCredentials() {
return "";
}
/**
* Returns the refresh token.
*
* @return the refresh token
*/
public String getRefreshToken() {
return this.refreshToken;
}

View File

@ -13,19 +13,8 @@
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.springframework.security.oauth2.server.authorization.authentication;
import java.net.MalformedURLException;
import java.net.URI;
import java.net.URL;
import java.time.Duration;
import java.time.Instant;
import java.time.temporal.ChronoUnit;
import java.util.Base64;
import java.util.Collections;
import java.util.Set;
import org.springframework.security.crypto.keygen.Base64StringKeyGenerator;
import org.springframework.security.crypto.keygen.StringKeyGenerator;
import org.springframework.security.oauth2.core.OAuth2RefreshToken;
@ -36,13 +25,23 @@ import org.springframework.security.oauth2.jwt.Jwt;
import org.springframework.security.oauth2.jwt.JwtClaimsSet;
import org.springframework.security.oauth2.jwt.JwtEncoder;
import java.net.MalformedURLException;
import java.net.URI;
import java.net.URL;
import java.time.Duration;
import java.time.Instant;
import java.time.temporal.ChronoUnit;
import java.util.Base64;
import java.util.Collections;
import java.util.Set;
/**
* @author Alexey Nesterov
* @since 0.0.3
*/
class OAuth2TokenIssuerUtil {
private static final StringKeyGenerator CODE_GENERATOR = new Base64StringKeyGenerator(Base64.getUrlEncoder().withoutPadding(), 96);
private static final StringKeyGenerator TOKEN_GENERATOR = new Base64StringKeyGenerator(Base64.getUrlEncoder().withoutPadding(), 96);
static Jwt issueJwtAccessToken(JwtEncoder jwtEncoder, String subject, String audience, Set<String> scopes) {
JoseHeader joseHeader = JoseHeader.withAlgorithm(SignatureAlgorithm.RS256).build();
@ -71,8 +70,8 @@ class OAuth2TokenIssuerUtil {
static OAuth2RefreshToken issueRefreshToken(Duration refreshTokenTimeToLive) {
Instant issuedAt = Instant.now();
Instant refreshTokenExpiresAt = issuedAt.plus(refreshTokenTimeToLive);
Instant expiresAt = issuedAt.plus(refreshTokenTimeToLive);
return new OAuth2RefreshToken(CODE_GENERATOR.generateKey(), issuedAt, refreshTokenExpiresAt);
return new OAuth2RefreshToken(TOKEN_GENERATOR.generateKey(), issuedAt, expiresAt);
}
}

View File

@ -15,12 +15,12 @@
*/
package org.springframework.security.oauth2.server.authorization.config;
import org.springframework.util.Assert;
import java.time.Duration;
import java.util.HashMap;
import java.util.Map;
import org.springframework.util.Assert;
/**
* A facility for token configuration settings.
*
@ -61,33 +61,31 @@ public class TokenSettings extends Settings {
}
/**
* Set the time-to-live for an access token.
* Set the time-to-live for an access token. Must be greater than {@code Duration.ZERO}.
*
* @param accessTokenTimeToLive the time-to-live for an access token
* @return the {@link TokenSettings}
*/
public TokenSettings accessTokenTimeToLive(Duration accessTokenTimeToLive) {
Assert.notNull(accessTokenTimeToLive, "accessTokenTimeToLive cannot be null");
Assert.isTrue(accessTokenTimeToLive.getSeconds() > 0, "accessTokenTimeToLive must be greater than Duration.ZERO");
setting(ACCESS_TOKEN_TIME_TO_LIVE, accessTokenTimeToLive);
return this;
}
/**
* Returns {@code true} if refresh tokens support is enabled.
* This include generation of refresh token as a part of Authorization Code Grant flow and support of Refresh Token
* Grant flow. The default is {@code true}.
* Returns {@code true} if refresh tokens are enabled. The default is {@code true}.
*
* @return {@code true} if the client support refresh token, {@code false} otherwise
* @return {@code true} if refresh tokens are enabled, {@code false} otherwise
*/
public boolean enableRefreshTokens() {
return setting(ENABLE_REFRESH_TOKENS);
}
/**
* Set to {@code true} to enable refresh tokens support.
* This include generation of refresh token as a part of Authorization Code Grant flow and support of Refresh Token
* Grant flow.
* Set to {@code true} to enable refresh tokens.
*
* @param enableRefreshTokens {@code true} to enable refresh token grant support, {@code false} otherwise
* @param enableRefreshTokens {@code true} to enable refresh tokens, {@code false} otherwise
* @return the {@link TokenSettings}
*/
public TokenSettings enableRefreshTokens(boolean enableRefreshTokens) {
@ -96,18 +94,19 @@ public class TokenSettings extends Settings {
}
/**
* Returns {@code true} if existing refresh token is re-used when a new access token is requested via Refresh Token grant,
* or {@code false} if a new refresh token is generated.
* The default is {@code false}.
* Returns {@code true} if refresh tokens are reused when returning the access token response,
* or {@code false} if a new refresh token is issued. The default is {@code true}.
*/
public boolean reuseRefreshTokens() {
return setting(REUSE_REFRESH_TOKENS);
}
/**
* Set to {@code true} to re-use existing refresh token when new access token is requested via Refresh Token grant,
* or to {@code false} to generate a new refresh token.
* @param reuseRefreshTokens {@code true} to re-use existing refresh token, {@code false} to generate a new one
* Set to {@code true} if refresh tokens are reused when returning the access token response,
* or {@code false} if a new refresh token is issued.
*
* @param reuseRefreshTokens {@code true} to reuse refresh tokens, {@code false} to issue new refresh tokens
* @return the {@link TokenSettings}
*/
public TokenSettings reuseRefreshTokens(boolean reuseRefreshTokens) {
setting(REUSE_REFRESH_TOKENS, reuseRefreshTokens);
@ -115,21 +114,23 @@ public class TokenSettings extends Settings {
}
/**
* Returns refresh token time-to-live. The default is 60 minutes. Always greater than {@code Duration.ZERO}.
* @return refresh token time-to-live
* Returns the time-to-live for a refresh token. The default is 60 minutes.
*
* @return the time-to-live for a refresh token
*/
public Duration refreshTokenTimeToLive() {
return setting(REFRESH_TOKEN_TIME_TO_LIVE);
}
/**
* Sets refresh token time-to-live.
* @param refreshTokenTimeToLive refresh token time-to-live. Has to be greater than {@code Duration.ZERO}.
* Set the time-to-live for a refresh token. Must be greater than {@code Duration.ZERO}.
*
* @param refreshTokenTimeToLive the time-to-live for a refresh token
* @return the {@link TokenSettings}
*/
public TokenSettings refreshTokenTimeToLive(Duration refreshTokenTimeToLive) {
Assert.notNull(refreshTokenTimeToLive, "refreshTokenTimeToLive cannot be null");
Assert.isTrue(refreshTokenTimeToLive.getSeconds() > 0, "refreshTokenTimeToLive has to be greater than Duration.ZERO");
Assert.isTrue(refreshTokenTimeToLive.getSeconds() > 0, "refreshTokenTimeToLive must be greater than Duration.ZERO");
setting(REFRESH_TOKEN_TIME_TO_LIVE, refreshTokenTimeToLive);
return this;
}
@ -138,7 +139,7 @@ public class TokenSettings extends Settings {
Map<String, Object> settings = new HashMap<>();
settings.put(ACCESS_TOKEN_TIME_TO_LIVE, Duration.ofMinutes(5));
settings.put(ENABLE_REFRESH_TOKENS, true);
settings.put(REUSE_REFRESH_TOKENS, false);
settings.put(REUSE_REFRESH_TOKENS, true);
settings.put(REFRESH_TOKEN_TIME_TO_LIVE, Duration.ofMinutes(60));
return settings;
}

View File

@ -130,8 +130,8 @@ public class OAuth2TokenEndpointFilter extends OncePerRequestFilter {
this.tokenEndpointMatcher = new AntPathRequestMatcher(tokenEndpointUri, HttpMethod.POST.name());
Map<AuthorizationGrantType, Converter<HttpServletRequest, Authentication>> converters = new HashMap<>();
converters.put(AuthorizationGrantType.AUTHORIZATION_CODE, new AuthorizationCodeAuthenticationConverter());
converters.put(AuthorizationGrantType.CLIENT_CREDENTIALS, new ClientCredentialsAuthenticationConverter());
converters.put(AuthorizationGrantType.REFRESH_TOKEN, new RefreshTokenAuthenticationConverter());
converters.put(AuthorizationGrantType.CLIENT_CREDENTIALS, new ClientCredentialsAuthenticationConverter());
this.authorizationGrantAuthenticationConverter = new DelegatingAuthorizationGrantAuthenticationConverter(converters);
}
@ -165,7 +165,9 @@ public class OAuth2TokenEndpointFilter extends OncePerRequestFilter {
}
}
private void sendAccessTokenResponse(HttpServletResponse response, OAuth2AccessToken accessToken, OAuth2RefreshToken refreshToken) throws IOException {
private void sendAccessTokenResponse(HttpServletResponse response, OAuth2AccessToken accessToken,
OAuth2RefreshToken refreshToken) throws IOException {
OAuth2AccessTokenResponse.Builder builder =
OAuth2AccessTokenResponse.withToken(accessToken.getTokenValue())
.tokenType(accessToken.getTokenType())
@ -235,6 +237,43 @@ public class OAuth2TokenEndpointFilter extends OncePerRequestFilter {
}
}
private static class RefreshTokenAuthenticationConverter implements Converter<HttpServletRequest, Authentication> {
@Override
public Authentication convert(HttpServletRequest request) {
// grant_type (REQUIRED)
String grantType = request.getParameter(OAuth2ParameterNames.GRANT_TYPE);
if (!AuthorizationGrantType.REFRESH_TOKEN.getValue().equals(grantType)) {
return null;
}
Authentication clientPrincipal = SecurityContextHolder.getContext().getAuthentication();
MultiValueMap<String, String> parameters = OAuth2EndpointUtils.getParameters(request);
// refresh_token (REQUIRED)
String refreshToken = parameters.getFirst(OAuth2ParameterNames.REFRESH_TOKEN);
if (!StringUtils.hasText(refreshToken) ||
parameters.get(OAuth2ParameterNames.REFRESH_TOKEN).size() != 1) {
throwError(OAuth2ErrorCodes.INVALID_REQUEST, OAuth2ParameterNames.REFRESH_TOKEN);
}
// scope (OPTIONAL)
String scope = parameters.getFirst(OAuth2ParameterNames.SCOPE);
if (StringUtils.hasText(scope) &&
parameters.get(OAuth2ParameterNames.SCOPE).size() != 1) {
throwError(OAuth2ErrorCodes.INVALID_REQUEST, OAuth2ParameterNames.SCOPE);
}
if (StringUtils.hasText(scope)) {
Set<String> requestedScopes = new HashSet<>(
Arrays.asList(StringUtils.delimitedListToStringArray(scope, " ")));
return new OAuth2RefreshTokenAuthenticationToken(refreshToken, clientPrincipal, requestedScopes);
}
return new OAuth2RefreshTokenAuthenticationToken(refreshToken, clientPrincipal);
}
}
private static class ClientCredentialsAuthenticationConverter implements Converter<HttpServletRequest, Authentication> {
@Override
@ -264,41 +303,4 @@ public class OAuth2TokenEndpointFilter extends OncePerRequestFilter {
return new OAuth2ClientCredentialsAuthenticationToken(clientPrincipal);
}
}
private static class RefreshTokenAuthenticationConverter implements Converter<HttpServletRequest, Authentication> {
@Override
public Authentication convert(HttpServletRequest request) {
// grant_type (REQUIRED)
String grantType = request.getParameter(OAuth2ParameterNames.GRANT_TYPE);
if (!AuthorizationGrantType.REFRESH_TOKEN.getValue().equals(grantType)) {
return null;
}
Authentication clientPrincipal = SecurityContextHolder.getContext().getAuthentication();
MultiValueMap<String, String> parameters = OAuth2EndpointUtils.getParameters(request);
// refresh token (REQUIRED)
String refreshToken = parameters.getFirst(OAuth2ParameterNames.REFRESH_TOKEN);
if (StringUtils.hasText(refreshToken) &&
parameters.get(OAuth2ParameterNames.REFRESH_TOKEN).size() != 1) {
throwError(OAuth2ErrorCodes.INVALID_REQUEST, OAuth2ParameterNames.REFRESH_TOKEN);
}
// scope (OPTIONAL)
String scope = parameters.getFirst(OAuth2ParameterNames.SCOPE);
if (StringUtils.hasText(scope) &&
parameters.get(OAuth2ParameterNames.SCOPE).size() != 1) {
throwError(OAuth2ErrorCodes.INVALID_REQUEST, OAuth2ParameterNames.SCOPE);
}
if (StringUtils.hasText(scope)) {
Set<String> requestedScopes = new HashSet<>(
Arrays.asList(StringUtils.delimitedListToStringArray(scope, " ")));
return new OAuth2RefreshTokenAuthenticationToken(clientPrincipal, refreshToken, requestedScopes);
}
return new OAuth2RefreshTokenAuthenticationToken(refreshToken, clientPrincipal);
}
}
}

View File

@ -162,7 +162,7 @@ public class OAuth2RefreshTokenAuthenticationProviderTests {
when(this.authorizationService.findByToken(REFRESH_TOKEN_VALUE, TokenType.REFRESH_TOKEN))
.thenReturn(this.authorization);
RegisteredClient clientWithReuseTokensTrue = TestRegisteredClients.registeredClient()
RegisteredClient clientWithReuseTokensTrue = TestRegisteredClients.registeredClient2()
.tokenSettings(tokenSettings -> tokenSettings.reuseRefreshTokens(true))
.build();
@ -183,7 +183,7 @@ public class OAuth2RefreshTokenAuthenticationProviderTests {
when(this.authorizationService.findByToken(REFRESH_TOKEN_VALUE, TokenType.REFRESH_TOKEN))
.thenReturn(this.authorization);
RegisteredClient clientWithReuseTokensFalse = TestRegisteredClients.registeredClient()
RegisteredClient clientWithReuseTokensFalse = TestRegisteredClients.registeredClient2()
.tokenSettings(tokenSettings -> tokenSettings.reuseRefreshTokens(false))
.build();
@ -208,7 +208,7 @@ public class OAuth2RefreshTokenAuthenticationProviderTests {
requestedScopes.add("openid");
OAuth2RefreshTokenAuthenticationToken tokenWithScopes
= new OAuth2RefreshTokenAuthenticationToken(this.clientPrincipal, REFRESH_TOKEN_VALUE, requestedScopes);
= new OAuth2RefreshTokenAuthenticationToken(REFRESH_TOKEN_VALUE, this.clientPrincipal, requestedScopes);
when(this.authorizationService.findByToken(REFRESH_TOKEN_VALUE, TokenType.REFRESH_TOKEN))
.thenReturn(this.authorization);
@ -227,7 +227,7 @@ public class OAuth2RefreshTokenAuthenticationProviderTests {
requestedScopes.add("another-scope");
OAuth2RefreshTokenAuthenticationToken tokenWithScopes
= new OAuth2RefreshTokenAuthenticationToken(this.clientPrincipal, REFRESH_TOKEN_VALUE, requestedScopes);
= new OAuth2RefreshTokenAuthenticationToken(REFRESH_TOKEN_VALUE, this.clientPrincipal, requestedScopes);
when(this.authorizationService.findByToken(REFRESH_TOKEN_VALUE, TokenType.REFRESH_TOKEN))
.thenReturn(this.authorization);

View File

@ -34,7 +34,7 @@ public class OAuth2RefreshTokenAuthenticationTokenTests {
@Test
public void constructorWhenClientPrincipalNullThrowException() {
assertThatThrownBy(() -> new OAuth2RefreshTokenAuthenticationToken("", null))
assertThatThrownBy(() -> new OAuth2RefreshTokenAuthenticationToken("test", null))
.isInstanceOf(IllegalArgumentException.class)
.hasMessage("clientPrincipal cannot be null");
}
@ -43,18 +43,18 @@ public class OAuth2RefreshTokenAuthenticationTokenTests {
public void constructorWhenRefreshTokenNullOrEmptyThrowException() {
assertThatThrownBy(() -> new OAuth2RefreshTokenAuthenticationToken(null, mock(OAuth2ClientAuthenticationToken.class)))
.isInstanceOf(IllegalArgumentException.class)
.hasMessage("refreshToken cannot be null or empty");
.hasMessage("refreshToken cannot be empty");
assertThatThrownBy(() -> new OAuth2RefreshTokenAuthenticationToken("", mock(OAuth2ClientAuthenticationToken.class)))
.isInstanceOf(IllegalArgumentException.class)
.hasMessage("refreshToken cannot be null or empty");
.hasMessage("refreshToken cannot be empty");
}
@Test
public void constructorWhenGettingScopesThenReturnRequestedScopes() {
Set<String> expectedScopes = new HashSet<>(Arrays.asList("scope-a", "scope-b"));
OAuth2RefreshTokenAuthenticationToken token
= new OAuth2RefreshTokenAuthenticationToken(mock(OAuth2ClientAuthenticationToken.class), "test", expectedScopes);
= new OAuth2RefreshTokenAuthenticationToken("test", mock(OAuth2ClientAuthenticationToken.class), expectedScopes);
assertThat(token.getScopes()).containsAll(expectedScopes);
}

View File

@ -35,7 +35,7 @@ public class TokenSettingsTests {
assertThat(tokenSettings.settings()).hasSize(4);
assertThat(tokenSettings.accessTokenTimeToLive()).isEqualTo(Duration.ofMinutes(5));
assertThat(tokenSettings.enableRefreshTokens()).isTrue();
assertThat(tokenSettings.reuseRefreshTokens()).isEqualTo(false);
assertThat(tokenSettings.reuseRefreshTokens()).isEqualTo(true);
assertThat(tokenSettings.refreshTokenTimeToLive()).isEqualTo(Duration.ofMinutes(60));
}
@ -83,12 +83,12 @@ public class TokenSettingsTests {
assertThatThrownBy(() -> new TokenSettings().refreshTokenTimeToLive(Duration.ZERO))
.isInstanceOf(IllegalArgumentException.class)
.extracting(Throwable::getMessage)
.isEqualTo("refreshTokenTimeToLive has to be greater than Duration.ZERO");
.isEqualTo("refreshTokenTimeToLive must be greater than Duration.ZERO");
assertThatThrownBy(() -> new TokenSettings().refreshTokenTimeToLive(Duration.ofSeconds(-10)))
.isInstanceOf(IllegalArgumentException.class)
.extracting(Throwable::getMessage)
.isEqualTo("refreshTokenTimeToLive has to be greater than Duration.ZERO");
.isEqualTo("refreshTokenTimeToLive must be greater than Duration.ZERO");
}
@Test
@ -101,7 +101,7 @@ public class TokenSettingsTests {
assertThat(tokenSettings.settings()).hasSize(6);
assertThat(tokenSettings.accessTokenTimeToLive()).isEqualTo(accessTokenTimeToLive);
assertThat(tokenSettings.enableRefreshTokens()).isTrue();
assertThat(tokenSettings.reuseRefreshTokens()).isFalse();
assertThat(tokenSettings.reuseRefreshTokens()).isTrue();
assertThat(tokenSettings.refreshTokenTimeToLive()).isEqualTo(Duration.ofMinutes(60));
assertThat(tokenSettings.<String>setting("name1")).isEqualTo("value1");
assertThat(tokenSettings.<String>setting("name2")).isEqualTo("value2");

View File

@ -1,7 +1,8 @@
ext['spring-security.version'] = '5.5.+' // TODO remove once Spring Boot upgraded to Spring Security 5.5
apply plugin: 'io.spring.convention.spring-sample-boot'
// TODO Remove once Spring Boot upgrades to Spring Security 5.5
ext['spring-security.version'] = '5.5.+'
dependencies {
compile 'org.springframework.boot:spring-boot-starter-web'
compile 'org.springframework.boot:spring-boot-starter-security'

View File

@ -50,8 +50,8 @@ public class WebClientConfig {
OAuth2AuthorizedClientProvider authorizedClientProvider =
OAuth2AuthorizedClientProviderBuilder.builder()
.authorizationCode()
.clientCredentials()
.refreshToken()
.clientCredentials()
.build();
DefaultOAuth2AuthorizedClientManager authorizedClientManager = new DefaultOAuth2AuthorizedClientManager(
clientRegistrationRepository, authorizedClientRepository);