|
|
|
@ -1,5 +1,5 @@ |
|
|
|
/* |
|
|
|
/* |
|
|
|
* Copyright 2020-2021 the original author or authors. |
|
|
|
* Copyright 2020-2022 the original author or authors. |
|
|
|
* |
|
|
|
* |
|
|
|
* Licensed under the Apache License, Version 2.0 (the "License"); |
|
|
|
* Licensed under the Apache License, Version 2.0 (the "License"); |
|
|
|
* you may not use this file except in compliance with the License. |
|
|
|
* you may not use this file except in compliance with the License. |
|
|
|
@ -31,8 +31,6 @@ import org.junit.Test; |
|
|
|
import org.springframework.beans.factory.annotation.Autowired; |
|
|
|
import org.springframework.beans.factory.annotation.Autowired; |
|
|
|
import org.springframework.context.annotation.Bean; |
|
|
|
import org.springframework.context.annotation.Bean; |
|
|
|
import org.springframework.context.annotation.Import; |
|
|
|
import org.springframework.context.annotation.Import; |
|
|
|
import org.springframework.core.Ordered; |
|
|
|
|
|
|
|
import org.springframework.core.annotation.Order; |
|
|
|
|
|
|
|
import org.springframework.http.HttpHeaders; |
|
|
|
import org.springframework.http.HttpHeaders; |
|
|
|
import org.springframework.jdbc.core.JdbcOperations; |
|
|
|
import org.springframework.jdbc.core.JdbcOperations; |
|
|
|
import org.springframework.jdbc.core.JdbcTemplate; |
|
|
|
import org.springframework.jdbc.core.JdbcTemplate; |
|
|
|
@ -66,7 +64,6 @@ import org.springframework.security.oauth2.server.authorization.client.JdbcRegis |
|
|
|
import org.springframework.security.oauth2.server.authorization.client.RegisteredClient; |
|
|
|
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.RegisteredClientRepository; |
|
|
|
import org.springframework.security.oauth2.server.authorization.client.TestRegisteredClients; |
|
|
|
import org.springframework.security.oauth2.server.authorization.client.TestRegisteredClients; |
|
|
|
import org.springframework.security.oauth2.server.authorization.config.ProviderSettings; |
|
|
|
|
|
|
|
import org.springframework.security.oauth2.server.authorization.jackson2.TestingAuthenticationTokenMixin; |
|
|
|
import org.springframework.security.oauth2.server.authorization.jackson2.TestingAuthenticationTokenMixin; |
|
|
|
import org.springframework.security.web.SecurityFilterChain; |
|
|
|
import org.springframework.security.web.SecurityFilterChain; |
|
|
|
import org.springframework.security.web.authentication.AuthenticationConverter; |
|
|
|
import org.springframework.security.web.authentication.AuthenticationConverter; |
|
|
|
@ -81,8 +78,8 @@ import static org.assertj.core.api.Assertions.assertThat; |
|
|
|
import static org.mockito.ArgumentMatchers.any; |
|
|
|
import static org.mockito.ArgumentMatchers.any; |
|
|
|
import static org.mockito.ArgumentMatchers.eq; |
|
|
|
import static org.mockito.ArgumentMatchers.eq; |
|
|
|
import static org.mockito.Mockito.mock; |
|
|
|
import static org.mockito.Mockito.mock; |
|
|
|
import static org.mockito.Mockito.when; |
|
|
|
|
|
|
|
import static org.mockito.Mockito.verify; |
|
|
|
import static org.mockito.Mockito.verify; |
|
|
|
|
|
|
|
import static org.mockito.Mockito.when; |
|
|
|
import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.post; |
|
|
|
import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.post; |
|
|
|
import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status; |
|
|
|
import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status; |
|
|
|
|
|
|
|
|
|
|
|
@ -95,11 +92,11 @@ public class OAuth2TokenRevocationTests { |
|
|
|
private static final String DEFAULT_TOKEN_REVOCATION_ENDPOINT_URI = "/oauth2/revoke"; |
|
|
|
private static final String DEFAULT_TOKEN_REVOCATION_ENDPOINT_URI = "/oauth2/revoke"; |
|
|
|
private static EmbeddedDatabase db; |
|
|
|
private static EmbeddedDatabase db; |
|
|
|
private static JWKSource<SecurityContext> jwkSource; |
|
|
|
private static JWKSource<SecurityContext> jwkSource; |
|
|
|
private static ProviderSettings providerSettings; |
|
|
|
private static AuthenticationConverter authenticationConverter; |
|
|
|
private static AuthenticationConverter revocationRequestConverter; |
|
|
|
|
|
|
|
private static AuthenticationProvider authenticationProvider; |
|
|
|
private static AuthenticationProvider authenticationProvider; |
|
|
|
private static AuthenticationSuccessHandler revocationResponseHandler; |
|
|
|
private static AuthenticationSuccessHandler authenticationSuccessHandler; |
|
|
|
private static AuthenticationFailureHandler errorResponseHandler; |
|
|
|
private static AuthenticationFailureHandler authenticationFailureHandler; |
|
|
|
|
|
|
|
|
|
|
|
@Rule |
|
|
|
@Rule |
|
|
|
public final SpringTestRule spring = new SpringTestRule(); |
|
|
|
public final SpringTestRule spring = new SpringTestRule(); |
|
|
|
|
|
|
|
|
|
|
|
@ -119,11 +116,10 @@ public class OAuth2TokenRevocationTests { |
|
|
|
public static void init() { |
|
|
|
public static void init() { |
|
|
|
JWKSet jwkSet = new JWKSet(TestJwks.DEFAULT_RSA_JWK); |
|
|
|
JWKSet jwkSet = new JWKSet(TestJwks.DEFAULT_RSA_JWK); |
|
|
|
jwkSource = (jwkSelector, securityContext) -> jwkSelector.select(jwkSet); |
|
|
|
jwkSource = (jwkSelector, securityContext) -> jwkSelector.select(jwkSet); |
|
|
|
providerSettings = ProviderSettings.builder().tokenRevocationEndpoint("/test/revoke").build(); |
|
|
|
authenticationConverter = mock(AuthenticationConverter.class); |
|
|
|
revocationRequestConverter = mock(AuthenticationConverter.class); |
|
|
|
|
|
|
|
authenticationProvider = mock(AuthenticationProvider.class); |
|
|
|
authenticationProvider = mock(AuthenticationProvider.class); |
|
|
|
revocationResponseHandler = mock(AuthenticationSuccessHandler.class); |
|
|
|
authenticationSuccessHandler = mock(AuthenticationSuccessHandler.class); |
|
|
|
errorResponseHandler = mock(AuthenticationFailureHandler.class); |
|
|
|
authenticationFailureHandler = mock(AuthenticationFailureHandler.class); |
|
|
|
db = new EmbeddedDatabaseBuilder() |
|
|
|
db = new EmbeddedDatabaseBuilder() |
|
|
|
.generateUniqueName(true) |
|
|
|
.generateUniqueName(true) |
|
|
|
.setType(EmbeddedDatabaseType.HSQL) |
|
|
|
.setType(EmbeddedDatabaseType.HSQL) |
|
|
|
@ -173,42 +169,31 @@ public class OAuth2TokenRevocationTests { |
|
|
|
public void requestWhenRevokeAccessTokenThenRevoked() throws Exception { |
|
|
|
public void requestWhenRevokeAccessTokenThenRevoked() throws Exception { |
|
|
|
this.spring.register(AuthorizationServerConfiguration.class).autowire(); |
|
|
|
this.spring.register(AuthorizationServerConfiguration.class).autowire(); |
|
|
|
|
|
|
|
|
|
|
|
assertRevokeAccessTokenThenRevoked(DEFAULT_TOKEN_REVOCATION_ENDPOINT_URI); |
|
|
|
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
@Test |
|
|
|
|
|
|
|
public void requestWhenRevokeAccessTokenEndpointCustomizedThenUsed() throws Exception { |
|
|
|
|
|
|
|
this.spring.register(AuthorizationServerConfigurationCustomEndpoints.class).autowire(); |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
RegisteredClient registeredClient = TestRegisteredClients.registeredClient().build(); |
|
|
|
RegisteredClient registeredClient = TestRegisteredClients.registeredClient().build(); |
|
|
|
this.registeredClientRepository.save(registeredClient); |
|
|
|
this.registeredClientRepository.save(registeredClient); |
|
|
|
Authentication clientPrincipal = new OAuth2ClientAuthenticationToken( |
|
|
|
|
|
|
|
registeredClient, ClientAuthenticationMethod.CLIENT_SECRET_BASIC, registeredClient.getClientSecret()); |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
OAuth2Authorization authorization = TestOAuth2Authorizations.authorization(registeredClient).build(); |
|
|
|
OAuth2Authorization authorization = TestOAuth2Authorizations.authorization(registeredClient).build(); |
|
|
|
OAuth2AccessToken token = authorization.getAccessToken().getToken(); |
|
|
|
OAuth2AccessToken token = authorization.getAccessToken().getToken(); |
|
|
|
OAuth2TokenType tokenType = OAuth2TokenType.ACCESS_TOKEN; |
|
|
|
OAuth2TokenType tokenType = OAuth2TokenType.ACCESS_TOKEN; |
|
|
|
this.authorizationService.save(authorization); |
|
|
|
this.authorizationService.save(authorization); |
|
|
|
|
|
|
|
|
|
|
|
OAuth2TokenRevocationAuthenticationToken tokenRevocationAuthenticationResult = |
|
|
|
this.mvc.perform(post(DEFAULT_TOKEN_REVOCATION_ENDPOINT_URI) |
|
|
|
new OAuth2TokenRevocationAuthenticationToken(token, clientPrincipal); |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
when(revocationRequestConverter.convert(any())).thenReturn(tokenRevocationAuthenticationResult); |
|
|
|
|
|
|
|
when(authenticationProvider.supports(eq(OAuth2TokenRevocationAuthenticationToken.class))).thenReturn(true); |
|
|
|
|
|
|
|
when(authenticationProvider.authenticate(any())).thenReturn(tokenRevocationAuthenticationResult); |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
this.mvc.perform(post(providerSettings.getTokenRevocationEndpoint()) |
|
|
|
|
|
|
|
.params(getTokenRevocationRequestParameters(token, tokenType)) |
|
|
|
.params(getTokenRevocationRequestParameters(token, tokenType)) |
|
|
|
.header(HttpHeaders.AUTHORIZATION, "Basic " + encodeBasicAuth( |
|
|
|
.header(HttpHeaders.AUTHORIZATION, "Basic " + encodeBasicAuth( |
|
|
|
registeredClient.getClientId(), registeredClient.getClientSecret()))) |
|
|
|
registeredClient.getClientId(), registeredClient.getClientSecret()))) |
|
|
|
.andExpect(status().isOk()); |
|
|
|
.andExpect(status().isOk()); |
|
|
|
|
|
|
|
|
|
|
|
verify(revocationRequestConverter).convert(any()); |
|
|
|
OAuth2Authorization updatedAuthorization = this.authorizationService.findById(authorization.getId()); |
|
|
|
verify(authenticationProvider).authenticate(eq(tokenRevocationAuthenticationResult)); |
|
|
|
OAuth2Authorization.Token<OAuth2AccessToken> accessToken = updatedAuthorization.getAccessToken(); |
|
|
|
verify(revocationResponseHandler).onAuthenticationSuccess(any(), any(), eq(tokenRevocationAuthenticationResult)); |
|
|
|
assertThat(accessToken.isInvalidated()).isTrue(); |
|
|
|
|
|
|
|
OAuth2Authorization.Token<OAuth2RefreshToken> refreshToken = updatedAuthorization.getRefreshToken(); |
|
|
|
|
|
|
|
assertThat(refreshToken.isInvalidated()).isFalse(); |
|
|
|
} |
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
private void assertRevokeAccessTokenThenRevoked(String tokenRevocationEndpointUri) throws Exception { |
|
|
|
@Test |
|
|
|
|
|
|
|
public void requestWhenTokenRevocationEndpointCustomizedThenUsed() throws Exception { |
|
|
|
|
|
|
|
this.spring.register(AuthorizationServerConfigurationCustomTokenRevocationEndpoint.class).autowire(); |
|
|
|
|
|
|
|
|
|
|
|
RegisteredClient registeredClient = TestRegisteredClients.registeredClient().build(); |
|
|
|
RegisteredClient registeredClient = TestRegisteredClients.registeredClient().build(); |
|
|
|
this.registeredClientRepository.save(registeredClient); |
|
|
|
this.registeredClientRepository.save(registeredClient); |
|
|
|
|
|
|
|
|
|
|
|
@ -217,17 +202,24 @@ public class OAuth2TokenRevocationTests { |
|
|
|
OAuth2TokenType tokenType = OAuth2TokenType.ACCESS_TOKEN; |
|
|
|
OAuth2TokenType tokenType = OAuth2TokenType.ACCESS_TOKEN; |
|
|
|
this.authorizationService.save(authorization); |
|
|
|
this.authorizationService.save(authorization); |
|
|
|
|
|
|
|
|
|
|
|
this.mvc.perform(post(tokenRevocationEndpointUri) |
|
|
|
Authentication clientPrincipal = new OAuth2ClientAuthenticationToken( |
|
|
|
|
|
|
|
registeredClient, ClientAuthenticationMethod.CLIENT_SECRET_BASIC, registeredClient.getClientSecret()); |
|
|
|
|
|
|
|
OAuth2TokenRevocationAuthenticationToken tokenRevocationAuthentication = |
|
|
|
|
|
|
|
new OAuth2TokenRevocationAuthenticationToken(token, clientPrincipal); |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
when(authenticationConverter.convert(any())).thenReturn(tokenRevocationAuthentication); |
|
|
|
|
|
|
|
when(authenticationProvider.supports(eq(OAuth2TokenRevocationAuthenticationToken.class))).thenReturn(true); |
|
|
|
|
|
|
|
when(authenticationProvider.authenticate(any())).thenReturn(tokenRevocationAuthentication); |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
this.mvc.perform(post(DEFAULT_TOKEN_REVOCATION_ENDPOINT_URI) |
|
|
|
.params(getTokenRevocationRequestParameters(token, tokenType)) |
|
|
|
.params(getTokenRevocationRequestParameters(token, tokenType)) |
|
|
|
.header(HttpHeaders.AUTHORIZATION, "Basic " + encodeBasicAuth( |
|
|
|
.header(HttpHeaders.AUTHORIZATION, "Basic " + encodeBasicAuth( |
|
|
|
registeredClient.getClientId(), registeredClient.getClientSecret()))) |
|
|
|
registeredClient.getClientId(), registeredClient.getClientSecret()))) |
|
|
|
.andExpect(status().isOk()); |
|
|
|
.andExpect(status().isOk()); |
|
|
|
|
|
|
|
|
|
|
|
OAuth2Authorization updatedAuthorization = this.authorizationService.findById(authorization.getId()); |
|
|
|
verify(authenticationConverter).convert(any()); |
|
|
|
OAuth2Authorization.Token<OAuth2AccessToken> accessToken = updatedAuthorization.getAccessToken(); |
|
|
|
verify(authenticationProvider).authenticate(eq(tokenRevocationAuthentication)); |
|
|
|
assertThat(accessToken.isInvalidated()).isTrue(); |
|
|
|
verify(authenticationSuccessHandler).onAuthenticationSuccess(any(), any(), eq(tokenRevocationAuthentication)); |
|
|
|
OAuth2Authorization.Token<OAuth2RefreshToken> refreshToken = updatedAuthorization.getRefreshToken(); |
|
|
|
|
|
|
|
assertThat(refreshToken.isInvalidated()).isFalse(); |
|
|
|
|
|
|
|
} |
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
private static MultiValueMap<String, String> getTokenRevocationRequestParameters(AbstractOAuth2Token token, OAuth2TokenType tokenType) { |
|
|
|
private static MultiValueMap<String, String> getTokenRevocationRequestParameters(AbstractOAuth2Token token, OAuth2TokenType tokenType) { |
|
|
|
@ -302,22 +294,20 @@ public class OAuth2TokenRevocationTests { |
|
|
|
} |
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
@EnableWebSecurity |
|
|
|
@EnableWebSecurity |
|
|
|
@Import(OAuth2AuthorizationServerConfiguration.class) |
|
|
|
static class AuthorizationServerConfigurationCustomTokenRevocationEndpoint extends AuthorizationServerConfiguration { |
|
|
|
static class AuthorizationServerConfigurationCustomEndpoints extends AuthorizationServerConfiguration { |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
// @formatter:off
|
|
|
|
// @formatter:off
|
|
|
|
@Bean |
|
|
|
@Bean |
|
|
|
@Order(Ordered.HIGHEST_PRECEDENCE) |
|
|
|
|
|
|
|
public SecurityFilterChain authorizationServerSecurityFilterChain(HttpSecurity http) throws Exception { |
|
|
|
public SecurityFilterChain authorizationServerSecurityFilterChain(HttpSecurity http) throws Exception { |
|
|
|
OAuth2AuthorizationServerConfigurer<HttpSecurity> authorizationServerConfigurer = |
|
|
|
OAuth2AuthorizationServerConfigurer<HttpSecurity> authorizationServerConfigurer = |
|
|
|
new OAuth2AuthorizationServerConfigurer<>(); |
|
|
|
new OAuth2AuthorizationServerConfigurer<>(); |
|
|
|
authorizationServerConfigurer |
|
|
|
authorizationServerConfigurer |
|
|
|
.tokenRevocationEndpoint(tokenRevocationEndpoint -> |
|
|
|
.tokenRevocationEndpoint(tokenRevocationEndpoint -> |
|
|
|
tokenRevocationEndpoint |
|
|
|
tokenRevocationEndpoint |
|
|
|
.revocationRequestConverter(revocationRequestConverter) |
|
|
|
.revocationRequestConverter(authenticationConverter) |
|
|
|
.authenticationProvider(authenticationProvider) |
|
|
|
.authenticationProvider(authenticationProvider) |
|
|
|
.revocationResponseHandler(revocationResponseHandler) |
|
|
|
.revocationResponseHandler(authenticationSuccessHandler) |
|
|
|
.errorResponseHandler(errorResponseHandler)); |
|
|
|
.errorResponseHandler(authenticationFailureHandler)); |
|
|
|
RequestMatcher endpointsMatcher = authorizationServerConfigurer.getEndpointsMatcher(); |
|
|
|
RequestMatcher endpointsMatcher = authorizationServerConfigurer.getEndpointsMatcher(); |
|
|
|
|
|
|
|
|
|
|
|
http |
|
|
|
http |
|
|
|
@ -331,10 +321,6 @@ public class OAuth2TokenRevocationTests { |
|
|
|
} |
|
|
|
} |
|
|
|
// @formatter:on
|
|
|
|
// @formatter:on
|
|
|
|
|
|
|
|
|
|
|
|
@Bean |
|
|
|
|
|
|
|
ProviderSettings providerSettings() { |
|
|
|
|
|
|
|
return providerSettings; |
|
|
|
|
|
|
|
} |
|
|
|
|
|
|
|
} |
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
} |
|
|
|
} |
|
|
|
|