Polish gh-84
This commit is contained in:
@@ -0,0 +1,188 @@
|
||||
/*
|
||||
* Copyright 2020 the original author or authors.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* https://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
package org.springframework.security.config.annotation.web.configurers.oauth2.server.authorization;
|
||||
|
||||
import org.junit.Before;
|
||||
import org.junit.BeforeClass;
|
||||
import org.junit.Rule;
|
||||
import org.junit.Test;
|
||||
import org.mockito.ArgumentCaptor;
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.context.annotation.Bean;
|
||||
import org.springframework.context.annotation.Import;
|
||||
import org.springframework.http.HttpHeaders;
|
||||
import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity;
|
||||
import org.springframework.security.config.annotation.web.configuration.OAuth2AuthorizationServerConfiguration;
|
||||
import org.springframework.security.config.test.SpringTestRule;
|
||||
import org.springframework.security.crypto.keys.KeyManager;
|
||||
import org.springframework.security.crypto.keys.StaticKeyGeneratingKeyManager;
|
||||
import org.springframework.security.oauth2.core.AbstractOAuth2Token;
|
||||
import org.springframework.security.oauth2.core.OAuth2AccessToken;
|
||||
import org.springframework.security.oauth2.core.OAuth2RefreshToken;
|
||||
import org.springframework.security.oauth2.server.authorization.OAuth2Authorization;
|
||||
import org.springframework.security.oauth2.server.authorization.OAuth2AuthorizationService;
|
||||
import org.springframework.security.oauth2.server.authorization.TestOAuth2Authorizations;
|
||||
import org.springframework.security.oauth2.server.authorization.TokenType;
|
||||
import org.springframework.security.oauth2.server.authorization.client.RegisteredClient;
|
||||
import org.springframework.security.oauth2.server.authorization.client.RegisteredClientRepository;
|
||||
import org.springframework.security.oauth2.server.authorization.client.TestRegisteredClients;
|
||||
import org.springframework.security.oauth2.server.authorization.web.OAuth2TokenRevocationEndpointFilter;
|
||||
import org.springframework.test.web.servlet.MockMvc;
|
||||
import org.springframework.test.web.servlet.request.MockMvcRequestBuilders;
|
||||
import org.springframework.util.LinkedMultiValueMap;
|
||||
import org.springframework.util.MultiValueMap;
|
||||
|
||||
import java.net.URLEncoder;
|
||||
import java.nio.charset.StandardCharsets;
|
||||
import java.util.Base64;
|
||||
|
||||
import static org.assertj.core.api.Assertions.assertThat;
|
||||
import static org.mockito.ArgumentMatchers.eq;
|
||||
import static org.mockito.Mockito.mock;
|
||||
import static org.mockito.Mockito.reset;
|
||||
import static org.mockito.Mockito.verify;
|
||||
import static org.mockito.Mockito.when;
|
||||
import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status;
|
||||
|
||||
/**
|
||||
* Integration tests for the OAuth 2.0 Token Revocation endpoint.
|
||||
*
|
||||
* @author Joe Grandja
|
||||
*/
|
||||
public class OAuth2TokenRevocationTests {
|
||||
private static RegisteredClientRepository registeredClientRepository;
|
||||
private static OAuth2AuthorizationService authorizationService;
|
||||
private static KeyManager keyManager;
|
||||
|
||||
@Rule
|
||||
public final SpringTestRule spring = new SpringTestRule();
|
||||
|
||||
@Autowired
|
||||
private MockMvc mvc;
|
||||
|
||||
@BeforeClass
|
||||
public static void init() {
|
||||
registeredClientRepository = mock(RegisteredClientRepository.class);
|
||||
authorizationService = mock(OAuth2AuthorizationService.class);
|
||||
keyManager = new StaticKeyGeneratingKeyManager();
|
||||
}
|
||||
|
||||
@Before
|
||||
public void setup() {
|
||||
reset(registeredClientRepository);
|
||||
reset(authorizationService);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void requestWhenRevokeRefreshTokenThenRevoked() throws Exception {
|
||||
this.spring.register(AuthorizationServerConfiguration.class).autowire();
|
||||
|
||||
RegisteredClient registeredClient = TestRegisteredClients.registeredClient().build();
|
||||
when(registeredClientRepository.findByClientId(eq(registeredClient.getClientId())))
|
||||
.thenReturn(registeredClient);
|
||||
|
||||
OAuth2Authorization authorization = TestOAuth2Authorizations.authorization(registeredClient).build();
|
||||
OAuth2RefreshToken token = authorization.getTokens().getRefreshToken();
|
||||
TokenType tokenType = TokenType.REFRESH_TOKEN;
|
||||
when(authorizationService.findByToken(eq(token.getTokenValue()), eq(tokenType))).thenReturn(authorization);
|
||||
|
||||
this.mvc.perform(MockMvcRequestBuilders.post(OAuth2TokenRevocationEndpointFilter.DEFAULT_TOKEN_REVOCATION_ENDPOINT_URI)
|
||||
.params(getTokenRevocationRequestParameters(token, tokenType))
|
||||
.header(HttpHeaders.AUTHORIZATION, "Basic " + encodeBasicAuth(
|
||||
registeredClient.getClientId(), registeredClient.getClientSecret())))
|
||||
.andExpect(status().isOk());
|
||||
|
||||
verify(registeredClientRepository).findByClientId(eq(registeredClient.getClientId()));
|
||||
verify(authorizationService).findByToken(eq(token.getTokenValue()), eq(tokenType));
|
||||
|
||||
ArgumentCaptor<OAuth2Authorization> authorizationCaptor = ArgumentCaptor.forClass(OAuth2Authorization.class);
|
||||
verify(authorizationService).save(authorizationCaptor.capture());
|
||||
|
||||
OAuth2Authorization updatedAuthorization = authorizationCaptor.getValue();
|
||||
OAuth2RefreshToken refreshToken = updatedAuthorization.getTokens().getRefreshToken();
|
||||
assertThat(updatedAuthorization.getTokens().getTokenMetadata(refreshToken).isInvalidated()).isTrue();
|
||||
OAuth2AccessToken accessToken = updatedAuthorization.getTokens().getAccessToken();
|
||||
assertThat(updatedAuthorization.getTokens().getTokenMetadata(accessToken).isInvalidated()).isTrue();
|
||||
}
|
||||
|
||||
@Test
|
||||
public void requestWhenRevokeAccessTokenThenRevoked() throws Exception {
|
||||
this.spring.register(AuthorizationServerConfiguration.class).autowire();
|
||||
|
||||
RegisteredClient registeredClient = TestRegisteredClients.registeredClient().build();
|
||||
when(registeredClientRepository.findByClientId(eq(registeredClient.getClientId())))
|
||||
.thenReturn(registeredClient);
|
||||
|
||||
OAuth2Authorization authorization = TestOAuth2Authorizations.authorization(registeredClient).build();
|
||||
OAuth2AccessToken token = authorization.getTokens().getAccessToken();
|
||||
TokenType tokenType = TokenType.ACCESS_TOKEN;
|
||||
when(authorizationService.findByToken(eq(token.getTokenValue()), eq(tokenType))).thenReturn(authorization);
|
||||
|
||||
this.mvc.perform(MockMvcRequestBuilders.post(OAuth2TokenRevocationEndpointFilter.DEFAULT_TOKEN_REVOCATION_ENDPOINT_URI)
|
||||
.params(getTokenRevocationRequestParameters(token, tokenType))
|
||||
.header(HttpHeaders.AUTHORIZATION, "Basic " + encodeBasicAuth(
|
||||
registeredClient.getClientId(), registeredClient.getClientSecret())))
|
||||
.andExpect(status().isOk());
|
||||
|
||||
verify(registeredClientRepository).findByClientId(eq(registeredClient.getClientId()));
|
||||
verify(authorizationService).findByToken(eq(token.getTokenValue()), eq(tokenType));
|
||||
|
||||
ArgumentCaptor<OAuth2Authorization> authorizationCaptor = ArgumentCaptor.forClass(OAuth2Authorization.class);
|
||||
verify(authorizationService).save(authorizationCaptor.capture());
|
||||
|
||||
OAuth2Authorization updatedAuthorization = authorizationCaptor.getValue();
|
||||
OAuth2AccessToken accessToken = updatedAuthorization.getTokens().getAccessToken();
|
||||
assertThat(updatedAuthorization.getTokens().getTokenMetadata(accessToken).isInvalidated()).isTrue();
|
||||
OAuth2RefreshToken refreshToken = updatedAuthorization.getTokens().getRefreshToken();
|
||||
assertThat(updatedAuthorization.getTokens().getTokenMetadata(refreshToken).isInvalidated()).isFalse();
|
||||
}
|
||||
|
||||
private static MultiValueMap<String, String> getTokenRevocationRequestParameters(AbstractOAuth2Token token, TokenType tokenType) {
|
||||
MultiValueMap<String, String> parameters = new LinkedMultiValueMap<>();
|
||||
// TODO Use OAuth2ParameterNames
|
||||
parameters.set("token", token.getTokenValue());
|
||||
parameters.set("token_type_hint", tokenType.getValue());
|
||||
return parameters;
|
||||
}
|
||||
|
||||
private static String encodeBasicAuth(String clientId, String secret) throws Exception {
|
||||
clientId = URLEncoder.encode(clientId, StandardCharsets.UTF_8.name());
|
||||
secret = URLEncoder.encode(secret, StandardCharsets.UTF_8.name());
|
||||
String credentialsString = clientId + ":" + secret;
|
||||
byte[] encodedBytes = Base64.getEncoder().encode(credentialsString.getBytes(StandardCharsets.UTF_8));
|
||||
return new String(encodedBytes, StandardCharsets.UTF_8);
|
||||
}
|
||||
|
||||
@EnableWebSecurity
|
||||
@Import(OAuth2AuthorizationServerConfiguration.class)
|
||||
static class AuthorizationServerConfiguration {
|
||||
|
||||
@Bean
|
||||
RegisteredClientRepository registeredClientRepository() {
|
||||
return registeredClientRepository;
|
||||
}
|
||||
|
||||
@Bean
|
||||
OAuth2AuthorizationService authorizationService() {
|
||||
return authorizationService;
|
||||
}
|
||||
|
||||
@Bean
|
||||
KeyManager keyManager() {
|
||||
return keyManager;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -16,6 +16,7 @@
|
||||
package org.springframework.security.oauth2.server.authorization;
|
||||
|
||||
import org.springframework.security.oauth2.core.OAuth2AccessToken;
|
||||
import org.springframework.security.oauth2.core.OAuth2RefreshToken;
|
||||
import org.springframework.security.oauth2.core.endpoint.OAuth2AuthorizationRequest;
|
||||
import org.springframework.security.oauth2.server.authorization.client.RegisteredClient;
|
||||
import org.springframework.security.oauth2.server.authorization.client.TestRegisteredClients;
|
||||
@@ -23,6 +24,7 @@ import org.springframework.security.oauth2.server.authorization.token.OAuth2Auth
|
||||
import org.springframework.security.oauth2.server.authorization.token.OAuth2Tokens;
|
||||
|
||||
import java.time.Instant;
|
||||
import java.time.temporal.ChronoUnit;
|
||||
import java.util.Collections;
|
||||
import java.util.Map;
|
||||
|
||||
@@ -46,6 +48,8 @@ public class TestOAuth2Authorizations {
|
||||
"code", Instant.now(), Instant.now().plusSeconds(120));
|
||||
OAuth2AccessToken accessToken = new OAuth2AccessToken(
|
||||
OAuth2AccessToken.TokenType.BEARER, "access-token", Instant.now(), Instant.now().plusSeconds(300));
|
||||
OAuth2RefreshToken refreshToken = new OAuth2RefreshToken(
|
||||
"refresh-token", Instant.now(), Instant.now().plus(1, ChronoUnit.HOURS));
|
||||
OAuth2AuthorizationRequest authorizationRequest = OAuth2AuthorizationRequest.authorizationCode()
|
||||
.authorizationUri("https://provider.com/oauth2/authorize")
|
||||
.clientId(registeredClient.getClientId())
|
||||
@@ -56,7 +60,7 @@ public class TestOAuth2Authorizations {
|
||||
.build();
|
||||
return OAuth2Authorization.withRegisteredClient(registeredClient)
|
||||
.principalName("principal")
|
||||
.tokens(OAuth2Tokens.builder().token(authorizationCode).accessToken(accessToken).build())
|
||||
.tokens(OAuth2Tokens.builder().token(authorizationCode).accessToken(accessToken).refreshToken(refreshToken).build())
|
||||
.attribute(OAuth2AuthorizationAttributeNames.AUTHORIZATION_REQUEST, authorizationRequest)
|
||||
.attribute(OAuth2AuthorizationAttributeNames.AUTHORIZED_SCOPES, authorizationRequest.getScopes());
|
||||
}
|
||||
|
||||
@@ -38,6 +38,7 @@ import org.springframework.security.oauth2.server.authorization.client.Registere
|
||||
import org.springframework.security.oauth2.server.authorization.client.RegisteredClientRepository;
|
||||
import org.springframework.security.oauth2.server.authorization.client.TestRegisteredClients;
|
||||
import org.springframework.security.oauth2.server.authorization.token.OAuth2AuthorizationCode;
|
||||
import org.springframework.security.oauth2.server.authorization.token.OAuth2TokenMetadata;
|
||||
import org.springframework.security.oauth2.server.authorization.token.OAuth2Tokens;
|
||||
|
||||
import java.time.Instant;
|
||||
@@ -186,9 +187,10 @@ public class OAuth2AuthorizationCodeAuthenticationProviderTests {
|
||||
OAuth2AuthorizationCode authorizationCode = new OAuth2AuthorizationCode(
|
||||
AUTHORIZATION_CODE, Instant.now(), Instant.now().plusSeconds(120));
|
||||
OAuth2Authorization authorization = TestOAuth2Authorizations.authorization()
|
||||
.tokens(OAuth2Tokens.builder().token(authorizationCode).build())
|
||||
.tokens(OAuth2Tokens.builder()
|
||||
.token(authorizationCode, OAuth2TokenMetadata.builder().invalidated().build())
|
||||
.build())
|
||||
.build();
|
||||
authorization.getTokens().invalidate(authorizationCode);
|
||||
when(this.authorizationService.findByToken(eq(AUTHORIZATION_CODE), eq(TokenType.AUTHORIZATION_CODE)))
|
||||
.thenReturn(authorization);
|
||||
|
||||
|
||||
@@ -0,0 +1,194 @@
|
||||
/*
|
||||
* Copyright 2020 the original author or authors.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* https://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
package org.springframework.security.oauth2.server.authorization.authentication;
|
||||
|
||||
import org.junit.Before;
|
||||
import org.junit.Test;
|
||||
import org.mockito.ArgumentCaptor;
|
||||
import org.springframework.security.authentication.TestingAuthenticationToken;
|
||||
import org.springframework.security.oauth2.core.OAuth2AccessToken;
|
||||
import org.springframework.security.oauth2.core.OAuth2AuthenticationException;
|
||||
import org.springframework.security.oauth2.core.OAuth2ErrorCodes;
|
||||
import org.springframework.security.oauth2.core.OAuth2RefreshToken;
|
||||
import org.springframework.security.oauth2.server.authorization.OAuth2Authorization;
|
||||
import org.springframework.security.oauth2.server.authorization.OAuth2AuthorizationService;
|
||||
import org.springframework.security.oauth2.server.authorization.TestOAuth2Authorizations;
|
||||
import org.springframework.security.oauth2.server.authorization.TokenType;
|
||||
import org.springframework.security.oauth2.server.authorization.client.RegisteredClient;
|
||||
import org.springframework.security.oauth2.server.authorization.client.TestRegisteredClients;
|
||||
|
||||
import static org.assertj.core.api.Assertions.assertThat;
|
||||
import static org.assertj.core.api.Assertions.assertThatThrownBy;
|
||||
import static org.mockito.ArgumentMatchers.any;
|
||||
import static org.mockito.ArgumentMatchers.eq;
|
||||
import static org.mockito.Mockito.mock;
|
||||
import static org.mockito.Mockito.never;
|
||||
import static org.mockito.Mockito.verify;
|
||||
import static org.mockito.Mockito.when;
|
||||
|
||||
/**
|
||||
* Tests for {@link OAuth2TokenRevocationAuthenticationProvider}.
|
||||
*
|
||||
* @author Vivek Babu
|
||||
* @author Joe Grandja
|
||||
*/
|
||||
public class OAuth2TokenRevocationAuthenticationProviderTests {
|
||||
private RegisteredClient registeredClient;
|
||||
private OAuth2AuthorizationService authorizationService;
|
||||
private OAuth2TokenRevocationAuthenticationProvider authenticationProvider;
|
||||
|
||||
@Before
|
||||
public void setUp() {
|
||||
this.registeredClient = TestRegisteredClients.registeredClient().build();
|
||||
this.authorizationService = mock(OAuth2AuthorizationService.class);
|
||||
this.authenticationProvider = new OAuth2TokenRevocationAuthenticationProvider(this.authorizationService);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void constructorWhenAuthorizationServiceNullThenThrowIllegalArgumentException() {
|
||||
assertThatThrownBy(() -> new OAuth2TokenRevocationAuthenticationProvider(null))
|
||||
.isInstanceOf(IllegalArgumentException.class)
|
||||
.hasMessage("authorizationService cannot be null");
|
||||
}
|
||||
|
||||
@Test
|
||||
public void supportsWhenTypeOAuth2TokenRevocationAuthenticationTokenThenReturnTrue() {
|
||||
assertThat(this.authenticationProvider.supports(OAuth2TokenRevocationAuthenticationToken.class)).isTrue();
|
||||
}
|
||||
|
||||
@Test
|
||||
public void authenticateWhenClientPrincipalNotOAuth2ClientAuthenticationTokenThenThrowOAuth2AuthenticationException() {
|
||||
TestingAuthenticationToken clientPrincipal = new TestingAuthenticationToken(
|
||||
this.registeredClient.getClientId(), this.registeredClient.getClientSecret());
|
||||
OAuth2TokenRevocationAuthenticationToken authentication = new OAuth2TokenRevocationAuthenticationToken(
|
||||
"token", clientPrincipal, TokenType.ACCESS_TOKEN.getValue());
|
||||
assertThatThrownBy(() -> this.authenticationProvider.authenticate(authentication))
|
||||
.isInstanceOf(OAuth2AuthenticationException.class)
|
||||
.extracting(ex -> ((OAuth2AuthenticationException) ex).getError())
|
||||
.extracting("errorCode")
|
||||
.isEqualTo(OAuth2ErrorCodes.INVALID_CLIENT);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void authenticateWhenClientPrincipalNotAuthenticatedThenThrowOAuth2AuthenticationException() {
|
||||
OAuth2ClientAuthenticationToken clientPrincipal = new OAuth2ClientAuthenticationToken(
|
||||
this.registeredClient.getClientId(), this.registeredClient.getClientSecret(), null);
|
||||
OAuth2TokenRevocationAuthenticationToken authentication = new OAuth2TokenRevocationAuthenticationToken(
|
||||
"token", clientPrincipal, TokenType.ACCESS_TOKEN.getValue());
|
||||
assertThatThrownBy(() -> this.authenticationProvider.authenticate(authentication))
|
||||
.isInstanceOf(OAuth2AuthenticationException.class)
|
||||
.extracting(ex -> ((OAuth2AuthenticationException) ex).getError())
|
||||
.extracting("errorCode")
|
||||
.isEqualTo(OAuth2ErrorCodes.INVALID_CLIENT);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void authenticateWhenInvalidTokenTypeThenThrowOAuth2AuthenticationException() {
|
||||
OAuth2ClientAuthenticationToken clientPrincipal = new OAuth2ClientAuthenticationToken(this.registeredClient);
|
||||
OAuth2TokenRevocationAuthenticationToken authentication = new OAuth2TokenRevocationAuthenticationToken(
|
||||
"token", clientPrincipal, "unsupported_token_type");
|
||||
assertThatThrownBy(() -> this.authenticationProvider.authenticate(authentication))
|
||||
.isInstanceOf(OAuth2AuthenticationException.class)
|
||||
.extracting(ex -> ((OAuth2AuthenticationException) ex).getError())
|
||||
.extracting("errorCode")
|
||||
.isEqualTo("unsupported_token_type");
|
||||
}
|
||||
|
||||
@Test
|
||||
public void authenticateWhenInvalidTokenThenNotRevoked() {
|
||||
OAuth2ClientAuthenticationToken clientPrincipal = new OAuth2ClientAuthenticationToken(this.registeredClient);
|
||||
OAuth2TokenRevocationAuthenticationToken authentication = new OAuth2TokenRevocationAuthenticationToken(
|
||||
"token", clientPrincipal, TokenType.ACCESS_TOKEN.getValue());
|
||||
OAuth2TokenRevocationAuthenticationToken authenticationResult =
|
||||
(OAuth2TokenRevocationAuthenticationToken) this.authenticationProvider.authenticate(authentication);
|
||||
assertThat(authenticationResult.isAuthenticated()).isFalse();
|
||||
verify(this.authorizationService, never()).save(any());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void authenticateWhenTokenIssuedToAnotherClientThenThrowOAuth2AuthenticationException() {
|
||||
OAuth2Authorization authorization = TestOAuth2Authorizations.authorization(
|
||||
TestRegisteredClients.registeredClient2().build()).build();
|
||||
when(this.authorizationService.findByToken(
|
||||
eq("token"),
|
||||
eq(TokenType.ACCESS_TOKEN)))
|
||||
.thenReturn(authorization);
|
||||
|
||||
OAuth2ClientAuthenticationToken clientPrincipal = new OAuth2ClientAuthenticationToken(this.registeredClient);
|
||||
OAuth2TokenRevocationAuthenticationToken authentication = new OAuth2TokenRevocationAuthenticationToken(
|
||||
"token", clientPrincipal, TokenType.ACCESS_TOKEN.getValue());
|
||||
|
||||
assertThatThrownBy(() -> this.authenticationProvider.authenticate(authentication))
|
||||
.isInstanceOf(OAuth2AuthenticationException.class)
|
||||
.extracting(ex -> ((OAuth2AuthenticationException) ex).getError())
|
||||
.extracting("errorCode")
|
||||
.isEqualTo(OAuth2ErrorCodes.INVALID_CLIENT);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void authenticateWhenValidRefreshTokenThenRevoked() {
|
||||
OAuth2Authorization authorization = TestOAuth2Authorizations.authorization(
|
||||
this.registeredClient).build();
|
||||
when(this.authorizationService.findByToken(
|
||||
eq(authorization.getTokens().getRefreshToken().getTokenValue()),
|
||||
eq(TokenType.REFRESH_TOKEN)))
|
||||
.thenReturn(authorization);
|
||||
|
||||
OAuth2ClientAuthenticationToken clientPrincipal = new OAuth2ClientAuthenticationToken(this.registeredClient);
|
||||
OAuth2TokenRevocationAuthenticationToken authentication = new OAuth2TokenRevocationAuthenticationToken(
|
||||
authorization.getTokens().getRefreshToken().getTokenValue(), clientPrincipal, TokenType.REFRESH_TOKEN.getValue());
|
||||
|
||||
OAuth2TokenRevocationAuthenticationToken authenticationResult =
|
||||
(OAuth2TokenRevocationAuthenticationToken) this.authenticationProvider.authenticate(authentication);
|
||||
assertThat(authenticationResult.isAuthenticated()).isTrue();
|
||||
|
||||
ArgumentCaptor<OAuth2Authorization> authorizationCaptor = ArgumentCaptor.forClass(OAuth2Authorization.class);
|
||||
verify(this.authorizationService).save(authorizationCaptor.capture());
|
||||
|
||||
OAuth2Authorization updatedAuthorization = authorizationCaptor.getValue();
|
||||
OAuth2RefreshToken refreshToken = updatedAuthorization.getTokens().getRefreshToken();
|
||||
assertThat(updatedAuthorization.getTokens().getTokenMetadata(refreshToken).isInvalidated()).isTrue();
|
||||
OAuth2AccessToken accessToken = updatedAuthorization.getTokens().getAccessToken();
|
||||
assertThat(updatedAuthorization.getTokens().getTokenMetadata(accessToken).isInvalidated()).isTrue();
|
||||
}
|
||||
|
||||
@Test
|
||||
public void authenticateWhenValidAccessTokenThenRevoked() {
|
||||
OAuth2Authorization authorization = TestOAuth2Authorizations.authorization(
|
||||
this.registeredClient).build();
|
||||
when(this.authorizationService.findByToken(
|
||||
eq(authorization.getTokens().getAccessToken().getTokenValue()),
|
||||
eq(TokenType.ACCESS_TOKEN)))
|
||||
.thenReturn(authorization);
|
||||
|
||||
OAuth2ClientAuthenticationToken clientPrincipal = new OAuth2ClientAuthenticationToken(this.registeredClient);
|
||||
OAuth2TokenRevocationAuthenticationToken authentication = new OAuth2TokenRevocationAuthenticationToken(
|
||||
authorization.getTokens().getAccessToken().getTokenValue(), clientPrincipal, TokenType.ACCESS_TOKEN.getValue());
|
||||
|
||||
OAuth2TokenRevocationAuthenticationToken authenticationResult =
|
||||
(OAuth2TokenRevocationAuthenticationToken) this.authenticationProvider.authenticate(authentication);
|
||||
assertThat(authenticationResult.isAuthenticated()).isTrue();
|
||||
|
||||
ArgumentCaptor<OAuth2Authorization> authorizationCaptor = ArgumentCaptor.forClass(OAuth2Authorization.class);
|
||||
verify(this.authorizationService).save(authorizationCaptor.capture());
|
||||
|
||||
OAuth2Authorization updatedAuthorization = authorizationCaptor.getValue();
|
||||
OAuth2AccessToken accessToken = updatedAuthorization.getTokens().getAccessToken();
|
||||
assertThat(updatedAuthorization.getTokens().getTokenMetadata(accessToken).isInvalidated()).isTrue();
|
||||
OAuth2RefreshToken refreshToken = updatedAuthorization.getTokens().getRefreshToken();
|
||||
assertThat(updatedAuthorization.getTokens().getTokenMetadata(refreshToken).isInvalidated()).isFalse();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,93 @@
|
||||
/*
|
||||
* Copyright 2020 the original author or authors.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* https://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
package org.springframework.security.oauth2.server.authorization.authentication;
|
||||
|
||||
import org.junit.Test;
|
||||
import org.springframework.security.oauth2.core.OAuth2AccessToken;
|
||||
import org.springframework.security.oauth2.server.authorization.TokenType;
|
||||
import org.springframework.security.oauth2.server.authorization.client.TestRegisteredClients;
|
||||
|
||||
import java.time.Duration;
|
||||
import java.time.Instant;
|
||||
|
||||
import static org.assertj.core.api.Assertions.assertThat;
|
||||
import static org.assertj.core.api.Assertions.assertThatThrownBy;
|
||||
|
||||
/**
|
||||
* Tests for {@link OAuth2TokenRevocationAuthenticationToken}.
|
||||
*
|
||||
* @author Vivek Babu
|
||||
* @author Joe Grandja
|
||||
*/
|
||||
public class OAuth2TokenRevocationAuthenticationTokenTests {
|
||||
private String token = "token";
|
||||
private OAuth2ClientAuthenticationToken clientPrincipal = new OAuth2ClientAuthenticationToken(
|
||||
TestRegisteredClients.registeredClient().build());
|
||||
private String tokenTypeHint = TokenType.ACCESS_TOKEN.getValue();
|
||||
private OAuth2AccessToken accessToken = new OAuth2AccessToken(
|
||||
OAuth2AccessToken.TokenType.BEARER, this.token,
|
||||
Instant.now(), Instant.now().plus(Duration.ofHours(1)));
|
||||
|
||||
@Test
|
||||
public void constructorWhenTokenNullThenThrowIllegalArgumentException() {
|
||||
assertThatThrownBy(() -> new OAuth2TokenRevocationAuthenticationToken(null, this.clientPrincipal, this.tokenTypeHint))
|
||||
.isInstanceOf(IllegalArgumentException.class)
|
||||
.hasMessage("token cannot be empty");
|
||||
}
|
||||
|
||||
@Test
|
||||
public void constructorWhenClientPrincipalNullThenThrowIllegalArgumentException() {
|
||||
assertThatThrownBy(() -> new OAuth2TokenRevocationAuthenticationToken(this.token, null, this.tokenTypeHint))
|
||||
.isInstanceOf(IllegalArgumentException.class)
|
||||
.hasMessage("clientPrincipal cannot be null");
|
||||
}
|
||||
|
||||
@Test
|
||||
public void constructorWhenRevokedTokenNullThenThrowIllegalArgumentException() {
|
||||
assertThatThrownBy(() -> new OAuth2TokenRevocationAuthenticationToken(null, this.clientPrincipal))
|
||||
.isInstanceOf(IllegalArgumentException.class)
|
||||
.hasMessage("revokedToken cannot be null");
|
||||
}
|
||||
|
||||
@Test
|
||||
public void constructorWhenRevokedTokenAndClientPrincipalNullThenThrowIllegalArgumentException() {
|
||||
assertThatThrownBy(() -> new OAuth2TokenRevocationAuthenticationToken(this.accessToken, null))
|
||||
.isInstanceOf(IllegalArgumentException.class)
|
||||
.hasMessage("clientPrincipal cannot be null");
|
||||
}
|
||||
|
||||
@Test
|
||||
public void constructorWhenTokenProvidedThenCreated() {
|
||||
OAuth2TokenRevocationAuthenticationToken authentication = new OAuth2TokenRevocationAuthenticationToken(
|
||||
this.token, this.clientPrincipal, this.tokenTypeHint);
|
||||
assertThat(authentication.getToken()).isEqualTo(this.token);
|
||||
assertThat(authentication.getPrincipal()).isEqualTo(this.clientPrincipal);
|
||||
assertThat(authentication.getTokenTypeHint()).isEqualTo(this.tokenTypeHint);
|
||||
assertThat(authentication.getCredentials().toString()).isEmpty();
|
||||
assertThat(authentication.isAuthenticated()).isFalse();
|
||||
}
|
||||
|
||||
@Test
|
||||
public void constructorWhenRevokedTokenProvidedThenCreated() {
|
||||
OAuth2TokenRevocationAuthenticationToken authentication = new OAuth2TokenRevocationAuthenticationToken(
|
||||
this.accessToken, this.clientPrincipal);
|
||||
assertThat(authentication.getToken()).isEqualTo(this.accessToken.getTokenValue());
|
||||
assertThat(authentication.getPrincipal()).isEqualTo(this.clientPrincipal);
|
||||
assertThat(authentication.getTokenTypeHint()).isNull();
|
||||
assertThat(authentication.getCredentials().toString()).isEmpty();
|
||||
assertThat(authentication.isAuthenticated()).isTrue();
|
||||
}
|
||||
}
|
||||
@@ -82,11 +82,18 @@ public class OAuth2TokensTests {
|
||||
|
||||
@Test
|
||||
public void getTokenWhenTokenTypeNullThenThrowIllegalArgumentException() {
|
||||
assertThatThrownBy(() -> OAuth2Tokens.builder().build().getToken(null))
|
||||
assertThatThrownBy(() -> OAuth2Tokens.builder().build().getToken((Class<OAuth2AccessToken>) null))
|
||||
.isInstanceOf(IllegalArgumentException.class)
|
||||
.hasMessage("tokenType cannot be null");
|
||||
}
|
||||
|
||||
@Test
|
||||
public void getTokenWhenTokenNullThenThrowIllegalArgumentException() {
|
||||
assertThatThrownBy(() -> OAuth2Tokens.builder().build().getToken((String) null))
|
||||
.isInstanceOf(IllegalArgumentException.class)
|
||||
.hasMessage("token cannot be empty");
|
||||
}
|
||||
|
||||
@Test
|
||||
public void getTokenMetadataWhenTokenNullThenThrowIllegalArgumentException() {
|
||||
assertThatThrownBy(() -> OAuth2Tokens.builder().build().getTokenMetadata(null))
|
||||
@@ -185,32 +192,4 @@ public class OAuth2TokensTests {
|
||||
this.accessToken.getScopes());
|
||||
assertThat(tokens.getTokenMetadata(otherAccessToken)).isNull();
|
||||
}
|
||||
|
||||
@Test
|
||||
public void invalidateWhenAllTokensThenAllInvalidated() {
|
||||
OAuth2Tokens tokens = OAuth2Tokens.builder()
|
||||
.accessToken(this.accessToken)
|
||||
.refreshToken(this.refreshToken)
|
||||
.token(this.idToken)
|
||||
.build();
|
||||
tokens.invalidate();
|
||||
|
||||
assertThat(tokens.getTokenMetadata(tokens.getAccessToken()).isInvalidated()).isTrue();
|
||||
assertThat(tokens.getTokenMetadata(tokens.getRefreshToken()).isInvalidated()).isTrue();
|
||||
assertThat(tokens.getTokenMetadata(tokens.getToken(OidcIdToken.class)).isInvalidated()).isTrue();
|
||||
}
|
||||
|
||||
@Test
|
||||
public void invalidateWhenTokenProvidedThenInvalidated() {
|
||||
OAuth2Tokens tokens = OAuth2Tokens.builder()
|
||||
.accessToken(this.accessToken)
|
||||
.refreshToken(this.refreshToken)
|
||||
.token(this.idToken)
|
||||
.build();
|
||||
tokens.invalidate(this.accessToken);
|
||||
|
||||
assertThat(tokens.getTokenMetadata(tokens.getAccessToken()).isInvalidated()).isTrue();
|
||||
assertThat(tokens.getTokenMetadata(tokens.getRefreshToken()).isInvalidated()).isFalse();
|
||||
assertThat(tokens.getTokenMetadata(tokens.getToken(OidcIdToken.class)).isInvalidated()).isFalse();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,210 @@
|
||||
/*
|
||||
* Copyright 2020 the original author or authors.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* https://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
package org.springframework.security.oauth2.server.authorization.web;
|
||||
|
||||
import org.junit.After;
|
||||
import org.junit.Before;
|
||||
import org.junit.Test;
|
||||
import org.springframework.http.HttpStatus;
|
||||
import org.springframework.http.converter.HttpMessageConverter;
|
||||
import org.springframework.mock.http.client.MockClientHttpResponse;
|
||||
import org.springframework.mock.web.MockHttpServletRequest;
|
||||
import org.springframework.mock.web.MockHttpServletResponse;
|
||||
import org.springframework.security.authentication.AuthenticationManager;
|
||||
import org.springframework.security.core.Authentication;
|
||||
import org.springframework.security.core.context.SecurityContext;
|
||||
import org.springframework.security.core.context.SecurityContextHolder;
|
||||
import org.springframework.security.oauth2.core.OAuth2AccessToken;
|
||||
import org.springframework.security.oauth2.core.OAuth2Error;
|
||||
import org.springframework.security.oauth2.core.OAuth2ErrorCodes;
|
||||
import org.springframework.security.oauth2.core.http.converter.OAuth2ErrorHttpMessageConverter;
|
||||
import org.springframework.security.oauth2.server.authorization.TokenType;
|
||||
import org.springframework.security.oauth2.server.authorization.authentication.OAuth2ClientAuthenticationToken;
|
||||
import org.springframework.security.oauth2.server.authorization.authentication.OAuth2TokenRevocationAuthenticationToken;
|
||||
import org.springframework.security.oauth2.server.authorization.client.RegisteredClient;
|
||||
import org.springframework.security.oauth2.server.authorization.client.TestRegisteredClients;
|
||||
|
||||
import javax.servlet.FilterChain;
|
||||
import javax.servlet.http.HttpServletRequest;
|
||||
import javax.servlet.http.HttpServletResponse;
|
||||
import java.time.Duration;
|
||||
import java.time.Instant;
|
||||
import java.util.Arrays;
|
||||
import java.util.HashSet;
|
||||
import java.util.function.Consumer;
|
||||
|
||||
import static org.assertj.core.api.Assertions.assertThat;
|
||||
import static org.assertj.core.api.Assertions.assertThatThrownBy;
|
||||
import static org.mockito.ArgumentMatchers.any;
|
||||
import static org.mockito.Mockito.mock;
|
||||
import static org.mockito.Mockito.verify;
|
||||
import static org.mockito.Mockito.verifyNoInteractions;
|
||||
import static org.mockito.Mockito.when;
|
||||
import static org.springframework.security.oauth2.server.authorization.web.OAuth2TokenRevocationEndpointFilter.TOKEN_PARAM_NAME;
|
||||
import static org.springframework.security.oauth2.server.authorization.web.OAuth2TokenRevocationEndpointFilter.TOKEN_TYPE_HINT_PARAM_NAME;
|
||||
|
||||
/**
|
||||
* Tests for {@link OAuth2TokenRevocationEndpointFilter}.
|
||||
*
|
||||
* @author Vivek Babu
|
||||
* @author Joe Grandja
|
||||
*/
|
||||
public class OAuth2TokenRevocationEndpointFilterTests {
|
||||
private AuthenticationManager authenticationManager;
|
||||
private OAuth2TokenRevocationEndpointFilter filter;
|
||||
private final HttpMessageConverter<OAuth2Error> errorHttpResponseConverter =
|
||||
new OAuth2ErrorHttpMessageConverter();
|
||||
|
||||
@Before
|
||||
public void setUp() {
|
||||
this.authenticationManager = mock(AuthenticationManager.class);
|
||||
this.filter = new OAuth2TokenRevocationEndpointFilter(this.authenticationManager);
|
||||
}
|
||||
|
||||
@After
|
||||
public void cleanup() {
|
||||
SecurityContextHolder.clearContext();
|
||||
}
|
||||
|
||||
@Test
|
||||
public void constructorWhenAuthenticationManagerNullThenThrowIllegalArgumentException() {
|
||||
assertThatThrownBy(() -> new OAuth2TokenRevocationEndpointFilter(null))
|
||||
.isInstanceOf(IllegalArgumentException.class)
|
||||
.hasMessage("authenticationManager cannot be null");
|
||||
}
|
||||
|
||||
@Test
|
||||
public void constructorWhenTokenRevocationEndpointUriNullThenThrowIllegalArgumentException() {
|
||||
assertThatThrownBy(() -> new OAuth2TokenRevocationEndpointFilter(this.authenticationManager, null))
|
||||
.isInstanceOf(IllegalArgumentException.class)
|
||||
.hasMessage("tokenRevocationEndpointUri cannot be empty");
|
||||
}
|
||||
|
||||
@Test
|
||||
public void doFilterWhenNotTokenRevocationRequestThenNotProcessed() throws Exception {
|
||||
String requestUri = "/path";
|
||||
MockHttpServletRequest request = new MockHttpServletRequest("POST", requestUri);
|
||||
request.setServletPath(requestUri);
|
||||
MockHttpServletResponse response = new MockHttpServletResponse();
|
||||
FilterChain filterChain = mock(FilterChain.class);
|
||||
|
||||
this.filter.doFilter(request, response, filterChain);
|
||||
|
||||
verify(filterChain).doFilter(any(HttpServletRequest.class), any(HttpServletResponse.class));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void doFilterWhenTokenRevocationRequestGetThenNotProcessed() throws Exception {
|
||||
String requestUri = OAuth2TokenRevocationEndpointFilter.DEFAULT_TOKEN_REVOCATION_ENDPOINT_URI;
|
||||
MockHttpServletRequest request = new MockHttpServletRequest("GET", requestUri);
|
||||
request.setServletPath(requestUri);
|
||||
MockHttpServletResponse response = new MockHttpServletResponse();
|
||||
FilterChain filterChain = mock(FilterChain.class);
|
||||
|
||||
this.filter.doFilter(request, response, filterChain);
|
||||
|
||||
verify(filterChain).doFilter(any(HttpServletRequest.class), any(HttpServletResponse.class));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void doFilterWhenTokenRevocationRequestMissingTokenThenInvalidRequestError() throws Exception {
|
||||
doFilterWhenTokenRevocationRequestInvalidParameterThenError(
|
||||
TOKEN_PARAM_NAME,
|
||||
OAuth2ErrorCodes.INVALID_REQUEST,
|
||||
request -> request.removeParameter(TOKEN_PARAM_NAME));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void doFilterWhenTokenRevocationRequestMultipleTokenThenInvalidRequestError() throws Exception {
|
||||
doFilterWhenTokenRevocationRequestInvalidParameterThenError(
|
||||
TOKEN_PARAM_NAME,
|
||||
OAuth2ErrorCodes.INVALID_REQUEST,
|
||||
request -> request.addParameter(TOKEN_PARAM_NAME, "token-2"));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void doFilterWhenTokenRevocationRequestMultipleTokenTypeHintThenInvalidRequestError() throws Exception {
|
||||
doFilterWhenTokenRevocationRequestInvalidParameterThenError(
|
||||
TOKEN_TYPE_HINT_PARAM_NAME,
|
||||
OAuth2ErrorCodes.INVALID_REQUEST,
|
||||
request -> request.addParameter(TOKEN_TYPE_HINT_PARAM_NAME, TokenType.ACCESS_TOKEN.getValue()));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void doFilterWhenTokenRevocationRequestValidThenSuccessResponse() throws Exception {
|
||||
RegisteredClient registeredClient = TestRegisteredClients.registeredClient().build();
|
||||
Authentication clientPrincipal = new OAuth2ClientAuthenticationToken(registeredClient);
|
||||
OAuth2AccessToken accessToken = new OAuth2AccessToken(
|
||||
OAuth2AccessToken.TokenType.BEARER, "token",
|
||||
Instant.now(), Instant.now().plus(Duration.ofHours(1)),
|
||||
new HashSet<>(Arrays.asList("scope1", "scope2")));
|
||||
OAuth2TokenRevocationAuthenticationToken tokenRevocationAuthentication =
|
||||
new OAuth2TokenRevocationAuthenticationToken(
|
||||
accessToken, clientPrincipal);
|
||||
|
||||
when(this.authenticationManager.authenticate(any())).thenReturn(tokenRevocationAuthentication);
|
||||
|
||||
SecurityContext securityContext = SecurityContextHolder.createEmptyContext();
|
||||
securityContext.setAuthentication(clientPrincipal);
|
||||
SecurityContextHolder.setContext(securityContext);
|
||||
|
||||
MockHttpServletRequest request = createTokenRevocationRequest();
|
||||
MockHttpServletResponse response = new MockHttpServletResponse();
|
||||
FilterChain filterChain = mock(FilterChain.class);
|
||||
|
||||
this.filter.doFilter(request, response, filterChain);
|
||||
|
||||
verifyNoInteractions(filterChain);
|
||||
verify(this.authenticationManager).authenticate(any());
|
||||
|
||||
assertThat(response.getStatus()).isEqualTo(HttpStatus.OK.value());
|
||||
}
|
||||
|
||||
private void doFilterWhenTokenRevocationRequestInvalidParameterThenError(String parameterName, String errorCode,
|
||||
Consumer<MockHttpServletRequest> requestConsumer) throws Exception {
|
||||
|
||||
MockHttpServletRequest request = createTokenRevocationRequest();
|
||||
requestConsumer.accept(request);
|
||||
MockHttpServletResponse response = new MockHttpServletResponse();
|
||||
FilterChain filterChain = mock(FilterChain.class);
|
||||
|
||||
this.filter.doFilter(request, response, filterChain);
|
||||
|
||||
verifyNoInteractions(filterChain);
|
||||
|
||||
assertThat(response.getStatus()).isEqualTo(HttpStatus.BAD_REQUEST.value());
|
||||
OAuth2Error error = readError(response);
|
||||
assertThat(error.getErrorCode()).isEqualTo(errorCode);
|
||||
assertThat(error.getDescription()).isEqualTo("OAuth 2.0 Token Revocation Parameter: " + parameterName);
|
||||
}
|
||||
|
||||
private OAuth2Error readError(MockHttpServletResponse response) throws Exception {
|
||||
MockClientHttpResponse httpResponse = new MockClientHttpResponse(
|
||||
response.getContentAsByteArray(), HttpStatus.valueOf(response.getStatus()));
|
||||
return this.errorHttpResponseConverter.read(OAuth2Error.class, httpResponse);
|
||||
}
|
||||
|
||||
private static MockHttpServletRequest createTokenRevocationRequest() {
|
||||
String requestUri = OAuth2TokenRevocationEndpointFilter.DEFAULT_TOKEN_REVOCATION_ENDPOINT_URI;
|
||||
MockHttpServletRequest request = new MockHttpServletRequest("POST", requestUri);
|
||||
request.setServletPath(requestUri);
|
||||
|
||||
request.addParameter(TOKEN_PARAM_NAME, "token");
|
||||
request.addParameter(TOKEN_TYPE_HINT_PARAM_NAME, TokenType.ACCESS_TOKEN.getValue());
|
||||
|
||||
return request;
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user