Jelajahi Sumber

Provide configuration for the authorization endpoint

Closes gh-342
Joe Grandja 4 tahun lalu
induk
melakukan
9def059e29

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

@@ -0,0 +1,209 @@
+/*
+ * 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.oauth2.core.OAuth2Error;
+import org.springframework.security.oauth2.core.endpoint.OAuth2AuthorizationResponse;
+import org.springframework.security.oauth2.server.authorization.OAuth2AuthorizationCodeRequestAuthenticationException;
+import org.springframework.security.oauth2.server.authorization.authentication.OAuth2AuthorizationCodeRequestAuthenticationProvider;
+import org.springframework.security.oauth2.server.authorization.authentication.OAuth2AuthorizationCodeRequestAuthenticationToken;
+import org.springframework.security.oauth2.server.authorization.config.ProviderSettings;
+import org.springframework.security.oauth2.server.authorization.web.OAuth2AuthorizationEndpointFilter;
+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;
+import org.springframework.util.StringUtils;
+
+/**
+ * Configurer for the OAuth 2.0 Authorization Endpoint.
+ *
+ * @author Joe Grandja
+ * @since 0.1.2
+ * @see OAuth2AuthorizationServerConfigurer#authorizationEndpoint
+ * @see OAuth2AuthorizationEndpointFilter
+ */
+public final class OAuth2AuthorizationEndpointConfigurer extends AbstractOAuth2Configurer {
+	private RequestMatcher requestMatcher;
+	private AuthenticationConverter authorizationRequestConverter;
+	private final List<AuthenticationProvider> authenticationProviders = new ArrayList<>();
+	private AuthenticationSuccessHandler authorizationResponseHandler;
+	private AuthenticationFailureHandler errorResponseHandler;
+	private String consentPage;
+
+	/**
+	 * Restrict for internal use only.
+	 */
+	OAuth2AuthorizationEndpointConfigurer(ObjectPostProcessor<Object> objectPostProcessor) {
+		super(objectPostProcessor);
+	}
+
+	/**
+	 * Sets the {@link AuthenticationConverter} used when attempting to extract an Authorization Request (or Consent) from {@link HttpServletRequest}
+	 * to an instance of {@link OAuth2AuthorizationCodeRequestAuthenticationToken} used for authenticating the request.
+	 *
+	 * @param authorizationRequestConverter the {@link AuthenticationConverter} used when attempting to extract an Authorization Request (or Consent) from {@link HttpServletRequest}
+	 * @return the {@link OAuth2AuthorizationEndpointConfigurer} for further configuration
+	 */
+	public OAuth2AuthorizationEndpointConfigurer authorizationRequestConverter(AuthenticationConverter authorizationRequestConverter) {
+		this.authorizationRequestConverter = authorizationRequestConverter;
+		return this;
+	}
+
+	/**
+	 * Adds an {@link AuthenticationProvider} used for authenticating an {@link OAuth2AuthorizationCodeRequestAuthenticationToken}.
+	 *
+	 * @param authenticationProvider an {@link AuthenticationProvider} used for authenticating an {@link OAuth2AuthorizationCodeRequestAuthenticationToken}
+	 * @return the {@link OAuth2AuthorizationEndpointConfigurer} for further configuration
+	 */
+	public OAuth2AuthorizationEndpointConfigurer authenticationProvider(AuthenticationProvider authenticationProvider) {
+		this.authenticationProviders.add(authenticationProvider);
+		return this;
+	}
+
+	/**
+	 * Sets the {@link AuthenticationSuccessHandler} used for handling an {@link OAuth2AuthorizationCodeRequestAuthenticationToken}
+	 * and returning the {@link OAuth2AuthorizationResponse Authorization Response}.
+	 *
+	 * @param authorizationResponseHandler the {@link AuthenticationSuccessHandler} used for handling an {@link OAuth2AuthorizationCodeRequestAuthenticationToken}
+	 * @return the {@link OAuth2AuthorizationEndpointConfigurer} for further configuration
+	 */
+	public OAuth2AuthorizationEndpointConfigurer authorizationResponseHandler(AuthenticationSuccessHandler authorizationResponseHandler) {
+		this.authorizationResponseHandler = authorizationResponseHandler;
+		return this;
+	}
+
+	/**
+	 * Sets the {@link AuthenticationFailureHandler} used for handling an {@link OAuth2AuthorizationCodeRequestAuthenticationException}
+	 * and returning the {@link OAuth2Error Error Response}.
+	 *
+	 * @param errorResponseHandler the {@link AuthenticationFailureHandler} used for handling an {@link OAuth2AuthorizationCodeRequestAuthenticationException}
+	 * @return the {@link OAuth2AuthorizationEndpointConfigurer} for further configuration
+	 */
+	public OAuth2AuthorizationEndpointConfigurer errorResponseHandler(AuthenticationFailureHandler errorResponseHandler) {
+		this.errorResponseHandler = errorResponseHandler;
+		return this;
+	}
+
+	/**
+	 * Specify the URI to redirect Resource Owners to if consent is required during
+	 * the {@code authorization_code} flow. A default consent page will be generated when
+	 * this attribute is not specified.
+	 *
+	 * If a URI is specified, applications are required to process the specified URI to generate
+	 * a consent page. The query string will contain the following parameters:
+	 *
+	 * <ul>
+	 * <li>{@code client_id} - the client identifier</li>
+	 * <li>{@code scope} - a space-delimited list of scopes present in the authorization request</li>
+	 * <li>{@code state} - a CSRF protection token</li>
+	 * </ul>
+	 *
+	 * In general, the consent page should create a form that submits
+	 * a request with the following requirements:
+	 *
+	 * <ul>
+	 * <li>It must be an HTTP POST</li>
+	 * <li>It must be submitted to {@link ProviderSettings#authorizationEndpoint()}</li>
+	 * <li>It must include the received {@code client_id} as an HTTP parameter</li>
+	 * <li>It must include the received {@code state} as an HTTP parameter</li>
+	 * <li>It must include the list of {@code scope}s the {@code Resource Owner}
+	 * consented to as an HTTP parameter</li>
+	 * </ul>
+	 *
+	 * @param consentPage the URI of the custom consent page to redirect to if consent is required (e.g. "/oauth2/consent")
+	 * @return the {@link OAuth2AuthorizationEndpointConfigurer} for further configuration
+	 */
+	public OAuth2AuthorizationEndpointConfigurer consentPage(String consentPage) {
+		this.consentPage = consentPage;
+		return this;
+	}
+
+	@Override
+	<B extends HttpSecurityBuilder<B>> void init(B builder) {
+		ProviderSettings providerSettings = OAuth2ConfigurerUtils.getProviderSettings(builder);
+		this.requestMatcher = new OrRequestMatcher(
+				new AntPathRequestMatcher(
+						providerSettings.authorizationEndpoint(),
+						HttpMethod.GET.name()),
+				new AntPathRequestMatcher(
+						providerSettings.authorizationEndpoint(),
+						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);
+		ProviderSettings providerSettings = OAuth2ConfigurerUtils.getProviderSettings(builder);
+
+		OAuth2AuthorizationEndpointFilter authorizationEndpointFilter =
+				new OAuth2AuthorizationEndpointFilter(
+						authenticationManager,
+						providerSettings.authorizationEndpoint());
+		if (this.authorizationRequestConverter != null) {
+			authorizationEndpointFilter.setAuthenticationConverter(this.authorizationRequestConverter);
+		}
+		if (this.authorizationResponseHandler != null) {
+			authorizationEndpointFilter.setAuthenticationSuccessHandler(this.authorizationResponseHandler);
+		}
+		if (this.errorResponseHandler != null) {
+			authorizationEndpointFilter.setAuthenticationFailureHandler(this.errorResponseHandler);
+		}
+		if (StringUtils.hasText(this.consentPage)) {
+			authorizationEndpointFilter.setConsentPage(this.consentPage);
+		}
+		builder.addFilterBefore(postProcess(authorizationEndpointFilter), AbstractPreAuthenticatedProcessingFilter.class);
+	}
+
+	@Override
+	RequestMatcher getRequestMatcher() {
+		return this.requestMatcher;
+	}
+
+	private <B extends HttpSecurityBuilder<B>> List<AuthenticationProvider> createDefaultAuthenticationProviders(B builder) {
+		List<AuthenticationProvider> authenticationProviders = new ArrayList<>();
+
+		OAuth2AuthorizationCodeRequestAuthenticationProvider authorizationCodeRequestAuthenticationProvider =
+				new OAuth2AuthorizationCodeRequestAuthenticationProvider(
+						OAuth2ConfigurerUtils.getRegisteredClientRepository(builder),
+						OAuth2ConfigurerUtils.getAuthorizationService(builder),
+						OAuth2ConfigurerUtils.getAuthorizationConsentService(builder));
+		authenticationProviders.add(authorizationCodeRequestAuthenticationProvider);
+
+		return authenticationProviders;
+	}
+
+}

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

@@ -32,7 +32,6 @@ import org.springframework.security.config.annotation.web.configurers.ExceptionH
 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.OAuth2AuthorizationCodeRequestAuthenticationProvider;
 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;
@@ -42,7 +41,6 @@ import org.springframework.security.oauth2.server.authorization.oidc.authenticat
 import org.springframework.security.oauth2.server.authorization.oidc.web.OidcClientRegistrationEndpointFilter;
 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.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.OAuth2TokenIntrospectionEndpointFilter;
@@ -54,7 +52,6 @@ import org.springframework.security.web.util.matcher.AntPathRequestMatcher;
 import org.springframework.security.web.util.matcher.OrRequestMatcher;
 import org.springframework.security.web.util.matcher.RequestMatcher;
 import org.springframework.util.Assert;
-import org.springframework.util.StringUtils;
 
 /**
  * An {@link AbstractHttpConfigurer} for OAuth 2.0 Authorization Server support.
@@ -65,11 +62,11 @@ import org.springframework.util.StringUtils;
  * @author Ovidiu Popa
  * @since 0.0.1
  * @see AbstractHttpConfigurer
+ * @see OAuth2AuthorizationEndpointConfigurer
  * @see OAuth2TokenEndpointConfigurer
  * @see RegisteredClientRepository
  * @see OAuth2AuthorizationService
  * @see OAuth2AuthorizationConsentService
- * @see OAuth2AuthorizationEndpointFilter
  * @see OAuth2TokenIntrospectionEndpointFilter
  * @see OAuth2TokenRevocationEndpointFilter
  * @see NimbusJwkSetEndpointFilter
@@ -82,7 +79,6 @@ public final class OAuth2AuthorizationServerConfigurer<B extends HttpSecurityBui
 		extends AbstractHttpConfigurer<OAuth2AuthorizationServerConfigurer<B>, B> {
 
 	private final Map<Class<? extends AbstractOAuth2Configurer>, AbstractOAuth2Configurer> configurers = createConfigurers();
-	private RequestMatcher authorizationEndpointMatcher;
 	private RequestMatcher tokenIntrospectionEndpointMatcher;
 	private RequestMatcher tokenRevocationEndpointMatcher;
 	private RequestMatcher jwkSetEndpointMatcher;
@@ -90,7 +86,7 @@ public final class OAuth2AuthorizationServerConfigurer<B extends HttpSecurityBui
 	private RequestMatcher authorizationServerMetadataEndpointMatcher;
 	private RequestMatcher oidcClientRegistrationEndpointMatcher;
 	private final RequestMatcher endpointsMatcher = (request) ->
-			this.authorizationEndpointMatcher.matches(request) ||
+			getRequestMatcher(OAuth2AuthorizationEndpointConfigurer.class).matches(request) ||
 			getRequestMatcher(OAuth2TokenEndpointConfigurer.class).matches(request) ||
 			this.tokenIntrospectionEndpointMatcher.matches(request) ||
 			this.tokenRevocationEndpointMatcher.matches(request) ||
@@ -98,7 +94,6 @@ public final class OAuth2AuthorizationServerConfigurer<B extends HttpSecurityBui
 			this.oidcProviderConfigurationEndpointMatcher.matches(request) ||
 			this.authorizationServerMetadataEndpointMatcher.matches(request) ||
 			this.oidcClientRegistrationEndpointMatcher.matches(request);
-	private String consentPage;
 
 	/**
 	 * Sets the repository of registered clients.
@@ -149,36 +144,13 @@ public final class OAuth2AuthorizationServerConfigurer<B extends HttpSecurityBui
 	}
 
 	/**
-	 * Specify the URI to redirect Resource Owners to if consent is required during
-	 * the {@code authorization_code} flow. A default consent page will be generated when
-	 * this attribute is not specified.
+	 * Configures the OAuth 2.0 Authorization Endpoint.
 	 *
-	 * If a URI is specified, applications are required to process the specified URI to generate
-	 * a consent page. The query string will contain the following parameters:
-	 *
-	 * <ul>
-	 * <li>{@code client_id} - the client identifier</li>
-	 * <li>{@code scope} - a space-delimited list of scopes present in the authorization request</li>
-	 * <li>{@code state} - a CSRF protection token</li>
-	 * </ul>
-	 *
-	 * In general, the consent page should create a form that submits
-	 * a request with the following requirements:
-	 *
-	 * <ul>
-	 * <li>It must be an HTTP POST</li>
-	 * <li>It must be submitted to {@link ProviderSettings#authorizationEndpoint()}</li>
-	 * <li>It must include the received {@code client_id} as an HTTP parameter</li>
-	 * <li>It must include the received {@code state} as an HTTP parameter</li>
-	 * <li>It must include the list of {@code scope}s the {@code Resource Owner}
-	 * consented to as an HTTP parameter</li>
-	 * </ul>
-	 *
-	 * @param consentPage the URI of the custom consent page to redirect to if consent is required (e.g. "/oauth2/consent")
+	 * @param authorizationEndpointCustomizer the {@link Customizer} providing access to the {@link OAuth2AuthorizationEndpointConfigurer}
 	 * @return the {@link OAuth2AuthorizationServerConfigurer} for further configuration
 	 */
-	public OAuth2AuthorizationServerConfigurer<B> consentPage(String consentPage) {
-		this.consentPage = consentPage;
+	public OAuth2AuthorizationServerConfigurer<B> authorizationEndpoint(Customizer<OAuth2AuthorizationEndpointConfigurer> authorizationEndpointCustomizer) {
+		authorizationEndpointCustomizer.customize(getConfigurer(OAuth2AuthorizationEndpointConfigurer.class));
 		return this;
 	}
 
@@ -220,13 +192,6 @@ public final class OAuth2AuthorizationServerConfigurer<B extends HttpSecurityBui
 		}
 		builder.authenticationProvider(postProcess(clientAuthenticationProvider));
 
-		OAuth2AuthorizationCodeRequestAuthenticationProvider authorizationCodeRequestAuthenticationProvider =
-				new OAuth2AuthorizationCodeRequestAuthenticationProvider(
-						OAuth2ConfigurerUtils.getRegisteredClientRepository(builder),
-						OAuth2ConfigurerUtils.getAuthorizationService(builder),
-						OAuth2ConfigurerUtils.getAuthorizationConsentService(builder));
-		builder.authenticationProvider(postProcess(authorizationCodeRequestAuthenticationProvider));
-
 		OAuth2TokenIntrospectionAuthenticationProvider tokenIntrospectionAuthenticationProvider =
 				new OAuth2TokenIntrospectionAuthenticationProvider(
 						OAuth2ConfigurerUtils.getRegisteredClientRepository(builder),
@@ -289,15 +254,6 @@ public final class OAuth2AuthorizationServerConfigurer<B extends HttpSecurityBui
 								this.tokenRevocationEndpointMatcher));
 		builder.addFilterAfter(postProcess(clientAuthenticationFilter), AbstractPreAuthenticatedProcessingFilter.class);
 
