소스 검색

Provide configuration for client authentication

Closes gh-380
Joe Grandja 4 년 전
부모
커밋
3d4df8807d

+ 17 - 27
oauth2-authorization-server/src/main/java/org/springframework/security/config/annotation/web/configurers/oauth2/server/authorization/OAuth2AuthorizationServerConfigurer.java

@@ -29,10 +29,8 @@ 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.server.authorization.OAuth2AuthorizationConsentService;
 import org.springframework.security.oauth2.server.authorization.OAuth2AuthorizationService;
-import org.springframework.security.oauth2.server.authorization.authentication.OAuth2ClientAuthenticationProvider;
 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;
@@ -42,7 +40,6 @@ import org.springframework.security.oauth2.server.authorization.oidc.web.OidcCli
 import org.springframework.security.oauth2.server.authorization.oidc.web.OidcProviderConfigurationEndpointFilter;
 import org.springframework.security.oauth2.server.authorization.web.NimbusJwkSetEndpointFilter;
 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.OAuth2TokenIntrospectionEndpointFilter;
 import org.springframework.security.oauth2.server.authorization.web.OAuth2TokenRevocationEndpointFilter;
 import org.springframework.security.web.access.intercept.FilterSecurityInterceptor;
@@ -62,6 +59,7 @@ import org.springframework.util.Assert;
  * @author Ovidiu Popa
  * @since 0.0.1
  * @see AbstractHttpConfigurer
+ * @see OAuth2ClientAuthenticationConfigurer
  * @see OAuth2AuthorizationEndpointConfigurer
  * @see OAuth2TokenEndpointConfigurer
  * @see RegisteredClientRepository
@@ -72,7 +70,6 @@ import org.springframework.util.Assert;
  * @see NimbusJwkSetEndpointFilter
  * @see OidcProviderConfigurationEndpointFilter
  * @see OAuth2AuthorizationServerMetadataEndpointFilter
- * @see OAuth2ClientAuthenticationFilter
  * @see OidcClientRegistrationEndpointFilter
  */
 public final class OAuth2AuthorizationServerConfigurer<B extends HttpSecurityBuilder<B>>
@@ -103,7 +100,7 @@ public final class OAuth2AuthorizationServerConfigurer<B extends HttpSecurityBui
 	 */
 	public OAuth2AuthorizationServerConfigurer<B> registeredClientRepository(RegisteredClientRepository registeredClientRepository) {
 		Assert.notNull(registeredClientRepository, "registeredClientRepository cannot be null");
-		this.getBuilder().setSharedObject(RegisteredClientRepository.class, registeredClientRepository);
+		getBuilder().setSharedObject(RegisteredClientRepository.class, registeredClientRepository);
 		return this;
 	}
 
@@ -115,7 +112,7 @@ public final class OAuth2AuthorizationServerConfigurer<B extends HttpSecurityBui
 	 */
 	public OAuth2AuthorizationServerConfigurer<B> authorizationService(OAuth2AuthorizationService authorizationService) {
 		Assert.notNull(authorizationService, "authorizationService cannot be null");
-		this.getBuilder().setSharedObject(OAuth2AuthorizationService.class, authorizationService);
+		getBuilder().setSharedObject(OAuth2AuthorizationService.class, authorizationService);
 		return this;
 	}
 
@@ -127,7 +124,7 @@ public final class OAuth2AuthorizationServerConfigurer<B extends HttpSecurityBui
 	 */
 	public OAuth2AuthorizationServerConfigurer<B> authorizationConsentService(OAuth2AuthorizationConsentService authorizationConsentService) {
 		Assert.notNull(authorizationConsentService, "authorizationConsentService cannot be null");
-		this.getBuilder().setSharedObject(OAuth2AuthorizationConsentService.class, authorizationConsentService);
+		getBuilder().setSharedObject(OAuth2AuthorizationConsentService.class, authorizationConsentService);
 		return this;
 	}
 
