瀏覽代碼

Provide extension for processing authorization request

Issue gh-342
Joe Grandja 4 年之前
父節點
當前提交
543fa264b3

+ 24 - 151
oauth2-authorization-server/src/main/java/org/springframework/security/oauth2/server/authorization/web/OAuth2AuthorizationEndpointFilter.java

@@ -17,11 +17,8 @@ package org.springframework.security.oauth2.server.authorization.web;
 
 
 import java.io.IOException;
 import java.io.IOException;
 import java.nio.charset.StandardCharsets;
 import java.nio.charset.StandardCharsets;
-import java.util.Arrays;
 import java.util.HashSet;
 import java.util.HashSet;
-import java.util.Map;
 import java.util.Set;
 import java.util.Set;
-import java.util.stream.Collectors;
 
 
 import javax.servlet.FilterChain;
 import javax.servlet.FilterChain;
 import javax.servlet.ServletException;
 import javax.servlet.ServletException;
@@ -31,24 +28,20 @@ import javax.servlet.http.HttpServletResponse;
 import org.springframework.http.HttpMethod;
 import org.springframework.http.HttpMethod;
 import org.springframework.http.HttpStatus;
 import org.springframework.http.HttpStatus;
 import org.springframework.http.MediaType;
 import org.springframework.http.MediaType;
-import org.springframework.security.authentication.AnonymousAuthenticationToken;
 import org.springframework.security.authentication.AuthenticationManager;
 import org.springframework.security.authentication.AuthenticationManager;
 import org.springframework.security.core.Authentication;
 import org.springframework.security.core.Authentication;
 import org.springframework.security.core.AuthenticationException;
 import org.springframework.security.core.AuthenticationException;
-import org.springframework.security.core.authority.AuthorityUtils;
 import org.springframework.security.core.context.SecurityContextHolder;
 import org.springframework.security.core.context.SecurityContextHolder;
 import org.springframework.security.oauth2.core.OAuth2AuthenticationException;
 import org.springframework.security.oauth2.core.OAuth2AuthenticationException;
 import org.springframework.security.oauth2.core.OAuth2Error;
 import org.springframework.security.oauth2.core.OAuth2Error;
-import org.springframework.security.oauth2.core.OAuth2ErrorCodes;
-import org.springframework.security.oauth2.core.endpoint.OAuth2AuthorizationResponseType;
 import org.springframework.security.oauth2.core.endpoint.OAuth2ParameterNames;
 import org.springframework.security.oauth2.core.endpoint.OAuth2ParameterNames;
-import org.springframework.security.oauth2.core.endpoint.PkceParameterNames;
 import org.springframework.security.oauth2.core.oidc.OidcScopes;
 import org.springframework.security.oauth2.core.oidc.OidcScopes;
 import org.springframework.security.oauth2.server.authorization.OAuth2AuthorizationCodeRequestAuthenticationException;
 import org.springframework.security.oauth2.server.authorization.OAuth2AuthorizationCodeRequestAuthenticationException;
 import org.springframework.security.oauth2.server.authorization.OAuth2AuthorizationService;
 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.OAuth2AuthorizationCodeRequestAuthenticationProvider;
 import org.springframework.security.oauth2.server.authorization.authentication.OAuth2AuthorizationCodeRequestAuthenticationToken;
 import org.springframework.security.oauth2.server.authorization.authentication.OAuth2AuthorizationCodeRequestAuthenticationToken;
 import org.springframework.security.oauth2.server.authorization.client.RegisteredClientRepository;
 import org.springframework.security.oauth2.server.authorization.client.RegisteredClientRepository;
+import org.springframework.security.oauth2.server.authorization.web.authentication.OAuth2AuthorizationCodeRequestAuthenticationConverter;
 import org.springframework.security.web.DefaultRedirectStrategy;
 import org.springframework.security.web.DefaultRedirectStrategy;
 import org.springframework.security.web.RedirectStrategy;
 import org.springframework.security.web.RedirectStrategy;
 import org.springframework.security.web.authentication.AuthenticationConverter;
 import org.springframework.security.web.authentication.AuthenticationConverter;