-		OAuth2AuthorizationEndpointFilter authorizationEndpointFilter =
-				new OAuth2AuthorizationEndpointFilter(
-						authenticationManager,
-						providerSettings.authorizationEndpoint());
-		if (StringUtils.hasText(this.consentPage)) {
-			authorizationEndpointFilter.setConsentPage(this.consentPage);
-		}
-		builder.addFilterBefore(postProcess(authorizationEndpointFilter), AbstractPreAuthenticatedProcessingFilter.class);
-
 		OAuth2TokenIntrospectionEndpointFilter tokenIntrospectionEndpointFilter =
 				new OAuth2TokenIntrospectionEndpointFilter(
 						authenticationManager,
@@ -320,6 +276,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(OAuth2AuthorizationEndpointConfigurer.class, new OAuth2AuthorizationEndpointConfigurer(this::postProcess));
 		configurers.put(OAuth2TokenEndpointConfigurer.class, new OAuth2TokenEndpointConfigurer(this::postProcess));
 		return configurers;
 	}
@@ -334,13 +291,6 @@ public final class OAuth2AuthorizationServerConfigurer<B extends HttpSecurityBui
 	}
 
 	private void initEndpointMatchers(ProviderSettings providerSettings) {
-		this.authorizationEndpointMatcher = new OrRequestMatcher(
-				new AntPathRequestMatcher(
-						providerSettings.authorizationEndpoint(),
-						HttpMethod.GET.name()),
-				new AntPathRequestMatcher(
-						providerSettings.authorizationEndpoint(),
-						HttpMethod.POST.name()));
 		this.tokenIntrospectionEndpointMatcher = new AntPathRequestMatcher(
 				providerSettings.tokenIntrospectionEndpoint(), HttpMethod.POST.name());
 		this.tokenRevocationEndpointMatcher = new AntPathRequestMatcher(

+ 0 - 1
oauth2-authorization-server/src/main/java/org/springframework/security/oauth2/server/authorization/web/OAuth2AuthorizationEndpointFilter.java

@@ -237,7 +237,6 @@ public class OAuth2AuthorizationEndpointFilter extends OncePerRequestFilter {
 	 * page will be generated when this attribute is not specified.
 	 *
 	 * @param consentPage the URI of the custom consent page to redirect to if consent is required (e.g. "/oauth2/consent")
-	 * @see org.springframework.security.config.annotation.web.configurers.oauth2.server.authorization.OAuth2AuthorizationServerConfigurer#consentPage(String)
 	 */
 	public final void setConsentPage(String consentPage) {
 		this.consentPage = consentPage;

+ 82 - 1
oauth2-authorization-server/src/test/java/org/springframework/security/config/annotation/web/configurers/oauth2/server/authorization/OAuth2AuthorizationCodeGrantTests.java

@@ -21,6 +21,8 @@ import java.net.URLEncoder;
 import java.nio.charset.StandardCharsets;
 import java.security.Principal;
 import java.text.MessageFormat;
+import java.time.Instant;
+import java.time.temporal.ChronoUnit;
 import java.util.Base64;
 import java.util.List;
 import java.util.Set;
@@ -48,6 +50,7 @@ import org.springframework.jdbc.datasource.embedded.EmbeddedDatabaseBuilder;
 import org.springframework.jdbc.datasource.embedded.EmbeddedDatabaseType;
 import org.springframework.mock.http.client.MockClientHttpResponse;
 import org.springframework.mock.web.MockHttpServletResponse;
+import org.springframework.security.authentication.AuthenticationProvider;
 import org.springframework.security.authentication.TestingAuthenticationToken;
 import org.springframework.security.config.annotation.web.builders.HttpSecurity;
 import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity;
@@ -78,6 +81,7 @@ import org.springframework.security.oauth2.server.authorization.OAuth2Authorizat
 import org.springframework.security.oauth2.server.authorization.OAuth2AuthorizationService;
 import org.springframework.security.oauth2.server.authorization.OAuth2TokenCustomizer;
 import org.springframework.security.oauth2.server.authorization.TestOAuth2Authorizations;
+import org.springframework.security.oauth2.server.authorization.authentication.OAuth2AuthorizationCodeRequestAuthenticationToken;
 import org.springframework.security.oauth2.server.authorization.client.JdbcRegisteredClientRepository;
 import org.springframework.security.oauth2.server.authorization.client.RegisteredClient;
 import org.springframework.security.oauth2.server.authorization.client.RegisteredClientRepository;
@@ -87,6 +91,9 @@ import org.springframework.security.oauth2.server.authorization.jackson2.Testing
 import org.springframework.security.oauth2.server.authorization.web.OAuth2AuthorizationEndpointFilter;
 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.MvcResult;
@@ -98,6 +105,11 @@ import org.springframework.web.util.UriComponentsBuilder;
 
 import static org.assertj.core.api.Assertions.assertThat;
 import static org.hamcrest.CoreMatchers.containsString;
+import static org.mockito.ArgumentMatchers.any;
+import static org.mockito.ArgumentMatchers.eq;
+import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.verify;
+import static org.mockito.Mockito.when;
 import static org.springframework.security.test.web.servlet.request.SecurityMockMvcRequestPostProcessors.user;
 import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.get;
 import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.post;
@@ -126,6 +138,10 @@ public class OAuth2AuthorizationCodeGrantTests {
 	private static ProviderSettings providerSettings;
 	private static HttpMessageConverter<OAuth2AccessTokenResponse> accessTokenHttpResponseConverter =
 			new OAuth2AccessTokenResponseHttpMessageConverter();
+	private static AuthenticationConverter authorizationRequestConverter;
+	private static AuthenticationProvider authorizationRequestAuthenticationProvider;
+	private static AuthenticationSuccessHandler authorizationResponseHandler;
+	private static AuthenticationFailureHandler authorizationErrorResponseHandler;
 	private static String consentPage = "/oauth2/consent";
 
 	@Rule
@@ -154,6 +170,10 @@ public class OAuth2AuthorizationCodeGrantTests {
 		providerSettings = new ProviderSettings()
 				.authorizationEndpoint("/test/authorize")
 				.tokenEndpoint("/test/token");
+		authorizationRequestConverter = mock(AuthenticationConverter.class);
+		authorizationRequestAuthenticationProvider = mock(AuthenticationProvider.class);
+		authorizationResponseHandler = mock(AuthenticationSuccessHandler.class);
+		authorizationErrorResponseHandler = mock(AuthenticationFailureHandler.class);
 		db = new EmbeddedDatabaseBuilder()
 				.generateUniqueName(true)
 				.setType(EmbeddedDatabaseType.HSQL)
@@ -469,6 +489,36 @@ public class OAuth2AuthorizationCodeGrantTests {
 		assertThat(authorization).isNotNull();
 	}
 
+	@Test
+	public void requestWhenAuthorizationEndpointCustomizedThenUsed() throws Exception {
+		this.spring.register(AuthorizationServerConfigurationCustomAuthorizationEndpoint.class).autowire();
+
+		RegisteredClient registeredClient = TestRegisteredClients.registeredClient().build();
+		TestingAuthenticationToken principal = new TestingAuthenticationToken("principalName", "password");
+		OAuth2AuthorizationCode authorizationCode = new OAuth2AuthorizationCode(
+				"code", Instant.now(), Instant.now().plus(5, ChronoUnit.MINUTES));
+		OAuth2AuthorizationCodeRequestAuthenticationToken authorizationCodeRequestAuthenticationResult =
+				OAuth2AuthorizationCodeRequestAuthenticationToken.with(registeredClient.getClientId(), principal)
+						.authorizationUri("https://provider.com/oauth2/authorize")
+						.redirectUri(registeredClient.getRedirectUris().iterator().next())
+						.scopes(registeredClient.getScopes())
+						.state("state")
+						.authorizationCode(authorizationCode)
+						.build();
+		when(authorizationRequestConverter.convert(any())).thenReturn(authorizationCodeRequestAuthenticationResult);
+		when(authorizationRequestAuthenticationProvider.supports(eq(OAuth2AuthorizationCodeRequestAuthenticationToken.class))).thenReturn(true);
+		when(authorizationRequestAuthenticationProvider.authenticate(any())).thenReturn(authorizationCodeRequestAuthenticationResult);
+
+		this.mvc.perform(get(OAuth2AuthorizationEndpointFilter.DEFAULT_AUTHORIZATION_ENDPOINT_URI)
+				.params(getAuthorizationRequestParameters(registeredClient))
+				.with(user("user")))
+				.andExpect(status().isOk());
+
+		verify(authorizationRequestConverter).convert(any());
+		verify(authorizationRequestAuthenticationProvider).authenticate(eq(authorizationCodeRequestAuthenticationResult));
+		verify(authorizationResponseHandler).onAuthenticationSuccess(any(), any(), eq(authorizationCodeRequestAuthenticationResult));
+	}
+
 	private static MultiValueMap<String, String> getAuthorizationRequestParameters(RegisteredClient registeredClient) {
 		MultiValueMap<String, String> parameters = new LinkedMultiValueMap<>();
 		parameters.set(OAuth2ParameterNames.RESPONSE_TYPE, OAuth2AuthorizationResponseType.CODE.getValue());
@@ -610,7 +660,9 @@ public class OAuth2AuthorizationCodeGrantTests {
 		public SecurityFilterChain authorizationServerSecurityFilterChain(HttpSecurity http) throws Exception {
 			OAuth2AuthorizationServerConfigurer<HttpSecurity> authorizationServerConfigurer =
 					new OAuth2AuthorizationServerConfigurer<>();
-			authorizationServerConfigurer.consentPage(consentPage);
+			authorizationServerConfigurer
+					.authorizationEndpoint(authorizationEndpoint ->
+							authorizationEndpoint.consentPage(consentPage));
 			RequestMatcher endpointsMatcher = authorizationServerConfigurer.getEndpointsMatcher();
 
 			http
@@ -624,4 +676,33 @@ public class OAuth2AuthorizationCodeGrantTests {
 		}
 		// @formatter:on
 	}
+
+	@EnableWebSecurity
+	static class AuthorizationServerConfigurationCustomAuthorizationEndpoint extends AuthorizationServerConfiguration {
+		// @formatter:off
+		@Bean
+		public SecurityFilterChain authorizationServerSecurityFilterChain(HttpSecurity http) throws Exception {
+			OAuth2AuthorizationServerConfigurer<HttpSecurity> authorizationServerConfigurer =
+					new OAuth2AuthorizationServerConfigurer<>();
+			authorizationServerConfigurer
+					.authorizationEndpoint(authorizationEndpoint ->
+							authorizationEndpoint
+									.authorizationRequestConverter(authorizationRequestConverter)
+									.authenticationProvider(authorizationRequestAuthenticationProvider)
+									.authorizationResponseHandler(authorizationResponseHandler)
+									.errorResponseHandler(authorizationErrorResponseHandler));
+			RequestMatcher endpointsMatcher = authorizationServerConfigurer.getEndpointsMatcher();
+
+			http
+					.requestMatcher(endpointsMatcher)
+					.authorizeRequests(authorizeRequests ->
+							authorizeRequests.anyRequest().authenticated()
+					)
+					.csrf(csrf -> csrf.ignoringRequestMatchers(endpointsMatcher))
+					.apply(authorizationServerConfigurer);
+			return http.build();
+		}
+		// @formatter:on
+	}
+
 }

+ 4 - 1
samples/boot/oauth2-integration/authorizationserver-custom-consent-page/src/main/java/sample/config/AuthorizationServerConfig.java

@@ -54,7 +54,10 @@ public class AuthorizationServerConfig {
 	public SecurityFilterChain authorizationServerSecurityFilterChain(HttpSecurity http) throws Exception {
 		OAuth2AuthorizationServerConfigurer<HttpSecurity> authorizationServerConfigurer =
 				new OAuth2AuthorizationServerConfigurer<>();
-		authorizationServerConfigurer.consentPage("/oauth2/consent");
+		authorizationServerConfigurer
+				.authorizationEndpoint(authorizationEndpoint ->
+						authorizationEndpoint.consentPage("/oauth2/consent"));
+
 		RequestMatcher endpointsMatcher = authorizationServerConfigurer
 				.getEndpointsMatcher();