@@ -139,7 +136,18 @@ public final class OAuth2AuthorizationServerConfigurer<B extends HttpSecurityBui
 	 */
 	public OAuth2AuthorizationServerConfigurer<B> providerSettings(ProviderSettings providerSettings) {
 		Assert.notNull(providerSettings, "providerSettings cannot be null");
-		this.getBuilder().setSharedObject(ProviderSettings.class, providerSettings);
+		getBuilder().setSharedObject(ProviderSettings.class, providerSettings);
+		return this;
+	}
+
+	/**
+	 * Configures OAuth 2.0 Client Authentication.
+	 *
+	 * @param clientAuthenticationCustomizer the {@link Customizer} providing access to the {@link OAuth2ClientAuthenticationConfigurer}
+	 * @return the {@link OAuth2AuthorizationServerConfigurer} for further configuration
+	 */
+	public OAuth2AuthorizationServerConfigurer<B> clientAuthentication(Customizer<OAuth2ClientAuthenticationConfigurer> clientAuthenticationCustomizer) {
+		clientAuthenticationCustomizer.customize(getConfigurer(OAuth2ClientAuthenticationConfigurer.class));
 		return this;
 	}
 
@@ -182,16 +190,6 @@ public final class OAuth2AuthorizationServerConfigurer<B extends HttpSecurityBui
 
 		this.configurers.values().forEach(configurer -> configurer.init(builder));
 
