diff --git a/oauth2/oauth2-authorization-server/src/main/java/org/springframework/security/oauth2/server/authorization/web/authentication/OAuth2EndpointUtils.java b/oauth2/oauth2-authorization-server/src/main/java/org/springframework/security/oauth2/server/authorization/web/authentication/OAuth2EndpointUtils.java index f361c4fa564..76e23f48634 100644 --- a/oauth2/oauth2-authorization-server/src/main/java/org/springframework/security/oauth2/server/authorization/web/authentication/OAuth2EndpointUtils.java +++ b/oauth2/oauth2-authorization-server/src/main/java/org/springframework/security/oauth2/server/authorization/web/authentication/OAuth2EndpointUtils.java @@ -18,6 +18,7 @@ import java.util.Collections; import java.util.HashMap; +import java.util.List; import java.util.Locale; import java.util.Map; @@ -85,6 +86,9 @@ static Map getParametersIfMatchesAuthorizationCodeGrantRequest(H } MultiValueMap multiValueParameters = "GET".equals(request.getMethod()) ? getQueryParameters(request) : getFormParameters(request); + validateSingleParameter(multiValueParameters, OAuth2ParameterNames.GRANT_TYPE); + validateSingleParameter(multiValueParameters, OAuth2ParameterNames.CODE); + validateSingleParameter(multiValueParameters, PkceParameterNames.CODE_VERIFIER); for (String exclusion : exclusions) { multiValueParameters.remove(exclusion); } @@ -96,6 +100,13 @@ static Map getParametersIfMatchesAuthorizationCodeGrantRequest(H return parameters; } + private static void validateSingleParameter(MultiValueMap parameters, String parameterName) { + List values = parameters.get(parameterName); + if (values != null && values.size() != 1) { + throwError(OAuth2ErrorCodes.INVALID_REQUEST, parameterName, ACCESS_TOKEN_REQUEST_ERROR_URI); + } + } + static boolean matchesAuthorizationCodeGrantRequest(HttpServletRequest request) { return AuthorizationGrantType.AUTHORIZATION_CODE.getValue() .equals(request.getParameter(OAuth2ParameterNames.GRANT_TYPE)) diff --git a/oauth2/oauth2-authorization-server/src/test/java/org/springframework/security/oauth2/server/authorization/web/authentication/ClientSecretBasicAuthenticationConverterTests.java b/oauth2/oauth2-authorization-server/src/test/java/org/springframework/security/oauth2/server/authorization/web/authentication/ClientSecretBasicAuthenticationConverterTests.java index c7e5bbfc2a5..d9961aa9df7 100644 --- a/oauth2/oauth2-authorization-server/src/test/java/org/springframework/security/oauth2/server/authorization/web/authentication/ClientSecretBasicAuthenticationConverterTests.java +++ b/oauth2/oauth2-authorization-server/src/test/java/org/springframework/security/oauth2/server/authorization/web/authentication/ClientSecretBasicAuthenticationConverterTests.java @@ -123,6 +123,28 @@ public void convertWhenConfidentialClientWithPkceParametersThenAdditionalParamet entry("custom-param", new String[] { "custom-value-1", "custom-value-2" })); } + @Test + public void convertWhenConfidentialClientWithMultipleCodeThenInvalidRequestError() throws Exception { + MockHttpServletRequest request = createPkceTokenRequest(); + request.addParameter(OAuth2ParameterNames.CODE, "code-2"); + request.addHeader(HttpHeaders.AUTHORIZATION, "Basic " + encodeBasicAuth("clientId", "secret")); + assertThatExceptionOfType(OAuth2AuthenticationException.class).isThrownBy(() -> this.converter.convert(request)) + .extracting(OAuth2AuthenticationException::getError) + .extracting("errorCode") + .isEqualTo(OAuth2ErrorCodes.INVALID_REQUEST); + } + + @Test + public void convertWhenConfidentialClientWithMultipleCodeVerifierThenInvalidRequestError() throws Exception { + MockHttpServletRequest request = createPkceTokenRequest(); + request.addParameter(PkceParameterNames.CODE_VERIFIER, "code-verifier-2"); + request.addHeader(HttpHeaders.AUTHORIZATION, "Basic " + encodeBasicAuth("clientId", "secret")); + assertThatExceptionOfType(OAuth2AuthenticationException.class).isThrownBy(() -> this.converter.convert(request)) + .extracting(OAuth2AuthenticationException::getError) + .extracting("errorCode") + .isEqualTo(OAuth2ErrorCodes.INVALID_REQUEST); + } + 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()); diff --git a/oauth2/oauth2-authorization-server/src/test/java/org/springframework/security/oauth2/server/authorization/web/authentication/ClientSecretPostAuthenticationConverterTests.java b/oauth2/oauth2-authorization-server/src/test/java/org/springframework/security/oauth2/server/authorization/web/authentication/ClientSecretPostAuthenticationConverterTests.java index 4078897c069..9aca887432f 100644 --- a/oauth2/oauth2-authorization-server/src/test/java/org/springframework/security/oauth2/server/authorization/web/authentication/ClientSecretPostAuthenticationConverterTests.java +++ b/oauth2/oauth2-authorization-server/src/test/java/org/springframework/security/oauth2/server/authorization/web/authentication/ClientSecretPostAuthenticationConverterTests.java @@ -110,6 +110,30 @@ public void convertWhenConfidentialClientWithPkceParametersThenAdditionalParamet entry("custom-param", new String[] { "custom-value-1", "custom-value-2" })); } + @Test + public void convertWhenConfidentialClientWithMultipleCodeThenInvalidRequestError() { + MockHttpServletRequest request = createPkceTokenRequest(); + request.addParameter(OAuth2ParameterNames.CLIENT_ID, "client-1"); + request.addParameter(OAuth2ParameterNames.CLIENT_SECRET, "client-secret"); + request.addParameter(OAuth2ParameterNames.CODE, "code-2"); + assertThatExceptionOfType(OAuth2AuthenticationException.class).isThrownBy(() -> this.converter.convert(request)) + .extracting(OAuth2AuthenticationException::getError) + .extracting("errorCode") + .isEqualTo(OAuth2ErrorCodes.INVALID_REQUEST); + } + + @Test + public void convertWhenConfidentialClientWithMultipleCodeVerifierThenInvalidRequestError() { + MockHttpServletRequest request = createPkceTokenRequest(); + request.addParameter(OAuth2ParameterNames.CLIENT_ID, "client-1"); + request.addParameter(OAuth2ParameterNames.CLIENT_SECRET, "client-secret"); + request.addParameter(PkceParameterNames.CODE_VERIFIER, "code-verifier-2"); + assertThatExceptionOfType(OAuth2AuthenticationException.class).isThrownBy(() -> this.converter.convert(request)) + .extracting(OAuth2AuthenticationException::getError) + .extracting("errorCode") + .isEqualTo(OAuth2ErrorCodes.INVALID_REQUEST); + } + private static MockHttpServletRequest createPkceTokenRequest() { MockHttpServletRequest request = new MockHttpServletRequest(); request.addParameter(OAuth2ParameterNames.GRANT_TYPE, AuthorizationGrantType.AUTHORIZATION_CODE.getValue());