@@ -60,7 +53,6 @@ import org.springframework.security.web.util.matcher.NegatedRequestMatcher;
 import org.springframework.security.web.util.matcher.OrRequestMatcher;
 import org.springframework.security.web.util.matcher.OrRequestMatcher;
 import org.springframework.security.web.util.matcher.RequestMatcher;
 import org.springframework.security.web.util.matcher.RequestMatcher;
 import org.springframework.util.Assert;
 import org.springframework.util.Assert;
-import org.springframework.util.MultiValueMap;
 import org.springframework.util.StringUtils;
 import org.springframework.util.StringUtils;
 import org.springframework.web.filter.OncePerRequestFilter;
 import org.springframework.web.filter.OncePerRequestFilter;
 import org.springframework.web.util.UriComponentsBuilder;
 import org.springframework.web.util.UriComponentsBuilder;
@@ -88,8 +80,8 @@ public class OAuth2AuthorizationEndpointFilter extends OncePerRequestFilter {
 
 
 	private final AuthenticationManager authenticationManager;
 	private final AuthenticationManager authenticationManager;
 	private final RequestMatcher authorizationEndpointMatcher;
 	private final RequestMatcher authorizationEndpointMatcher;
-	private final AuthenticationConverter authenticationConverter;
 	private final RedirectStrategy redirectStrategy = new DefaultRedirectStrategy();
 	private final RedirectStrategy redirectStrategy = new DefaultRedirectStrategy();
+	private AuthenticationConverter authenticationConverter;
 	private String consentPage;
 	private String consentPage;
 
 
 	/**
 	/**
@@ -164,17 +156,6 @@ public class OAuth2AuthorizationEndpointFilter extends OncePerRequestFilter {
 		return new OrRequestMatcher(authorizationRequestMatcher, authorizationConsentMatcher);
 		return new OrRequestMatcher(authorizationRequestMatcher, authorizationConsentMatcher);
 	}
 	}
 
 
-	/**
-	 * Specify the URI to redirect Resource Owners to if consent is required. A default consent
-	 * 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;
-	}
-
 	@Override
 	@Override
 	protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain filterChain)
 	protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain filterChain)
 			throws ServletException, IOException {
 			throws ServletException, IOException {
@@ -212,6 +193,28 @@ public class OAuth2AuthorizationEndpointFilter extends OncePerRequestFilter {
 		}
 		}
 	}
 	}
 
 
+	/**
+	 * 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 authenticationConverter the {@link AuthenticationConverter} used when attempting to extract an Authorization Request (or Consent) from {@link HttpServletRequest}
+	 */
+	public final void setAuthenticationConverter(AuthenticationConverter authenticationConverter) {
+		Assert.notNull(authenticationConverter, "authenticationConverter cannot be null");
+		this.authenticationConverter = authenticationConverter;
+	}
+
+	/**
+	 * Specify the URI to redirect Resource Owners to if consent is required. A default consent
+	 * 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;
+	}
+
 	private void sendAuthorizationConsent(HttpServletRequest request, HttpServletResponse response,
 	private void sendAuthorizationConsent(HttpServletRequest request, HttpServletResponse response,
 			OAuth2AuthorizationCodeRequestAuthenticationToken authorizationCodeRequestAuthentication,
 			OAuth2AuthorizationCodeRequestAuthenticationToken authorizationCodeRequestAuthentication,
 			OAuth2AuthorizationCodeRequestAuthenticationToken authorizationCodeRequestAuthenticationResult) throws IOException {
 			OAuth2AuthorizationCodeRequestAuthenticationToken authorizationCodeRequestAuthenticationResult) throws IOException {
@@ -294,136 +297,6 @@ public class OAuth2AuthorizationEndpointFilter extends OncePerRequestFilter {
 		this.redirectStrategy.sendRedirect(request, response, uriBuilder.toUriString());
 		this.redirectStrategy.sendRedirect(request, response, uriBuilder.toUriString());
 	}
 	}
 
 
-	private static class OAuth2AuthorizationCodeRequestAuthenticationConverter implements AuthenticationConverter {
-		private static final Authentication ANONYMOUS_AUTHENTICATION = new AnonymousAuthenticationToken(
-				"anonymous", "anonymousUser", AuthorityUtils.createAuthorityList("ROLE_ANONYMOUS"));
-		private static final String PKCE_ERROR_URI = "https://datatracker.ietf.org/doc/html/rfc7636#section-4.4.1";
-		private final RequestMatcher oidcAuthenticationRequestMatcher;
-
-		private OAuth2AuthorizationCodeRequestAuthenticationConverter() {
-			RequestMatcher postMethodMatcher = request -> "POST".equals(request.getMethod());
-			RequestMatcher responseTypeParameterMatcher = request ->
-					request.getParameter(OAuth2ParameterNames.RESPONSE_TYPE) != null;
-			RequestMatcher openidScopeMatcher = request -> {
-				String scope = request.getParameter(OAuth2ParameterNames.SCOPE);
-				return StringUtils.hasText(scope) && scope.contains(OidcScopes.OPENID);
-			};
-			this.oidcAuthenticationRequestMatcher = new AndRequestMatcher(
-					postMethodMatcher, responseTypeParameterMatcher, openidScopeMatcher);
-		}
-
-		@Override
-		public Authentication convert(HttpServletRequest request) {
-			MultiValueMap<String, String> parameters = OAuth2EndpointUtils.getParameters(request);
-
-			boolean authorizationRequest = false;
-			if ("GET".equals(request.getMethod()) || this.oidcAuthenticationRequestMatcher.matches(request)) {
-				authorizationRequest = true;
-
-				// response_type (REQUIRED)
-				String responseType = request.getParameter(OAuth2ParameterNames.RESPONSE_TYPE);
-				if (!StringUtils.hasText(responseType) ||
-						parameters.get(OAuth2ParameterNames.RESPONSE_TYPE).size() != 1) {
-					throwError(OAuth2ErrorCodes.INVALID_REQUEST, OAuth2ParameterNames.RESPONSE_TYPE);
-				} else if (!responseType.equals(OAuth2AuthorizationResponseType.CODE.getValue())) {
-					throwError(OAuth2ErrorCodes.UNSUPPORTED_RESPONSE_TYPE, OAuth2ParameterNames.RESPONSE_TYPE);
-				}
-			}
-
-			String authorizationUri = request.getRequestURL().toString();
-
-			// client_id (REQUIRED)
-			String clientId = parameters.getFirst(OAuth2ParameterNames.CLIENT_ID);
-			if (!StringUtils.hasText(clientId) ||
-					parameters.get(OAuth2ParameterNames.CLIENT_ID).size() != 1) {
-				throwError(OAuth2ErrorCodes.INVALID_REQUEST, OAuth2ParameterNames.CLIENT_ID);
-			}
-
-			Authentication principal = SecurityContextHolder.getContext().getAuthentication();
-			if (principal == null) {
-				principal = ANONYMOUS_AUTHENTICATION;
-			}
-
-			// redirect_uri (OPTIONAL)
-			String redirectUri = parameters.getFirst(OAuth2ParameterNames.REDIRECT_URI);
-			if (StringUtils.hasText(redirectUri) &&
-					parameters.get(OAuth2ParameterNames.REDIRECT_URI).size() != 1) {
-				throwError(OAuth2ErrorCodes.INVALID_REQUEST, OAuth2ParameterNames.REDIRECT_URI);
-			}
-
-			// scope (OPTIONAL)
-			Set<String> scopes = null;
-			if (authorizationRequest) {
-				String scope = parameters.getFirst(OAuth2ParameterNames.SCOPE);
-				if (StringUtils.hasText(scope) &&
-						parameters.get(OAuth2ParameterNames.SCOPE).size() != 1) {
-					throwError(OAuth2ErrorCodes.INVALID_REQUEST, OAuth2ParameterNames.SCOPE);
-				}
-				if (StringUtils.hasText(scope)) {
-					scopes = new HashSet<>(
-							Arrays.asList(StringUtils.delimitedListToStringArray(scope, " ")));
-				}
-			} else {
-				// Consent request
-				if (parameters.containsKey(OAuth2ParameterNames.SCOPE)) {
-					scopes = new HashSet<>(parameters.get(OAuth2ParameterNames.SCOPE));
-				}
-			}
-
-			// state (RECOMMENDED)
-			String state = parameters.getFirst(OAuth2ParameterNames.STATE);
-			if (StringUtils.hasText(state) &&
-					parameters.get(OAuth2ParameterNames.STATE).size() != 1) {
-				throwError(OAuth2ErrorCodes.INVALID_REQUEST, OAuth2ParameterNames.STATE);
-			}
-
-			// code_challenge (REQUIRED for public clients) - RFC 7636 (PKCE)
-			String codeChallenge = parameters.getFirst(PkceParameterNames.CODE_CHALLENGE);
-			if (StringUtils.hasText(codeChallenge) &&
-					parameters.get(PkceParameterNames.CODE_CHALLENGE).size() != 1) {
-				throwError(OAuth2ErrorCodes.INVALID_REQUEST, PkceParameterNames.CODE_CHALLENGE, PKCE_ERROR_URI);
-			}
-
-			// code_challenge_method (OPTIONAL for public clients) - RFC 7636 (PKCE)
-			String codeChallengeMethod = parameters.getFirst(PkceParameterNames.CODE_CHALLENGE_METHOD);
-			if (StringUtils.hasText(codeChallengeMethod) &&
-					parameters.get(PkceParameterNames.CODE_CHALLENGE_METHOD).size() != 1) {
-				throwError(OAuth2ErrorCodes.INVALID_REQUEST, PkceParameterNames.CODE_CHALLENGE_METHOD, PKCE_ERROR_URI);
-			}
-
-			// @formatter:off
-			Map<String, Object> additionalParameters = parameters
-					.entrySet()
-					.stream()
-					.filter(e -> !e.getKey().equals(OAuth2ParameterNames.RESPONSE_TYPE) &&
-							!e.getKey().equals(OAuth2ParameterNames.CLIENT_ID) &&
-							!e.getKey().equals(OAuth2ParameterNames.REDIRECT_URI) &&
-							!e.getKey().equals(OAuth2ParameterNames.SCOPE) &&
-							!e.getKey().equals(OAuth2ParameterNames.STATE))
-					.collect(Collectors.toMap(Map.Entry::getKey, e -> e.getValue().get(0)));
-			// @formatter:on
-
-			return OAuth2AuthorizationCodeRequestAuthenticationToken.with(clientId, principal)
-					.authorizationUri(authorizationUri)
-					.redirectUri(redirectUri)
-					.scopes(scopes)
-					.state(state)
-					.additionalParameters(additionalParameters)
-					.consent(!authorizationRequest)
-					.build();
-		}
-
-		private static void throwError(String errorCode, String parameterName) {
-			throwError(errorCode, parameterName, "https://datatracker.ietf.org/doc/html/rfc6749#section-4.1.2.1");
-		}
-
-		private static void throwError(String errorCode, String parameterName, String errorUri) {
-			OAuth2Error error = new OAuth2Error(errorCode, "OAuth 2.0 Parameter: " + parameterName, errorUri);
-			throw new OAuth2AuthorizationCodeRequestAuthenticationException(error, null);
-		}
-
-	}
-
 	/**
 	/**
 	 * For internal use only.
 	 * For internal use only.
 	 */
 	 */

+ 185 - 0
oauth2-authorization-server/src/main/java/org/springframework/security/oauth2/server/authorization/web/authentication/OAuth2AuthorizationCodeRequestAuthenticationConverter.java

@@ -0,0 +1,185 @@
+/*
+ * 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.oauth2.server.authorization.web.authentication;
+
+import java.util.Arrays;
+import java.util.HashSet;
+import java.util.Map;
+import java.util.Set;
+import java.util.stream.Collectors;
+
+import javax.servlet.http.HttpServletRequest;
+
+import org.springframework.security.authentication.AnonymousAuthenticationToken;
+import org.springframework.security.core.Authentication;
+import org.springframework.security.core.authority.AuthorityUtils;
+import org.springframework.security.core.context.SecurityContextHolder;
+import org.springframework.security.oauth2.core.OAuth2Error;
+import org.springframework.security.oauth2.core.OAuth2ErrorCodes;
+import org.springframework.security.oauth2.core.endpoint.OAuth2AuthorizationResponseType;
+import org.springframework.security.oauth2.core.endpoint.OAuth2ParameterNames;
+import org.springframework.security.oauth2.core.endpoint.PkceParameterNames;
+import org.springframework.security.oauth2.core.oidc.OidcScopes;
+import org.springframework.security.oauth2.server.authorization.OAuth2AuthorizationCodeRequestAuthenticationException;
+import org.springframework.security.oauth2.server.authorization.authentication.OAuth2AuthorizationCodeRequestAuthenticationToken;
+import org.springframework.security.oauth2.server.authorization.web.OAuth2AuthorizationEndpointFilter;
+import org.springframework.security.web.authentication.AuthenticationConverter;
+import org.springframework.security.web.util.matcher.AndRequestMatcher;
+import org.springframework.security.web.util.matcher.RequestMatcher;
+import org.springframework.util.MultiValueMap;
+import org.springframework.util.StringUtils;
+
+/**
+ * Attempts to extract an Authorization Request (or Consent) from {@link HttpServletRequest}
+ * for the OAuth 2.0 Authorization Code Grant and then converts it to
+ * an {@link OAuth2AuthorizationCodeRequestAuthenticationToken} used for authenticating the request.
+ *
+ * @author Joe Grandja
+ * @since 0.1.2
+ * @see AuthenticationConverter
+ * @see OAuth2AuthorizationCodeRequestAuthenticationToken
+ * @see OAuth2AuthorizationEndpointFilter
+ */
+public final class OAuth2AuthorizationCodeRequestAuthenticationConverter implements AuthenticationConverter {
+	private static final String DEFAULT_ERROR_URI = "https://datatracker.ietf.org/doc/html/rfc6749#section-4.1.2.1";
+	private static final String PKCE_ERROR_URI = "https://datatracker.ietf.org/doc/html/rfc7636#section-4.4.1";
+	private static final Authentication ANONYMOUS_AUTHENTICATION = new AnonymousAuthenticationToken(
+			"anonymous", "anonymousUser", AuthorityUtils.createAuthorityList("ROLE_ANONYMOUS"));
+	private static final RequestMatcher OIDC_REQUEST_MATCHER = createOidcRequestMatcher();
+
+	@Override
+	public Authentication convert(HttpServletRequest request) {
+		MultiValueMap<String, String> parameters = OAuth2EndpointUtils.getParameters(request);
+
+		boolean authorizationRequest = false;
+		if ("GET".equals(request.getMethod()) || OIDC_REQUEST_MATCHER.matches(request)) {
+			authorizationRequest = true;
+
+			// response_type (REQUIRED)
+			String responseType = request.getParameter(OAuth2ParameterNames.RESPONSE_TYPE);
+			if (!StringUtils.hasText(responseType) ||
+					parameters.get(OAuth2ParameterNames.RESPONSE_TYPE).size() != 1) {
+				throwError(OAuth2ErrorCodes.INVALID_REQUEST, OAuth2ParameterNames.RESPONSE_TYPE);
+			} else if (!responseType.equals(OAuth2AuthorizationResponseType.CODE.getValue())) {
+				throwError(OAuth2ErrorCodes.UNSUPPORTED_RESPONSE_TYPE, OAuth2ParameterNames.RESPONSE_TYPE);
+			}
+		}
+
+		String authorizationUri = request.getRequestURL().toString();
+
+		// client_id (REQUIRED)
+		String clientId = parameters.getFirst(OAuth2ParameterNames.CLIENT_ID);
+		if (!StringUtils.hasText(clientId) ||
+				parameters.get(OAuth2ParameterNames.CLIENT_ID).size() != 1) {
+			throwError(OAuth2ErrorCodes.INVALID_REQUEST, OAuth2ParameterNames.CLIENT_ID);
+		}
+
+		Authentication principal = SecurityContextHolder.getContext().getAuthentication();
+		if (principal == null) {
+			principal = ANONYMOUS_AUTHENTICATION;
+		}
+
+		// redirect_uri (OPTIONAL)
+		String redirectUri = parameters.getFirst(OAuth2ParameterNames.REDIRECT_URI);
+		if (StringUtils.hasText(redirectUri) &&
+				parameters.get(OAuth2ParameterNames.REDIRECT_URI).size() != 1) {
+			throwError(OAuth2ErrorCodes.INVALID_REQUEST, OAuth2ParameterNames.REDIRECT_URI);
+		}
+
+		// scope (OPTIONAL)
+		Set<String> scopes = null;
+		if (authorizationRequest) {
+			String scope = parameters.getFirst(OAuth2ParameterNames.SCOPE);
+			if (StringUtils.hasText(scope) &&
+					parameters.get(OAuth2ParameterNames.SCOPE).size() != 1) {
+				throwError(OAuth2ErrorCodes.INVALID_REQUEST, OAuth2ParameterNames.SCOPE);
+			}
+			if (StringUtils.hasText(scope)) {
+				scopes = new HashSet<>(
+						Arrays.asList(StringUtils.delimitedListToStringArray(scope, " ")));
+			}
+		} else {
+			// Consent request
+			if (parameters.containsKey(OAuth2ParameterNames.SCOPE)) {
+				scopes = new HashSet<>(parameters.get(OAuth2ParameterNames.SCOPE));
+			}
+		}
+
+		// state (RECOMMENDED)
+		String state = parameters.getFirst(OAuth2ParameterNames.STATE);
+		if (StringUtils.hasText(state) &&
+				parameters.get(OAuth2ParameterNames.STATE).size() != 1) {
+			throwError(OAuth2ErrorCodes.INVALID_REQUEST, OAuth2ParameterNames.STATE);
+		}
+
+		// code_challenge (REQUIRED for public clients) - RFC 7636 (PKCE)
+		String codeChallenge = parameters.getFirst(PkceParameterNames.CODE_CHALLENGE);
+		if (StringUtils.hasText(codeChallenge) &&
+				parameters.get(PkceParameterNames.CODE_CHALLENGE).size() != 1) {
+			throwError(OAuth2ErrorCodes.INVALID_REQUEST, PkceParameterNames.CODE_CHALLENGE, PKCE_ERROR_URI);
+		}
+
+		// code_challenge_method (OPTIONAL for public clients) - RFC 7636 (PKCE)
+		String codeChallengeMethod = parameters.getFirst(PkceParameterNames.CODE_CHALLENGE_METHOD);
+		if (StringUtils.hasText(codeChallengeMethod) &&
+				parameters.get(PkceParameterNames.CODE_CHALLENGE_METHOD).size() != 1) {
+			throwError(OAuth2ErrorCodes.INVALID_REQUEST, PkceParameterNames.CODE_CHALLENGE_METHOD, PKCE_ERROR_URI);
+		}
+
+		// @formatter:off
+		Map<String, Object> additionalParameters = parameters
+				.entrySet()
+				.stream()
+				.filter(e -> !e.getKey().equals(OAuth2ParameterNames.RESPONSE_TYPE) &&
+						!e.getKey().equals(OAuth2ParameterNames.CLIENT_ID) &&
+						!e.getKey().equals(OAuth2ParameterNames.REDIRECT_URI) &&
+						!e.getKey().equals(OAuth2ParameterNames.SCOPE) &&
+						!e.getKey().equals(OAuth2ParameterNames.STATE))
+				.collect(Collectors.toMap(Map.Entry::getKey, e -> e.getValue().get(0)));
+        // @formatter:on
+
+		return OAuth2AuthorizationCodeRequestAuthenticationToken.with(clientId, principal)
+				.authorizationUri(authorizationUri)
+				.redirectUri(redirectUri)
+				.scopes(scopes)
+				.state(state)
+				.additionalParameters(additionalParameters)
+				.consent(!authorizationRequest)
+				.build();
+	}
+
+	private static RequestMatcher createOidcRequestMatcher() {
+		RequestMatcher postMethodMatcher = request -> "POST".equals(request.getMethod());
+		RequestMatcher responseTypeParameterMatcher = request ->
+				request.getParameter(OAuth2ParameterNames.RESPONSE_TYPE) != null;
+		RequestMatcher openidScopeMatcher = request -> {
+			String scope = request.getParameter(OAuth2ParameterNames.SCOPE);
+			return StringUtils.hasText(scope) && scope.contains(OidcScopes.OPENID);
+		};
+		return new AndRequestMatcher(
+				postMethodMatcher, responseTypeParameterMatcher, openidScopeMatcher);
+	}
+
+	private static void throwError(String errorCode, String parameterName) {
+		throwError(errorCode, parameterName, DEFAULT_ERROR_URI);
+	}
+
+	private static void throwError(String errorCode, String parameterName, String errorUri) {
+		OAuth2Error error = new OAuth2Error(errorCode, "OAuth 2.0 Parameter: " + parameterName, errorUri);
+		throw new OAuth2AuthorizationCodeRequestAuthenticationException(error, null);
+	}
+
+}

+ 33 - 0
oauth2-authorization-server/src/test/java/org/springframework/security/oauth2/server/authorization/web/OAuth2AuthorizationEndpointFilterTests.java

@@ -52,6 +52,7 @@ import org.springframework.security.oauth2.server.authorization.OAuth2Authorizat
 import org.springframework.security.oauth2.server.authorization.authentication.OAuth2AuthorizationCodeRequestAuthenticationToken;
 import org.springframework.security.oauth2.server.authorization.authentication.OAuth2AuthorizationCodeRequestAuthenticationToken;
 import org.springframework.security.oauth2.server.authorization.client.RegisteredClient;
 import org.springframework.security.oauth2.server.authorization.client.RegisteredClient;
 import org.springframework.security.oauth2.server.authorization.client.TestRegisteredClients;
 import org.springframework.security.oauth2.server.authorization.client.TestRegisteredClients;
+import org.springframework.security.web.authentication.AuthenticationConverter;
 import org.springframework.util.StringUtils;
 import org.springframework.util.StringUtils;
 
 
 import static org.assertj.core.api.Assertions.assertThat;
 import static org.assertj.core.api.Assertions.assertThat;
@@ -110,6 +111,13 @@ public class OAuth2AuthorizationEndpointFilterTests {
 				.hasMessage("authorizationEndpointUri cannot be empty");
 				.hasMessage("authorizationEndpointUri cannot be empty");
 	}
 	}
 
 
