|
|
|
@ -26,9 +26,6 @@ import java.util.Collections; |
|
|
|
import java.util.HashSet; |
|
|
|
import java.util.HashSet; |
|
|
|
import java.util.List; |
|
|
|
import java.util.List; |
|
|
|
|
|
|
|
|
|
|
|
import com.nimbusds.jose.jwk.JWKSet; |
|
|
|
|
|
|
|
import com.nimbusds.jose.jwk.source.JWKSource; |
|
|
|
|
|
|
|
import com.nimbusds.jose.proc.SecurityContext; |
|
|
|
|
|
|
|
import org.junit.After; |
|
|
|
import org.junit.After; |
|
|
|
import org.junit.AfterClass; |
|
|
|
import org.junit.AfterClass; |
|
|
|
import org.junit.BeforeClass; |
|
|
|
import org.junit.BeforeClass; |
|
|
|
@ -49,34 +46,38 @@ import org.springframework.jdbc.datasource.embedded.EmbeddedDatabaseBuilder; |
|
|
|
import org.springframework.jdbc.datasource.embedded.EmbeddedDatabaseType; |
|
|
|
import org.springframework.jdbc.datasource.embedded.EmbeddedDatabaseType; |
|
|
|
import org.springframework.mock.http.client.MockClientHttpResponse; |
|
|
|
import org.springframework.mock.http.client.MockClientHttpResponse; |
|
|
|
import org.springframework.mock.web.MockHttpServletResponse; |
|
|
|
import org.springframework.mock.web.MockHttpServletResponse; |
|
|
|
|
|
|
|
import org.springframework.security.authentication.AuthenticationProvider; |
|
|
|
import org.springframework.security.authentication.TestingAuthenticationToken; |
|
|
|
import org.springframework.security.authentication.TestingAuthenticationToken; |
|
|
|
|
|
|
|
import org.springframework.security.config.annotation.web.builders.HttpSecurity; |
|
|
|
import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity; |
|
|
|
import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity; |
|
|
|
import org.springframework.security.config.annotation.web.configuration.OAuth2AuthorizationServerConfiguration; |
|
|
|
import org.springframework.security.config.annotation.web.configuration.OAuth2AuthorizationServerConfiguration; |
|
|
|
import org.springframework.security.config.test.SpringTestRule; |
|
|
|
import org.springframework.security.config.test.SpringTestRule; |
|
|
|
|
|
|
|
import org.springframework.security.core.Authentication; |
|
|
|
import org.springframework.security.crypto.password.NoOpPasswordEncoder; |
|
|
|
import org.springframework.security.crypto.password.NoOpPasswordEncoder; |
|
|
|
import org.springframework.security.crypto.password.PasswordEncoder; |
|
|
|
import org.springframework.security.crypto.password.PasswordEncoder; |
|
|
|
import org.springframework.security.oauth2.core.AbstractOAuth2Token; |
|
|
|
import org.springframework.security.oauth2.core.AbstractOAuth2Token; |
|
|
|
import org.springframework.security.oauth2.core.AuthorizationGrantType; |
|
|
|
import org.springframework.security.oauth2.core.AuthorizationGrantType; |
|
|
|
|
|
|
|
import org.springframework.security.oauth2.core.ClientAuthenticationMethod; |
|
|
|
import org.springframework.security.oauth2.core.OAuth2AccessToken; |
|
|
|
import org.springframework.security.oauth2.core.OAuth2AccessToken; |
|
|
|
import org.springframework.security.oauth2.core.OAuth2AuthorizationCode; |
|
|
|
import org.springframework.security.oauth2.core.OAuth2AuthorizationCode; |
|
|
|
import org.springframework.security.oauth2.core.OAuth2RefreshToken; |
|
|
|
import org.springframework.security.oauth2.core.OAuth2RefreshToken; |
|
|
|
import org.springframework.security.oauth2.server.authorization.token.OAuth2TokenClaimsSet; |
|
|
|
|
|
|
|
import org.springframework.security.oauth2.core.OAuth2TokenFormat; |
|
|
|
import org.springframework.security.oauth2.core.OAuth2TokenFormat; |
|
|
|
import org.springframework.security.oauth2.core.OAuth2TokenIntrospection; |
|
|
|
import org.springframework.security.oauth2.core.OAuth2TokenIntrospection; |
|
|
|
|
|
|
|
import org.springframework.security.oauth2.core.OAuth2TokenIntrospectionClaimNames; |
|
|
|
import org.springframework.security.oauth2.core.OAuth2TokenType; |
|
|
|
import org.springframework.security.oauth2.core.OAuth2TokenType; |
|
|
|
import org.springframework.security.oauth2.core.endpoint.OAuth2AccessTokenResponse; |
|
|
|
import org.springframework.security.oauth2.core.endpoint.OAuth2AccessTokenResponse; |
|
|
|
import org.springframework.security.oauth2.core.endpoint.OAuth2ParameterNames; |
|
|
|
import org.springframework.security.oauth2.core.endpoint.OAuth2ParameterNames; |
|
|
|
import org.springframework.security.oauth2.core.http.converter.OAuth2AccessTokenResponseHttpMessageConverter; |
|
|
|
import org.springframework.security.oauth2.core.http.converter.OAuth2AccessTokenResponseHttpMessageConverter; |
|
|
|
import org.springframework.security.oauth2.core.http.converter.OAuth2TokenIntrospectionHttpMessageConverter; |
|
|
|
import org.springframework.security.oauth2.core.http.converter.OAuth2TokenIntrospectionHttpMessageConverter; |
|
|
|
import org.springframework.security.oauth2.jose.TestJwks; |
|
|
|
|
|
|
|
import org.springframework.security.oauth2.server.authorization.JdbcOAuth2AuthorizationConsentService; |
|
|
|
import org.springframework.security.oauth2.server.authorization.JdbcOAuth2AuthorizationConsentService; |
|
|
|
import org.springframework.security.oauth2.server.authorization.JdbcOAuth2AuthorizationService; |
|
|
|
import org.springframework.security.oauth2.server.authorization.JdbcOAuth2AuthorizationService; |
|
|
|
import org.springframework.security.oauth2.server.authorization.OAuth2Authorization; |
|
|
|
import org.springframework.security.oauth2.server.authorization.OAuth2Authorization; |
|
|
|
import org.springframework.security.oauth2.server.authorization.OAuth2AuthorizationConsentService; |
|
|
|
import org.springframework.security.oauth2.server.authorization.OAuth2AuthorizationConsentService; |
|
|
|
import org.springframework.security.oauth2.server.authorization.OAuth2AuthorizationService; |
|
|
|
import org.springframework.security.oauth2.server.authorization.OAuth2AuthorizationService; |
|
|
|
import org.springframework.security.oauth2.server.authorization.token.OAuth2TokenClaimsContext; |
|
|
|
|
|
|
|
import org.springframework.security.oauth2.server.authorization.OAuth2TokenCustomizer; |
|
|
|
import org.springframework.security.oauth2.server.authorization.OAuth2TokenCustomizer; |
|
|
|
import org.springframework.security.oauth2.server.authorization.TestOAuth2Authorizations; |
|
|
|
import org.springframework.security.oauth2.server.authorization.TestOAuth2Authorizations; |
|
|
|
|
|
|
|
import org.springframework.security.oauth2.server.authorization.authentication.OAuth2ClientAuthenticationToken; |
|
|
|
|
|
|
|
import org.springframework.security.oauth2.server.authorization.authentication.OAuth2TokenIntrospectionAuthenticationToken; |
|
|
|
import org.springframework.security.oauth2.server.authorization.client.JdbcRegisteredClientRepository; |
|
|
|
import org.springframework.security.oauth2.server.authorization.client.JdbcRegisteredClientRepository; |
|
|
|
import org.springframework.security.oauth2.server.authorization.client.JdbcRegisteredClientRepository.RegisteredClientParametersMapper; |
|
|
|
import org.springframework.security.oauth2.server.authorization.client.JdbcRegisteredClientRepository.RegisteredClientParametersMapper; |
|
|
|
import org.springframework.security.oauth2.server.authorization.client.RegisteredClient; |
|
|
|
import org.springframework.security.oauth2.server.authorization.client.RegisteredClient; |
|
|
|
@ -85,14 +86,24 @@ import org.springframework.security.oauth2.server.authorization.client.TestRegis |
|
|
|
import org.springframework.security.oauth2.server.authorization.config.ProviderSettings; |
|
|
|
import org.springframework.security.oauth2.server.authorization.config.ProviderSettings; |
|
|
|
import org.springframework.security.oauth2.server.authorization.config.TokenSettings; |
|
|
|
import org.springframework.security.oauth2.server.authorization.config.TokenSettings; |
|
|
|
import org.springframework.security.oauth2.server.authorization.jackson2.TestingAuthenticationTokenMixin; |
|
|
|
import org.springframework.security.oauth2.server.authorization.jackson2.TestingAuthenticationTokenMixin; |
|
|
|
|
|
|
|
import org.springframework.security.oauth2.server.authorization.token.OAuth2TokenClaimsContext; |
|
|
|
|
|
|
|
import org.springframework.security.oauth2.server.authorization.token.OAuth2TokenClaimsSet; |
|
|
|
|
|
|
|
import org.springframework.security.web.SecurityFilterChain; |
|
|
|
|
|
|
|
import org.springframework.security.web.authentication.AuthenticationConverter; |
|
|
|
|
|
|
|
import org.springframework.security.web.authentication.AuthenticationFailureHandler; |
|
|
|
|
|
|
|
import org.springframework.security.web.authentication.AuthenticationSuccessHandler; |
|
|
|
|
|
|
|
import org.springframework.security.web.util.matcher.RequestMatcher; |
|
|
|
import org.springframework.test.web.servlet.MockMvc; |
|
|
|
import org.springframework.test.web.servlet.MockMvc; |
|
|
|
import org.springframework.test.web.servlet.MvcResult; |
|
|
|
import org.springframework.test.web.servlet.MvcResult; |
|
|
|
import org.springframework.util.LinkedMultiValueMap; |
|
|
|
import org.springframework.util.LinkedMultiValueMap; |
|
|
|
import org.springframework.util.MultiValueMap; |
|
|
|
import org.springframework.util.MultiValueMap; |
|
|
|
|
|
|
|
|
|
|
|
import static org.assertj.core.api.Assertions.assertThat; |
|
|
|
import static org.assertj.core.api.Assertions.assertThat; |
|
|
|
|
|
|
|
import static org.mockito.ArgumentMatchers.any; |
|
|
|
|
|
|
|
import static org.mockito.ArgumentMatchers.eq; |
|
|
|
import static org.mockito.Mockito.mock; |
|
|
|
import static org.mockito.Mockito.mock; |
|
|
|
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; |
|
|
|
|
|
|
|
|
|
|
|
@ -104,9 +115,12 @@ import static org.springframework.test.web.servlet.result.MockMvcResultMatchers. |
|
|
|
*/ |
|
|
|
*/ |
|
|
|
public class OAuth2TokenIntrospectionTests { |
|
|
|
public class OAuth2TokenIntrospectionTests { |
|
|
|
private static EmbeddedDatabase db; |
|
|
|
private static EmbeddedDatabase db; |
|
|
|
private static JWKSource<SecurityContext> jwkSource; |
|
|
|
|
|
|
|
private static ProviderSettings providerSettings; |
|
|
|
private static ProviderSettings providerSettings; |
|
|
|
private static OAuth2TokenCustomizer<OAuth2TokenClaimsContext> accessTokenCustomizer; |
|
|
|
private static OAuth2TokenCustomizer<OAuth2TokenClaimsContext> accessTokenCustomizer; |
|
|
|
|
|
|
|
private static AuthenticationConverter authenticationConverter; |
|
|
|
|
|
|
|
private static AuthenticationProvider authenticationProvider; |
|
|
|
|
|
|
|
private static AuthenticationSuccessHandler authenticationSuccessHandler; |
|
|
|
|
|
|
|
private static AuthenticationFailureHandler authenticationFailureHandler; |
|
|
|
private static final HttpMessageConverter<OAuth2TokenIntrospection> tokenIntrospectionHttpResponseConverter = |
|
|
|
private static final HttpMessageConverter<OAuth2TokenIntrospection> tokenIntrospectionHttpResponseConverter = |
|
|
|
new OAuth2TokenIntrospectionHttpMessageConverter(); |
|
|
|
new OAuth2TokenIntrospectionHttpMessageConverter(); |
|
|
|
private static final HttpMessageConverter<OAuth2AccessTokenResponse> accessTokenHttpResponseConverter = |
|
|
|
private static final HttpMessageConverter<OAuth2AccessTokenResponse> accessTokenHttpResponseConverter = |
|
|
|
@ -129,9 +143,11 @@ public class OAuth2TokenIntrospectionTests { |
|
|
|
|
|
|
|
|
|
|
|
@BeforeClass |
|
|
|
@BeforeClass |
|
|
|
public static void init() { |
|
|
|
public static void init() { |
|
|
|
JWKSet jwkSet = new JWKSet(TestJwks.DEFAULT_RSA_JWK); |
|
|
|
|
|
|
|
jwkSource = (jwkSelector, securityContext) -> jwkSelector.select(jwkSet); |
|
|
|
|
|
|
|
providerSettings = ProviderSettings.builder().tokenIntrospectionEndpoint("/test/introspect").build(); |
|
|
|
providerSettings = ProviderSettings.builder().tokenIntrospectionEndpoint("/test/introspect").build(); |
|
|
|
|
|
|
|
authenticationConverter = mock(AuthenticationConverter.class); |
|
|
|
|
|
|
|
authenticationProvider = mock(AuthenticationProvider.class); |
|
|
|
|
|
|
|
authenticationSuccessHandler = mock(AuthenticationSuccessHandler.class); |
|
|
|
|
|
|
|
authenticationFailureHandler = mock(AuthenticationFailureHandler.class); |
|
|
|
accessTokenCustomizer = mock(OAuth2TokenCustomizer.class); |
|
|
|
accessTokenCustomizer = mock(OAuth2TokenCustomizer.class); |
|
|
|
db = new EmbeddedDatabaseBuilder() |
|
|
|
db = new EmbeddedDatabaseBuilder() |
|
|
|
.generateUniqueName(true) |
|
|
|
.generateUniqueName(true) |
|
|
|
@ -175,6 +191,7 @@ public class OAuth2TokenIntrospectionTests { |
|
|
|
.issuedAt(issuedAt) |
|
|
|
.issuedAt(issuedAt) |
|
|
|
.notBefore(issuedAt) |
|
|
|
.notBefore(issuedAt) |
|
|
|
.expiresAt(expiresAt) |
|
|
|
.expiresAt(expiresAt) |
|
|
|
|
|
|
|
.claim(OAuth2TokenIntrospectionClaimNames.SCOPE, accessToken.getScopes()) |
|
|
|
.id("id") |
|
|
|
.id("id") |
|
|
|
.build(); |
|
|
|
.build(); |
|
|
|
// @formatter:on
|
|
|
|
// @formatter:on
|
|
|
|
@ -314,6 +331,43 @@ public class OAuth2TokenIntrospectionTests { |
|
|
|
assertThat(tokenIntrospectionResponse.getId()).isEqualTo(accessTokenClaims.getId()); |
|
|
|
assertThat(tokenIntrospectionResponse.getId()).isEqualTo(accessTokenClaims.getId()); |
|
|
|
} |
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
@Test |
|
|
|
|
|
|
|
public void requestWhenTokenIntrospectionEndpointCustomizedThenUsed() throws Exception { |
|
|
|
|
|
|
|
this.spring.register(AuthorizationServerConfigurationCustomTokenIntrospectionEndpoint.class).autowire(); |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
RegisteredClient introspectRegisteredClient = TestRegisteredClients.registeredClient2().build(); |
|
|
|
|
|
|
|
this.registeredClientRepository.save(introspectRegisteredClient); |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
RegisteredClient authorizedRegisteredClient = TestRegisteredClients.registeredClient().build(); |
|
|
|
|
|
|
|
this.registeredClientRepository.save(authorizedRegisteredClient); |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
OAuth2Authorization authorization = TestOAuth2Authorizations.authorization(authorizedRegisteredClient).build(); |
|
|
|
|
|
|
|
this.authorizationService.save(authorization); |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
OAuth2AccessToken accessToken = authorization.getAccessToken().getToken(); |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
Authentication clientPrincipal = new OAuth2ClientAuthenticationToken( |
|
|
|
|
|
|
|
introspectRegisteredClient, ClientAuthenticationMethod.CLIENT_SECRET_BASIC, introspectRegisteredClient.getClientSecret()); |
|
|
|
|
|
|
|
OAuth2TokenIntrospectionAuthenticationToken tokenIntrospectionAuthentication = |
|
|
|
|
|
|
|
new OAuth2TokenIntrospectionAuthenticationToken( |
|
|
|
|
|
|
|
accessToken.getTokenValue(), clientPrincipal, null, null); |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
when(authenticationConverter.convert(any())).thenReturn(tokenIntrospectionAuthentication); |
|
|
|
|
|
|
|
when(authenticationProvider.supports(eq(OAuth2TokenIntrospectionAuthenticationToken.class))).thenReturn(true); |
|
|
|
|
|
|
|
when(authenticationProvider.authenticate(any())).thenReturn(tokenIntrospectionAuthentication); |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
// @formatter:off
|
|
|
|
|
|
|
|
this.mvc.perform(post(providerSettings.getTokenIntrospectionEndpoint()) |
|
|
|
|
|
|
|
.params(getTokenIntrospectionRequestParameters(accessToken, OAuth2TokenType.ACCESS_TOKEN)) |
|
|
|
|
|
|
|
.header(HttpHeaders.AUTHORIZATION, getAuthorizationHeader(introspectRegisteredClient))) |
|
|
|
|
|
|
|
.andExpect(status().isOk()); |
|
|
|
|
|
|
|
// @formatter:on
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
verify(authenticationConverter).convert(any()); |
|
|
|
|
|
|
|
verify(authenticationProvider).authenticate(eq(tokenIntrospectionAuthentication)); |
|
|
|
|
|
|
|
verify(authenticationSuccessHandler).onAuthenticationSuccess(any(), any(), eq(tokenIntrospectionAuthentication)); |
|
|
|
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
private static MultiValueMap<String, String> getTokenIntrospectionRequestParameters(AbstractOAuth2Token token, |
|
|
|
private static MultiValueMap<String, String> getTokenIntrospectionRequestParameters(AbstractOAuth2Token token, |
|
|
|
OAuth2TokenType tokenType) { |
|
|
|
OAuth2TokenType tokenType) { |
|
|
|
MultiValueMap<String, String> parameters = new LinkedMultiValueMap<>(); |
|
|
|
MultiValueMap<String, String> parameters = new LinkedMultiValueMap<>(); |
|
|
|
@ -420,4 +474,35 @@ public class OAuth2TokenIntrospectionTests { |
|
|
|
} |
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
} |
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
@EnableWebSecurity |
|
|
|
|
|
|
|
static class AuthorizationServerConfigurationCustomTokenIntrospectionEndpoint extends AuthorizationServerConfiguration { |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
// @formatter:off
|
|
|
|
|
|
|
|
@Bean |
|
|
|
|
|
|
|
public SecurityFilterChain authorizationServerSecurityFilterChain(HttpSecurity http) throws Exception { |
|
|
|
|
|
|
|
OAuth2AuthorizationServerConfigurer<HttpSecurity> authorizationServerConfigurer = |
|
|
|
|
|
|
|
new OAuth2AuthorizationServerConfigurer<>(); |
|
|
|
|
|
|
|
authorizationServerConfigurer |
|
|
|
|
|
|
|
.tokenIntrospectionEndpoint(tokenIntrospectionEndpoint -> |
|
|
|
|
|
|
|
tokenIntrospectionEndpoint |
|
|
|
|
|
|
|
.introspectionRequestConverter(authenticationConverter) |
|
|
|
|
|
|
|
.authenticationProvider(authenticationProvider) |
|
|
|
|
|
|
|
.introspectionResponseHandler(authenticationSuccessHandler) |
|
|
|
|
|
|
|
.errorResponseHandler(authenticationFailureHandler)); |
|
|
|
|
|
|
|
RequestMatcher endpointsMatcher = authorizationServerConfigurer.getEndpointsMatcher(); |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
http |
|
|
|
|
|
|
|
.requestMatcher(endpointsMatcher) |
|
|
|
|
|
|
|
.authorizeRequests(authorizeRequests -> |
|
|
|
|
|
|
|
authorizeRequests.anyRequest().authenticated() |
|
|
|
|
|
|
|
) |
|
|
|
|
|
|
|
.csrf(csrf -> csrf.ignoringRequestMatchers(endpointsMatcher)) |
|
|
|
|
|
|
|
.apply(authorizationServerConfigurer); |
|
|
|
|
|
|
|
return http.build(); |
|
|
|
|
|
|
|
} |
|
|
|
|
|
|
|
// @formatter:on
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
} |
|
|
|
} |
|
|
|
|