From 1f4b369912f544d0bcc307c946ed5fa77d5a6c9e Mon Sep 17 00:00:00 2001 From: Joe Grandja Date: Thu, 17 Jun 2021 05:56:11 -0400 Subject: [PATCH] Provide configuration for the token endpoint Closes gh-319 --- .../AbstractOAuth2Configurer.java | 45 ++++ .../OAuth2AuthorizationServerConfigurer.java | 90 ++++---- .../OAuth2TokenEndpointConfigurer.java | 194 ++++++++++++++++++ .../OAuth2ClientCredentialsGrantTests.java | 83 ++++++++ 4 files changed, 361 insertions(+), 51 deletions(-) create mode 100644 oauth2-authorization-server/src/main/java/org/springframework/security/config/annotation/web/configurers/oauth2/server/authorization/AbstractOAuth2Configurer.java create mode 100644 oauth2-authorization-server/src/main/java/org/springframework/security/config/annotation/web/configurers/oauth2/server/authorization/OAuth2TokenEndpointConfigurer.java diff --git a/oauth2-authorization-server/src/main/java/org/springframework/security/config/annotation/web/configurers/oauth2/server/authorization/AbstractOAuth2Configurer.java b/oauth2-authorization-server/src/main/java/org/springframework/security/config/annotation/web/configurers/oauth2/server/authorization/AbstractOAuth2Configurer.java new file mode 100644 index 00000000..325ecc20 --- /dev/null +++ b/oauth2-authorization-server/src/main/java/org/springframework/security/config/annotation/web/configurers/oauth2/server/authorization/AbstractOAuth2Configurer.java @@ -0,0 +1,45 @@ +/* + * Copyright 2020-2021 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.springframework.security.config.annotation.ObjectPostProcessor; +import org.springframework.security.config.annotation.web.HttpSecurityBuilder; +import org.springframework.security.web.util.matcher.RequestMatcher; + +/** + * Base configurer for an OAuth 2.0 component (e.g. protocol endpoint). + * + * @author Joe Grandja + * @since 0.1.2 + */ +abstract class AbstractOAuth2Configurer { + private final ObjectPostProcessor objectPostProcessor; + + AbstractOAuth2Configurer(ObjectPostProcessor objectPostProcessor) { + this.objectPostProcessor = objectPostProcessor; + } + + abstract > void init(B builder); + + abstract > void configure(B builder); + + abstract RequestMatcher getRequestMatcher(); + + protected T postProcess(T object) { + return (T) this.objectPostProcessor.postProcess(object); + } + +} diff --git a/oauth2-authorization-server/src/main/java/org/springframework/security/config/annotation/web/configurers/oauth2/server/authorization/OAuth2AuthorizationServerConfigurer.java b/oauth2-authorization-server/src/main/java/org/springframework/security/config/annotation/web/configurers/oauth2/server/authorization/OAuth2AuthorizationServerConfigurer.java index 2c52b248..9b0da659 100644 --- a/oauth2-authorization-server/src/main/java/org/springframework/security/config/annotation/web/configurers/oauth2/server/authorization/OAuth2AuthorizationServerConfigurer.java +++ b/oauth2-authorization-server/src/main/java/org/springframework/security/config/annotation/web/configurers/oauth2/server/authorization/OAuth2AuthorizationServerConfigurer.java @@ -16,6 +16,8 @@ package org.springframework.security.config.annotation.web.configurers.oauth2.server.authorization; import java.net.URI; +import java.util.LinkedHashMap; +import java.util.Map; import com.nimbusds.jose.jwk.source.JWKSource; import com.nimbusds.jose.proc.SecurityContext; @@ -23,19 +25,14 @@ import com.nimbusds.jose.proc.SecurityContext; import org.springframework.http.HttpMethod; import org.springframework.http.HttpStatus; import org.springframework.security.authentication.AuthenticationManager; +import org.springframework.security.config.Customizer; import org.springframework.security.config.annotation.web.HttpSecurityBuilder; import org.springframework.security.config.annotation.web.configurers.AbstractHttpConfigurer; import org.springframework.security.config.annotation.web.configurers.ExceptionHandlingConfigurer; import org.springframework.security.crypto.password.PasswordEncoder; -import org.springframework.security.oauth2.jwt.JwtEncoder; -import org.springframework.security.oauth2.server.authorization.JwtEncodingContext; import org.springframework.security.oauth2.server.authorization.OAuth2AuthorizationConsentService; import org.springframework.security.oauth2.server.authorization.OAuth2AuthorizationService; -import org.springframework.security.oauth2.server.authorization.OAuth2TokenCustomizer; -import org.springframework.security.oauth2.server.authorization.authentication.OAuth2AuthorizationCodeAuthenticationProvider; import org.springframework.security.oauth2.server.authorization.authentication.OAuth2ClientAuthenticationProvider; -import org.springframework.security.oauth2.server.authorization.authentication.OAuth2ClientCredentialsAuthenticationProvider; -import org.springframework.security.oauth2.server.authorization.authentication.OAuth2RefreshTokenAuthenticationProvider; import org.springframework.security.oauth2.server.authorization.authentication.OAuth2TokenIntrospectionAuthenticationProvider; import org.springframework.security.oauth2.server.authorization.authentication.OAuth2TokenRevocationAuthenticationProvider; import org.springframework.security.oauth2.server.authorization.client.RegisteredClientRepository; @@ -47,7 +44,6 @@ import org.springframework.security.oauth2.server.authorization.web.NimbusJwkSet import org.springframework.security.oauth2.server.authorization.web.OAuth2AuthorizationEndpointFilter; import org.springframework.security.oauth2.server.authorization.web.OAuth2AuthorizationServerMetadataEndpointFilter; import org.springframework.security.oauth2.server.authorization.web.OAuth2ClientAuthenticationFilter; -import org.springframework.security.oauth2.server.authorization.web.OAuth2TokenEndpointFilter; import org.springframework.security.oauth2.server.authorization.web.OAuth2TokenIntrospectionEndpointFilter; import org.springframework.security.oauth2.server.authorization.web.OAuth2TokenRevocationEndpointFilter; import org.springframework.security.web.access.intercept.FilterSecurityInterceptor; @@ -68,11 +64,11 @@ import org.springframework.util.StringUtils; * @author Ovidiu Popa * @since 0.0.1 * @see AbstractHttpConfigurer + * @see OAuth2TokenEndpointConfigurer * @see RegisteredClientRepository * @see OAuth2AuthorizationService * @see OAuth2AuthorizationConsentService * @see OAuth2AuthorizationEndpointFilter - * @see OAuth2TokenEndpointFilter * @see OAuth2TokenIntrospectionEndpointFilter * @see OAuth2TokenRevocationEndpointFilter * @see NimbusJwkSetEndpointFilter @@ -84,8 +80,8 @@ import org.springframework.util.StringUtils; public final class OAuth2AuthorizationServerConfigurer> extends AbstractHttpConfigurer, B> { + private final Map, AbstractOAuth2Configurer> configurers = createConfigurers(); private RequestMatcher authorizationEndpointMatcher; - private RequestMatcher tokenEndpointMatcher; private RequestMatcher tokenIntrospectionEndpointMatcher; private RequestMatcher tokenRevocationEndpointMatcher; private RequestMatcher jwkSetEndpointMatcher; @@ -94,7 +90,7 @@ public final class OAuth2AuthorizationServerConfigurer this.authorizationEndpointMatcher.matches(request) || - this.tokenEndpointMatcher.matches(request) || + getRequestMatcher(OAuth2TokenEndpointConfigurer.class).matches(request) || this.tokenIntrospectionEndpointMatcher.matches(request) || this.tokenRevocationEndpointMatcher.matches(request) || this.jwkSetEndpointMatcher.matches(request) || @@ -187,6 +183,17 @@ public final class OAuth2AuthorizationServerConfigurer tokenEndpoint(Customizer tokenEndpointCustomizer) { + tokenEndpointCustomizer.customize(getConfigurer(OAuth2TokenEndpointConfigurer.class)); + return this; + } + /** * Returns a {@link RequestMatcher} for the authorization server endpoints. * @@ -202,6 +209,8 @@ public final class OAuth2AuthorizationServerConfigurer configurer.init(builder)); + OAuth2ClientAuthenticationProvider clientAuthenticationProvider = new OAuth2ClientAuthenticationProvider( OAuth2ConfigurerUtils.getRegisteredClientRepository(builder), @@ -212,36 +221,6 @@ public final class OAuth2AuthorizationServerConfigurer jwtCustomizer = OAuth2ConfigurerUtils.getJwtCustomizer(builder); - - OAuth2AuthorizationCodeAuthenticationProvider authorizationCodeAuthenticationProvider = - new OAuth2AuthorizationCodeAuthenticationProvider( - OAuth2ConfigurerUtils.getAuthorizationService(builder), - jwtEncoder); - if (jwtCustomizer != null) { - authorizationCodeAuthenticationProvider.setJwtCustomizer(jwtCustomizer); - } - builder.authenticationProvider(postProcess(authorizationCodeAuthenticationProvider)); - - OAuth2RefreshTokenAuthenticationProvider refreshTokenAuthenticationProvider = - new OAuth2RefreshTokenAuthenticationProvider( - OAuth2ConfigurerUtils.getAuthorizationService(builder), - jwtEncoder); - if (jwtCustomizer != null) { - refreshTokenAuthenticationProvider.setJwtCustomizer(jwtCustomizer); - } - builder.authenticationProvider(postProcess(refreshTokenAuthenticationProvider)); - - OAuth2ClientCredentialsAuthenticationProvider clientCredentialsAuthenticationProvider = - new OAuth2ClientCredentialsAuthenticationProvider( - OAuth2ConfigurerUtils.getAuthorizationService(builder), - jwtEncoder); - if (jwtCustomizer != null) { - clientCredentialsAuthenticationProvider.setJwtCustomizer(jwtCustomizer); - } - builder.authenticationProvider(postProcess(clientCredentialsAuthenticationProvider)); - OAuth2TokenIntrospectionAuthenticationProvider tokenIntrospectionAuthenticationProvider = new OAuth2TokenIntrospectionAuthenticationProvider( OAuth2ConfigurerUtils.getRegisteredClientRepository(builder), @@ -265,7 +244,7 @@ public final class OAuth2AuthorizationServerConfigurer configurer.configure(builder)); + ProviderSettings providerSettings = OAuth2ConfigurerUtils.getProviderSettings(builder); if (providerSettings.issuer() != null) { OidcProviderConfigurationEndpointFilter oidcProviderConfigurationEndpointFilter = @@ -297,7 +278,7 @@ public final class OAuth2AuthorizationServerConfigurer, AbstractOAuth2Configurer> createConfigurers() { + Map, AbstractOAuth2Configurer> configurers = new LinkedHashMap<>(); + configurers.put(OAuth2TokenEndpointConfigurer.class, new OAuth2TokenEndpointConfigurer(this::postProcess)); + return configurers; + } + + @SuppressWarnings("unchecked") + private T getConfigurer(Class type) { + return (T) this.configurers.get(type); + } + + private RequestMatcher getRequestMatcher(Class configurerType) { + return getConfigurer(configurerType).getRequestMatcher(); + } + private void initEndpointMatchers(ProviderSettings providerSettings) { this.authorizationEndpointMatcher = new OrRequestMatcher( new AntPathRequestMatcher( @@ -347,8 +337,6 @@ public final class OAuth2AuthorizationServerConfigurer authenticationProviders = new LinkedList<>(); + private AuthenticationSuccessHandler accessTokenResponseHandler; + private AuthenticationFailureHandler errorResponseHandler; + + /** + * Restrict for internal use only. + */ + OAuth2TokenEndpointConfigurer(ObjectPostProcessor objectPostProcessor) { + super(objectPostProcessor); + } + + /** + * Sets the {@link AuthenticationConverter} used when attempting to extract an Access Token Request from {@link HttpServletRequest} + * to an instance of {@link OAuth2AuthorizationGrantAuthenticationToken} used for authenticating the authorization grant. + * + * @param accessTokenRequestConverter the {@link AuthenticationConverter} used when attempting to extract an Access Token Request from {@link HttpServletRequest} + * @return the {@link OAuth2TokenEndpointConfigurer} for further configuration + */ + public OAuth2TokenEndpointConfigurer accessTokenRequestConverter(AuthenticationConverter accessTokenRequestConverter) { + this.accessTokenRequestConverter = accessTokenRequestConverter; + return this; + } + + /** + * Adds an {@link AuthenticationProvider} used for authenticating a type of {@link OAuth2AuthorizationGrantAuthenticationToken}. + * + * @param authenticationProvider an {@link AuthenticationProvider} used for authenticating a type of {@link OAuth2AuthorizationGrantAuthenticationToken} + * @return the {@link OAuth2TokenEndpointConfigurer} for further configuration + */ + public OAuth2TokenEndpointConfigurer authenticationProvider(AuthenticationProvider authenticationProvider) { + this.authenticationProviders.add(authenticationProvider); + return this; + } + + /** + * Sets the {@link AuthenticationSuccessHandler} used for handling an {@link OAuth2AccessTokenAuthenticationToken} + * and returning the {@link OAuth2AccessTokenResponse Access Token Response}. + * + * @param accessTokenResponseHandler the {@link AuthenticationSuccessHandler} used for handling an {@link OAuth2AccessTokenAuthenticationToken} + * @return the {@link OAuth2TokenEndpointConfigurer} for further configuration + */ + public OAuth2TokenEndpointConfigurer accessTokenResponseHandler(AuthenticationSuccessHandler accessTokenResponseHandler) { + this.accessTokenResponseHandler = accessTokenResponseHandler; + return this; + } + + /** + * Sets the {@link AuthenticationFailureHandler} used for handling an {@link OAuth2AuthenticationException} + * and returning the {@link OAuth2Error Error Response}. + * + * @param errorResponseHandler the {@link AuthenticationFailureHandler} used for handling an {@link OAuth2AuthenticationException} + * @return the {@link OAuth2TokenEndpointConfigurer} for further configuration + */ + public OAuth2TokenEndpointConfigurer errorResponseHandler(AuthenticationFailureHandler errorResponseHandler) { + this.errorResponseHandler = errorResponseHandler; + return this; + } + + @Override + > void init(B builder) { + ProviderSettings providerSettings = OAuth2ConfigurerUtils.getProviderSettings(builder); + this.requestMatcher = new AntPathRequestMatcher( + providerSettings.tokenEndpoint(), HttpMethod.POST.name()); + + List authenticationProviders = + !this.authenticationProviders.isEmpty() ? + this.authenticationProviders : + createDefaultAuthenticationProviders(builder); + authenticationProviders.forEach(authenticationProvider -> + builder.authenticationProvider(postProcess(authenticationProvider))); + } + + @Override + > void configure(B builder) { + AuthenticationManager authenticationManager = builder.getSharedObject(AuthenticationManager.class); + ProviderSettings providerSettings = OAuth2ConfigurerUtils.getProviderSettings(builder); + + OAuth2TokenEndpointFilter tokenEndpointFilter = + new OAuth2TokenEndpointFilter( + authenticationManager, + providerSettings.tokenEndpoint()); + if (this.accessTokenRequestConverter != null) { + tokenEndpointFilter.setAuthenticationConverter(this.accessTokenRequestConverter); + } + if (this.accessTokenResponseHandler != null) { + tokenEndpointFilter.setAuthenticationSuccessHandler(this.accessTokenResponseHandler); + } + if (this.errorResponseHandler != null) { + tokenEndpointFilter.setAuthenticationFailureHandler(this.errorResponseHandler); + } + builder.addFilterAfter(postProcess(tokenEndpointFilter), FilterSecurityInterceptor.class); + } + + @Override + RequestMatcher getRequestMatcher() { + return this.requestMatcher; + } + + private > List createDefaultAuthenticationProviders(B builder) { + List authenticationProviders = new ArrayList<>(); + + JwtEncoder jwtEncoder = OAuth2ConfigurerUtils.getJwtEncoder(builder); + OAuth2TokenCustomizer jwtCustomizer = OAuth2ConfigurerUtils.getJwtCustomizer(builder); + + OAuth2AuthorizationCodeAuthenticationProvider authorizationCodeAuthenticationProvider = + new OAuth2AuthorizationCodeAuthenticationProvider( + OAuth2ConfigurerUtils.getAuthorizationService(builder), + jwtEncoder); + if (jwtCustomizer != null) { + authorizationCodeAuthenticationProvider.setJwtCustomizer(jwtCustomizer); + } + authenticationProviders.add(authorizationCodeAuthenticationProvider); + + OAuth2RefreshTokenAuthenticationProvider refreshTokenAuthenticationProvider = + new OAuth2RefreshTokenAuthenticationProvider( + OAuth2ConfigurerUtils.getAuthorizationService(builder), + jwtEncoder); + if (jwtCustomizer != null) { + refreshTokenAuthenticationProvider.setJwtCustomizer(jwtCustomizer); + } + authenticationProviders.add(refreshTokenAuthenticationProvider); + + OAuth2ClientCredentialsAuthenticationProvider clientCredentialsAuthenticationProvider = + new OAuth2ClientCredentialsAuthenticationProvider( + OAuth2ConfigurerUtils.getAuthorizationService(builder), + jwtEncoder); + if (jwtCustomizer != null) { + clientCredentialsAuthenticationProvider.setJwtCustomizer(jwtCustomizer); + } + authenticationProviders.add(clientCredentialsAuthenticationProvider); + + return authenticationProviders; + } + +} diff --git a/oauth2-authorization-server/src/test/java/org/springframework/security/config/annotation/web/configurers/oauth2/server/authorization/OAuth2ClientCredentialsGrantTests.java b/oauth2-authorization-server/src/test/java/org/springframework/security/config/annotation/web/configurers/oauth2/server/authorization/OAuth2ClientCredentialsGrantTests.java index 3663207d..3111b2c6 100644 --- a/oauth2-authorization-server/src/test/java/org/springframework/security/config/annotation/web/configurers/oauth2/server/authorization/OAuth2ClientCredentialsGrantTests.java +++ b/oauth2-authorization-server/src/test/java/org/springframework/security/config/annotation/web/configurers/oauth2/server/authorization/OAuth2ClientCredentialsGrantTests.java @@ -17,6 +17,8 @@ package org.springframework.security.config.annotation.web.configurers.oauth2.se import java.net.URLEncoder; import java.nio.charset.StandardCharsets; +import java.time.Duration; +import java.time.Instant; import java.util.Base64; import com.nimbusds.jose.jwk.JWKSet; @@ -31,21 +33,32 @@ 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.authentication.AuthenticationProvider; +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.OAuth2AuthorizationServerConfiguration; import org.springframework.security.config.test.SpringTestRule; import org.springframework.security.crypto.password.NoOpPasswordEncoder; import org.springframework.security.crypto.password.PasswordEncoder; import org.springframework.security.oauth2.core.AuthorizationGrantType; +import org.springframework.security.oauth2.core.OAuth2AccessToken; import org.springframework.security.oauth2.core.endpoint.OAuth2ParameterNames; import org.springframework.security.oauth2.jose.TestJwks; import org.springframework.security.oauth2.server.authorization.JwtEncodingContext; import org.springframework.security.oauth2.server.authorization.OAuth2AuthorizationService; import org.springframework.security.oauth2.server.authorization.OAuth2TokenCustomizer; +import org.springframework.security.oauth2.server.authorization.authentication.OAuth2AccessTokenAuthenticationToken; +import org.springframework.security.oauth2.server.authorization.authentication.OAuth2ClientAuthenticationToken; +import org.springframework.security.oauth2.server.authorization.authentication.OAuth2ClientCredentialsAuthenticationToken; 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.OAuth2TokenEndpointFilter; +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.request.MockMvcRequestBuilders; @@ -64,12 +77,17 @@ import static org.springframework.test.web.servlet.result.MockMvcResultMatchers. * Integration tests for the OAuth 2.0 Client Credentials Grant. * * @author Alexey Nesterov + * @author Joe Grandja */ public class OAuth2ClientCredentialsGrantTests { private static RegisteredClientRepository registeredClientRepository; private static OAuth2AuthorizationService authorizationService; private static JWKSource jwkSource; private static OAuth2TokenCustomizer jwtCustomizer; + private static AuthenticationConverter accessTokenRequestConverter; + private static AuthenticationProvider authenticationProvider; + private static AuthenticationSuccessHandler accessTokenResponseHandler; + private static AuthenticationFailureHandler errorResponseHandler; @Rule public final SpringTestRule spring = new SpringTestRule(); @@ -84,6 +102,10 @@ public class OAuth2ClientCredentialsGrantTests { JWKSet jwkSet = new JWKSet(TestJwks.DEFAULT_RSA_JWK); jwkSource = (jwkSelector, securityContext) -> jwkSelector.select(jwkSet); jwtCustomizer = mock(OAuth2TokenCustomizer.class); + accessTokenRequestConverter = mock(AuthenticationConverter.class); + authenticationProvider = mock(AuthenticationProvider.class); + accessTokenResponseHandler = mock(AuthenticationSuccessHandler.class); + errorResponseHandler = mock(AuthenticationFailureHandler.class); } @SuppressWarnings("unchecked") @@ -150,6 +172,38 @@ public class OAuth2ClientCredentialsGrantTests { verify(authorizationService).save(any()); } + @Test + public void requestWhenTokenEndpointCustomizedThenUsed() throws Exception { + this.spring.register(AuthorizationServerConfigurationCustomTokenEndpoint.class).autowire(); + + RegisteredClient registeredClient = TestRegisteredClients.registeredClient2().build(); + when(registeredClientRepository.findByClientId(eq(registeredClient.getClientId()))) + .thenReturn(registeredClient); + + OAuth2ClientAuthenticationToken clientPrincipal = new OAuth2ClientAuthenticationToken(registeredClient); + OAuth2ClientCredentialsAuthenticationToken clientCredentialsAuthentication = + new OAuth2ClientCredentialsAuthenticationToken(clientPrincipal, null, null); + when(accessTokenRequestConverter.convert(any())).thenReturn(clientCredentialsAuthentication); + + OAuth2AccessToken accessToken = new OAuth2AccessToken( + OAuth2AccessToken.TokenType.BEARER, "token", + Instant.now(), Instant.now().plus(Duration.ofHours(1))); + OAuth2AccessTokenAuthenticationToken accessTokenAuthentication = + new OAuth2AccessTokenAuthenticationToken(registeredClient, clientPrincipal, accessToken); + when(authenticationProvider.supports(eq(OAuth2ClientCredentialsAuthenticationToken.class))).thenReturn(true); + when(authenticationProvider.authenticate(any())).thenReturn(accessTokenAuthentication); + + this.mvc.perform(post(OAuth2TokenEndpointFilter.DEFAULT_TOKEN_ENDPOINT_URI) + .param(OAuth2ParameterNames.GRANT_TYPE, AuthorizationGrantType.CLIENT_CREDENTIALS.getValue()) + .header(HttpHeaders.AUTHORIZATION, "Basic " + encodeBasicAuth( + registeredClient.getClientId(), registeredClient.getClientSecret()))) + .andExpect(status().isOk()); + + verify(accessTokenRequestConverter).convert(any()); + verify(authenticationProvider).authenticate(eq(clientCredentialsAuthentication)); + verify(accessTokenResponseHandler).onAuthenticationSuccess(any(), any(), eq(accessTokenAuthentication)); + } + 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()); @@ -188,4 +242,33 @@ public class OAuth2ClientCredentialsGrantTests { } } + + @EnableWebSecurity + static class AuthorizationServerConfigurationCustomTokenEndpoint extends AuthorizationServerConfiguration { + // @formatter:off + @Bean + public SecurityFilterChain authorizationServerSecurityFilterChain(HttpSecurity http) throws Exception { + OAuth2AuthorizationServerConfigurer authorizationServerConfigurer = + new OAuth2AuthorizationServerConfigurer<>(); + authorizationServerConfigurer + .tokenEndpoint(tokenEndpoint -> + tokenEndpoint + .accessTokenRequestConverter(accessTokenRequestConverter) + .authenticationProvider(authenticationProvider) + .accessTokenResponseHandler(accessTokenResponseHandler) + .errorResponseHandler(errorResponseHandler)); + RequestMatcher endpointsMatcher = authorizationServerConfigurer.getEndpointsMatcher(); + + http + .requestMatcher(endpointsMatcher) + .authorizeRequests(authorizeRequests -> + authorizeRequests.anyRequest().authenticated() + ) + .csrf(csrf -> csrf.ignoringRequestMatchers(endpointsMatcher)) + .apply(authorizationServerConfigurer); + return http.build(); + } + // @formatter:on + } + }