+	@Test
+	public void setAuthenticationConverterWhenNullThenThrowIllegalArgumentException() {
+		assertThatThrownBy(() -> this.filter.setAuthenticationConverter(null))
+				.isInstanceOf(IllegalArgumentException.class)
+				.hasMessage("authenticationConverter cannot be null");
+	}
+
 	@Test
 	@Test
 	public void doFilterWhenNotAuthorizationRequestThenNotProcessed() throws Exception {
 	public void doFilterWhenNotAuthorizationRequestThenNotProcessed() throws Exception {
 		String requestUri = "/path";
 		String requestUri = "/path";
@@ -242,6 +250,31 @@ public class OAuth2AuthorizationEndpointFilterTests {
 		assertThat(response.getRedirectedUrl()).isEqualTo("https://example.com?error=errorCode&error_description=errorDescription&error_uri=errorUri&state=state");
 		assertThat(response.getRedirectedUrl()).isEqualTo("https://example.com?error=errorCode&error_description=errorDescription&error_uri=errorUri&state=state");
 	}
 	}
 
 
+	@Test
+	public void doFilterWhenCustomAuthenticationConverterThenUsed() throws Exception {
+		RegisteredClient registeredClient = TestRegisteredClients.registeredClient().build();
+		OAuth2AuthorizationCodeRequestAuthenticationToken authorizationCodeRequestAuthentication =
+				authorizationCodeRequestAuthentication(registeredClient, this.principal)
+						.build();
+
+		AuthenticationConverter authenticationConverter = mock(AuthenticationConverter.class);
+		when(authenticationConverter.convert(any())).thenReturn(authorizationCodeRequestAuthentication);
+		this.filter.setAuthenticationConverter(authenticationConverter);
+
+		when(this.authenticationManager.authenticate(any()))
+				.thenReturn(authorizationCodeRequestAuthentication);
+
+		MockHttpServletRequest request = createAuthorizationRequest(registeredClient);
+		MockHttpServletResponse response = new MockHttpServletResponse();
+		FilterChain filterChain = mock(FilterChain.class);
+
+		this.filter.doFilter(request, response, filterChain);
+
+		verify(authenticationConverter).convert(any());
+		verify(this.authenticationManager).authenticate(any());
+		verify(filterChain).doFilter(any(HttpServletRequest.class), any(HttpServletResponse.class));
+	}
+
 	@Test
 	@Test
 	public void doFilterWhenAuthorizationRequestPrincipalNotAuthenticatedThenCommenceAuthentication() throws Exception {
 	public void doFilterWhenAuthorizationRequestPrincipalNotAuthenticatedThenCommenceAuthentication() throws Exception {
 		this.principal.setAuthenticated(false);
 		this.principal.setAuthenticated(false);