-		OAuth2ClientAuthenticationProvider clientAuthenticationProvider =
-				new OAuth2ClientAuthenticationProvider(
-						OAuth2ConfigurerUtils.getRegisteredClientRepository(builder),
-						OAuth2ConfigurerUtils.getAuthorizationService(builder));
-		PasswordEncoder passwordEncoder = OAuth2ConfigurerUtils.getOptionalBean(builder, PasswordEncoder.class);
-		if (passwordEncoder != null) {
-			clientAuthenticationProvider.setPasswordEncoder(passwordEncoder);
-		}
-		builder.authenticationProvider(postProcess(clientAuthenticationProvider));
-
 		OAuth2TokenIntrospectionAuthenticationProvider tokenIntrospectionAuthenticationProvider =
 				new OAuth2TokenIntrospectionAuthenticationProvider(
 						OAuth2ConfigurerUtils.getRegisteredClientRepository(builder),
@@ -245,15 +243,6 @@ public final class OAuth2AuthorizationServerConfigurer<B extends HttpSecurityBui
 
 		AuthenticationManager authenticationManager = builder.getSharedObject(AuthenticationManager.class);
 
-		OAuth2ClientAuthenticationFilter clientAuthenticationFilter =
-				new OAuth2ClientAuthenticationFilter(
-						authenticationManager,
-						new OrRequestMatcher(
-								getRequestMatcher(OAuth2TokenEndpointConfigurer.class),
-								this.tokenIntrospectionEndpointMatcher,
-								this.tokenRevocationEndpointMatcher));
-		builder.addFilterAfter(postProcess(clientAuthenticationFilter), AbstractPreAuthenticatedProcessingFilter.class);
-
 		OAuth2TokenIntrospectionEndpointFilter tokenIntrospectionEndpointFilter =
 				new OAuth2TokenIntrospectionEndpointFilter(
 						authenticationManager,
@@ -276,6 +265,7 @@ public final class OAuth2AuthorizationServerConfigurer<B extends HttpSecurityBui
 
 	private Map<Class<? extends AbstractOAuth2Configurer>, AbstractOAuth2Configurer> createConfigurers() {
 		Map<Class<? extends AbstractOAuth2Configurer>, AbstractOAuth2Configurer> configurers = new LinkedHashMap<>();
+		configurers.put(OAuth2ClientAuthenticationConfigurer.class, new OAuth2ClientAuthenticationConfigurer(this::postProcess));
 		configurers.put(OAuth2AuthorizationEndpointConfigurer.class, new OAuth2AuthorizationEndpointConfigurer(this::postProcess));
 		configurers.put(OAuth2TokenEndpointConfigurer.class, new OAuth2TokenEndpointConfigurer(this::postProcess));
 		return configurers;

+ 172 - 0
oauth2-authorization-server/src/main/java/org/springframework/security/config/annotation/web/configurers/oauth2/server/authorization/OAuth2ClientAuthenticationConfigurer.java

@@ -0,0 +1,172 @@
+/*
+ * 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 java.util.ArrayList;
+import java.util.List;
+
+import javax.servlet.http.HttpServletRequest;
+
+import org.springframework.http.HttpMethod;
+import org.springframework.security.authentication.AuthenticationManager;
+import org.springframework.security.authentication.AuthenticationProvider;
+import org.springframework.security.config.annotation.ObjectPostProcessor;
+import org.springframework.security.config.annotation.web.HttpSecurityBuilder;
+import org.springframework.security.core.context.SecurityContext;
+import org.springframework.security.crypto.password.PasswordEncoder;
+import org.springframework.security.oauth2.core.OAuth2Error;
+import org.springframework.security.oauth2.server.authorization.authentication.OAuth2ClientAuthenticationProvider;
+import org.springframework.security.oauth2.server.authorization.authentication.OAuth2ClientAuthenticationToken;
+import org.springframework.security.oauth2.server.authorization.config.ProviderSettings;
+import org.springframework.security.oauth2.server.authorization.web.OAuth2ClientAuthenticationFilter;
+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.authentication.preauth.AbstractPreAuthenticatedProcessingFilter;
+import org.springframework.security.web.util.matcher.AntPathRequestMatcher;
+import org.springframework.security.web.util.matcher.OrRequestMatcher;
+import org.springframework.security.web.util.matcher.RequestMatcher;
+
+/**
+ * Configurer for OAuth 2.0 Client Authentication.
+ *
+ * @author Joe Grandja
+ * @since 0.2.0
+ * @see OAuth2AuthorizationServerConfigurer#clientAuthentication
+ * @see OAuth2ClientAuthenticationFilter
+ */
+public final class OAuth2ClientAuthenticationConfigurer extends AbstractOAuth2Configurer {
+	private RequestMatcher requestMatcher;
+	private AuthenticationConverter authenticationConverter;
+	private final List<AuthenticationProvider> authenticationProviders = new ArrayList<>();
+	private AuthenticationSuccessHandler authenticationSuccessHandler;
+	private AuthenticationFailureHandler errorResponseHandler;
+
+	/**
+	 * Restrict for internal use only.
+	 */
+	OAuth2ClientAuthenticationConfigurer(ObjectPostProcessor<Object> objectPostProcessor) {
+		super(objectPostProcessor);
+	}
+
+	/**
+	 * Sets the {@link AuthenticationConverter} used when attempting to extract client credentials from {@link HttpServletRequest}
+	 * to an instance of {@link OAuth2ClientAuthenticationToken} used for authenticating the client.
+	 *
+	 * @param authenticationConverter the {@link AuthenticationConverter} used when attempting to extract client credentials from {@link HttpServletRequest}
+	 * @return the {@link OAuth2ClientAuthenticationConfigurer} for further configuration
+	 */
+	public OAuth2ClientAuthenticationConfigurer authenticationConverter(AuthenticationConverter authenticationConverter) {
+		this.authenticationConverter = authenticationConverter;
+		return this;
+	}
+
+	/**
+	 * Adds an {@link AuthenticationProvider} used for authenticating an {@link OAuth2ClientAuthenticationToken}.
+	 *
+	 * @param authenticationProvider an {@link AuthenticationProvider} used for authenticating an {@link OAuth2ClientAuthenticationToken}
+	 * @return the {@link OAuth2ClientAuthenticationConfigurer} for further configuration
+	 */
+	public OAuth2ClientAuthenticationConfigurer authenticationProvider(AuthenticationProvider authenticationProvider) {
+		this.authenticationProviders.add(authenticationProvider);
+		return this;
+	}
+
+	/**
+	 * Sets the {@link AuthenticationSuccessHandler} used for handling a successful client authentication
+	 * and associating the {@link OAuth2ClientAuthenticationToken} to the {@link SecurityContext}.
+	 *
+	 * @param authenticationSuccessHandler the {@link AuthenticationSuccessHandler} used for handling a successful client authentication
+	 * @return the {@link OAuth2ClientAuthenticationConfigurer} for further configuration
+	 */
+	public OAuth2ClientAuthenticationConfigurer authenticationSuccessHandler(AuthenticationSuccessHandler authenticationSuccessHandler) {
+		this.authenticationSuccessHandler = authenticationSuccessHandler;
+		return this;
+	}
+
+	/**
+	 * Sets the {@link AuthenticationFailureHandler} used for handling a failed client authentication
+	 * and returning the {@link OAuth2Error Error Response}.
+	 *
+	 * @param errorResponseHandler the {@link AuthenticationFailureHandler} used for handling a failed client authentication
+	 * @return the {@link OAuth2ClientAuthenticationConfigurer} for further configuration
+	 */
+	public OAuth2ClientAuthenticationConfigurer errorResponseHandler(AuthenticationFailureHandler errorResponseHandler) {
+		this.errorResponseHandler = errorResponseHandler;
+		return this;
+	}
+
+	@Override
+	<B extends HttpSecurityBuilder<B>> void init(B builder) {
+		ProviderSettings providerSettings = OAuth2ConfigurerUtils.getProviderSettings(builder);
+		this.requestMatcher = new OrRequestMatcher(
+				new AntPathRequestMatcher(
+						providerSettings.getTokenEndpoint(),
+						HttpMethod.POST.name()),
+				new AntPathRequestMatcher(
+						providerSettings.getTokenIntrospectionEndpoint(),
+						HttpMethod.POST.name()),
+				new AntPathRequestMatcher(
+						providerSettings.getTokenRevocationEndpoint(),
+						HttpMethod.POST.name()));
+
+		List<AuthenticationProvider> authenticationProviders =
+				!this.authenticationProviders.isEmpty() ?
+						this.authenticationProviders :
+						createDefaultAuthenticationProviders(builder);
+		authenticationProviders.forEach(authenticationProvider ->
+				builder.authenticationProvider(postProcess(authenticationProvider)));
+	}
+
+	@Override
+	<B extends HttpSecurityBuilder<B>> void configure(B builder) {
+		AuthenticationManager authenticationManager = builder.getSharedObject(AuthenticationManager.class);
+		OAuth2ClientAuthenticationFilter clientAuthenticationFilter = new OAuth2ClientAuthenticationFilter(
+				authenticationManager, this.requestMatcher);
+		if (this.authenticationConverter != null) {
+			clientAuthenticationFilter.setAuthenticationConverter(this.authenticationConverter);
+		}
+		if (this.authenticationSuccessHandler != null) {
+			clientAuthenticationFilter.setAuthenticationSuccessHandler(this.authenticationSuccessHandler);
+		}
+		if (this.errorResponseHandler != null) {
+			clientAuthenticationFilter.setAuthenticationFailureHandler(this.errorResponseHandler);
+		}
+		builder.addFilterAfter(postProcess(clientAuthenticationFilter), AbstractPreAuthenticatedProcessingFilter.class);
+	}
+
+	@Override
+	RequestMatcher getRequestMatcher() {
+		return this.requestMatcher;
+	}
+
+	private <B extends HttpSecurityBuilder<B>> List<AuthenticationProvider> createDefaultAuthenticationProviders(B builder) {
+		List<AuthenticationProvider> authenticationProviders = new ArrayList<>();
+
+		OAuth2ClientAuthenticationProvider clientAuthenticationProvider =
+				new OAuth2ClientAuthenticationProvider(
+						OAuth2ConfigurerUtils.getRegisteredClientRepository(builder),
+						OAuth2ConfigurerUtils.getAuthorizationService(builder));
+		PasswordEncoder passwordEncoder = OAuth2ConfigurerUtils.getOptionalBean(builder, PasswordEncoder.class);
+		if (passwordEncoder != null) {
+			clientAuthenticationProvider.setPasswordEncoder(passwordEncoder);
+		}
+		authenticationProviders.add(clientAuthenticationProvider);
+
+		return authenticationProviders;
+	}
+
+}

+ 89 - 12
oauth2-authorization-server/src/test/java/org/springframework/security/config/annotation/web/configurers/oauth2/server/authorization/OAuth2ClientCredentialsGrantTests.java

@@ -15,12 +15,17 @@
  */
 package org.springframework.security.config.annotation.web.configurers.oauth2.server.authorization;
 
+import java.io.IOException;
 import java.net.URLEncoder;
 import java.nio.charset.StandardCharsets;
 import java.time.Duration;
 import java.time.Instant;
 import java.util.Base64;
 
+import javax.servlet.ServletException;
+import javax.servlet.http.HttpServletRequest;
+import javax.servlet.http.HttpServletResponse;
+
 import com.nimbusds.jose.jwk.JWKSet;
 import com.nimbusds.jose.jwk.source.JWKSource;
 import com.nimbusds.jose.proc.SecurityContext;
@@ -46,6 +51,8 @@ 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.core.Authentication;
+import org.springframework.security.core.context.SecurityContextHolder;
 import org.springframework.security.crypto.password.NoOpPasswordEncoder;
 import org.springframework.security.crypto.password.PasswordEncoder;
 import org.springframework.security.oauth2.core.AuthorizationGrantType;
@@ -76,6 +83,7 @@ import static org.mockito.ArgumentMatchers.any;
 import static org.mockito.ArgumentMatchers.eq;
 import static org.mockito.Mockito.mock;
 import static org.mockito.Mockito.reset;
+import static org.mockito.Mockito.spy;
 import static org.mockito.Mockito.verify;
 import static org.mockito.Mockito.when;
 import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.post;
@@ -93,10 +101,10 @@ public class OAuth2ClientCredentialsGrantTests {
 	private static EmbeddedDatabase db;
 	private static JWKSource<SecurityContext> jwkSource;
 	private static OAuth2TokenCustomizer<JwtEncodingContext> jwtCustomizer;
-	private static AuthenticationConverter accessTokenRequestConverter;
+	private static AuthenticationConverter authenticationConverter;
 	private static AuthenticationProvider authenticationProvider;
-	private static AuthenticationSuccessHandler accessTokenResponseHandler;
-	private static AuthenticationFailureHandler errorResponseHandler;
+	private static AuthenticationSuccessHandler authenticationSuccessHandler;
+	private static AuthenticationFailureHandler authenticationFailureHandler;
 
 	@Rule
 	public final SpringTestRule spring = new SpringTestRule();
@@ -115,10 +123,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);
+		authenticationConverter = mock(AuthenticationConverter.class);
 		authenticationProvider = mock(AuthenticationProvider.class);
-		accessTokenResponseHandler = mock(AuthenticationSuccessHandler.class);
-		errorResponseHandler = mock(AuthenticationFailureHandler.class);
+		authenticationSuccessHandler = mock(AuthenticationSuccessHandler.class);
+		authenticationFailureHandler = mock(AuthenticationFailureHandler.class);
 		db = new EmbeddedDatabaseBuilder()
 				.generateUniqueName(true)
 				.setType(EmbeddedDatabaseType.HSQL)
@@ -132,6 +140,10 @@ public class OAuth2ClientCredentialsGrantTests {
 	@Before
 	public void setup() {
 		reset(jwtCustomizer);
+		reset(authenticationConverter);
+		reset(authenticationProvider);
+		reset(authenticationSuccessHandler);
+		reset(authenticationFailureHandler);
 	}
 
 	@After
@@ -202,7 +214,7 @@ public class OAuth2ClientCredentialsGrantTests {
 		OAuth2ClientAuthenticationToken clientPrincipal = new OAuth2ClientAuthenticationToken(registeredClient);
 		OAuth2ClientCredentialsAuthenticationToken clientCredentialsAuthentication =
 				new OAuth2ClientCredentialsAuthenticationToken(clientPrincipal, null, null);
-		when(accessTokenRequestConverter.convert(any())).thenReturn(clientCredentialsAuthentication);
+		when(authenticationConverter.convert(any())).thenReturn(clientCredentialsAuthentication);
 
 		OAuth2AccessToken accessToken = new OAuth2AccessToken(
 				OAuth2AccessToken.TokenType.BEARER, "token",
@@ -218,9 +230,32 @@ public class OAuth2ClientCredentialsGrantTests {
 						registeredClient.getClientId(), registeredClient.getClientSecret())))
 				.andExpect(status().isOk());
 
-		verify(accessTokenRequestConverter).convert(any());
+		verify(authenticationConverter).convert(any());
 		verify(authenticationProvider).authenticate(eq(clientCredentialsAuthentication));
-		verify(accessTokenResponseHandler).onAuthenticationSuccess(any(), any(), eq(accessTokenAuthentication));
+		verify(authenticationSuccessHandler).onAuthenticationSuccess(any(), any(), eq(accessTokenAuthentication));
+	}
+
+	@Test
+	public void requestWhenClientAuthenticationCustomizedThenUsed() throws Exception {
+		this.spring.register(AuthorizationServerConfigurationCustomClientAuthentication.class).autowire();
+
+		RegisteredClient registeredClient = TestRegisteredClients.registeredClient2().build();
+		this.registeredClientRepository.save(registeredClient);
+
+		OAuth2ClientAuthenticationToken clientPrincipal = new OAuth2ClientAuthenticationToken(registeredClient);
+		when(authenticationConverter.convert(any())).thenReturn(clientPrincipal);
+		when(authenticationProvider.supports(eq(OAuth2ClientAuthenticationToken.class))).thenReturn(true);
+		when(authenticationProvider.authenticate(any())).thenReturn(clientPrincipal);
+
+		this.mvc.perform(post(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(authenticationConverter).convert(any());
+		verify(authenticationProvider).authenticate(eq(clientPrincipal));
+		verify(authenticationSuccessHandler).onAuthenticationSuccess(any(), any(), eq(clientPrincipal));
 	}
 
 	private static String encodeBasicAuth(String clientId, String secret) throws Exception {
@@ -298,10 +333,40 @@ public class OAuth2ClientCredentialsGrantTests {
 			authorizationServerConfigurer
 					.tokenEndpoint(tokenEndpoint ->
 							tokenEndpoint
-									.accessTokenRequestConverter(accessTokenRequestConverter)
+									.accessTokenRequestConverter(authenticationConverter)
+									.authenticationProvider(authenticationProvider)
+									.accessTokenResponseHandler(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
+	}
+
+	@EnableWebSecurity
+	static class AuthorizationServerConfigurationCustomClientAuthentication extends AuthorizationServerConfiguration {
+		// @formatter:off
+		@Bean
+		public SecurityFilterChain authorizationServerSecurityFilterChain(HttpSecurity http) throws Exception {
+			authenticationSuccessHandler = spy(authenticationSuccessHandler());
+
+			OAuth2AuthorizationServerConfigurer<HttpSecurity> authorizationServerConfigurer =
+					new OAuth2AuthorizationServerConfigurer<>();
+			authorizationServerConfigurer
+					.clientAuthentication(clientAuthentication ->
+							clientAuthentication
+									.authenticationConverter(authenticationConverter)
 									.authenticationProvider(authenticationProvider)
-									.accessTokenResponseHandler(accessTokenResponseHandler)
-									.errorResponseHandler(errorResponseHandler));
+									.authenticationSuccessHandler(authenticationSuccessHandler)
+									.errorResponseHandler(authenticationFailureHandler));
 			RequestMatcher endpointsMatcher = authorizationServerConfigurer.getEndpointsMatcher();
 
 			http
@@ -314,6 +379,18 @@ public class OAuth2ClientCredentialsGrantTests {
 			return http.build();
 		}
 		// @formatter:on
+
+		private AuthenticationSuccessHandler authenticationSuccessHandler() {
+			return new AuthenticationSuccessHandler() {
+				@Override
+				public void onAuthenticationSuccess(HttpServletRequest request, HttpServletResponse response, Authentication authentication) throws IOException, ServletException {
+					org.springframework.security.core.context.SecurityContext securityContext = SecurityContextHolder.createEmptyContext();
+					securityContext.setAuthentication(authentication);
+					SecurityContextHolder.setContext(securityContext);
+				}
+			};
+		}
+
 	}
 
 }