Forráskód Böngészése

Add client secret POST authentication method support

Added support for client secret POST authentication method.
Added validation of client authentication method when
authenticating a client.

Closes gh-134
Anoop Garlapati 4 éve
szülő
commit
e1f491bd61
17 módosított fájl, 304 hozzáadás és 20 törlés
  1. 5 0
      oauth2-authorization-server/src/main/java/org/springframework/security/oauth2/server/authorization/authentication/OAuth2ClientAuthenticationProvider.java
  2. 17 0
      oauth2-authorization-server/src/main/java/org/springframework/security/oauth2/server/authorization/authentication/OAuth2ClientAuthenticationToken.java
  3. 3 1
      oauth2-authorization-server/src/main/java/org/springframework/security/oauth2/server/authorization/web/ClientSecretBasicAuthenticationConverter.java
  4. 84 0
      oauth2-authorization-server/src/main/java/org/springframework/security/oauth2/server/authorization/web/ClientSecretPostAuthenticationConverter.java
  5. 1 0
      oauth2-authorization-server/src/main/java/org/springframework/security/oauth2/server/authorization/web/OAuth2ClientAuthenticationFilter.java
  6. 1 3
      oauth2-authorization-server/src/test/java/org/springframework/security/config/annotation/web/configurers/oauth2/server/authorization/OAuth2AuthorizationCodeGrantTests.java
  7. 2 1
      oauth2-authorization-server/src/test/java/org/springframework/security/oauth2/server/authorization/authentication/OAuth2AuthorizationCodeAuthenticationProviderTests.java
  8. 24 7
      oauth2-authorization-server/src/test/java/org/springframework/security/oauth2/server/authorization/authentication/OAuth2ClientAuthenticationProviderTests.java
  9. 26 3
      oauth2-authorization-server/src/test/java/org/springframework/security/oauth2/server/authorization/authentication/OAuth2ClientAuthenticationTokenTests.java
  10. 2 1
      oauth2-authorization-server/src/test/java/org/springframework/security/oauth2/server/authorization/authentication/OAuth2ClientCredentialsAuthenticationProviderTests.java
  11. 2 1
      oauth2-authorization-server/src/test/java/org/springframework/security/oauth2/server/authorization/authentication/OAuth2RefreshTokenAuthenticationProviderTests.java
  12. 2 1
      oauth2-authorization-server/src/test/java/org/springframework/security/oauth2/server/authorization/authentication/OAuth2TokenRevocationAuthenticationProviderTests.java
  13. 12 0
      oauth2-authorization-server/src/test/java/org/springframework/security/oauth2/server/authorization/client/TestRegisteredClients.java
  14. 3 0
      oauth2-authorization-server/src/test/java/org/springframework/security/oauth2/server/authorization/web/ClientSecretBasicAuthenticationConverterTests.java
  15. 115 0
      oauth2-authorization-server/src/test/java/org/springframework/security/oauth2/server/authorization/web/ClientSecretPostAuthenticationConverterTests.java
  16. 3 2
      oauth2-authorization-server/src/test/java/org/springframework/security/oauth2/server/authorization/web/OAuth2ClientAuthenticationFilterTests.java
  17. 2 0
      oauth2-authorization-server/src/test/java/org/springframework/security/oauth2/server/authorization/web/PublicClientAuthenticationConverterTests.java

+ 5 - 0
oauth2-authorization-server/src/main/java/org/springframework/security/oauth2/server/authorization/authentication/OAuth2ClientAuthenticationProvider.java

@@ -84,6 +84,11 @@ public class OAuth2ClientAuthenticationProvider implements AuthenticationProvide
 
 		boolean authenticatedCredentials = false;
 
+		if (!registeredClient.getClientAuthenticationMethods().contains(
+				clientAuthentication.getClientAuthenticationMethod())) {
+			throwInvalidClient();
+		}
+
 		if (clientAuthentication.getCredentials() != null) {
 			String clientSecret = clientAuthentication.getCredentials().toString();
 			// TODO Use PasswordEncoder.matches()

+ 17 - 0
oauth2-authorization-server/src/main/java/org/springframework/security/oauth2/server/authorization/authentication/OAuth2ClientAuthenticationToken.java

@@ -18,6 +18,7 @@ package org.springframework.security.oauth2.server.authorization.authentication;
 import org.springframework.lang.Nullable;
 import org.springframework.security.authentication.AbstractAuthenticationToken;
 import org.springframework.security.core.Authentication;
+import org.springframework.security.oauth2.core.ClientAuthenticationMethod;
 import org.springframework.security.oauth2.server.authorization.Version;
 import org.springframework.security.oauth2.server.authorization.client.RegisteredClient;
 import org.springframework.util.Assert;
@@ -30,6 +31,7 @@ import java.util.Map;
  *
  * @author Joe Grandja
  * @author Patryk Kostrzewa
+ * @author Anoop Garlapati
  * @since 0.0.1
  * @see AbstractAuthenticationToken
  * @see RegisteredClient
@@ -39,6 +41,7 @@ public class OAuth2ClientAuthenticationToken extends AbstractAuthenticationToken
 	private static final long serialVersionUID = Version.SERIAL_VERSION_UID;
 	private String clientId;
 	private String clientSecret;
+	private ClientAuthenticationMethod clientAuthenticationMethod;
 	private Map<String, Object> additionalParameters;
 	private RegisteredClient registeredClient;
 
@@ -47,13 +50,17 @@ public class OAuth2ClientAuthenticationToken extends AbstractAuthenticationToken
 	 *
 	 * @param clientId the client identifier
 	 * @param clientSecret the client secret
+	 * @param clientAuthenticationMethod the authentication method used by the client
 	 * @param additionalParameters the additional parameters
 	 */
 	public OAuth2ClientAuthenticationToken(String clientId, String clientSecret,
+			ClientAuthenticationMethod clientAuthenticationMethod,
 			@Nullable Map<String, Object> additionalParameters) {
 		this(clientId, additionalParameters);
 		Assert.hasText(clientSecret, "clientSecret cannot be empty");
+		Assert.notNull(clientAuthenticationMethod, "clientAuthenticationMethod cannot be null");
 		this.clientSecret = clientSecret;
+		this.clientAuthenticationMethod = clientAuthenticationMethod;
 	}
 
 	/**
@@ -69,6 +76,7 @@ public class OAuth2ClientAuthenticationToken extends AbstractAuthenticationToken
 		this.clientId = clientId;
 		this.additionalParameters = additionalParameters != null ?
 				Collections.unmodifiableMap(additionalParameters) : null;
+		this.clientAuthenticationMethod = ClientAuthenticationMethod.NONE;
 	}
 
 	/**
@@ -112,4 +120,13 @@ public class OAuth2ClientAuthenticationToken extends AbstractAuthenticationToken
 	public @Nullable RegisteredClient getRegisteredClient() {
 		return this.registeredClient;
 	}
+
+	/**
+	 * Returns the {@link ClientAuthenticationMethod client authentication method}.
+	 *
+	 * @return the {@link ClientAuthenticationMethod}
+	 */
+	public @Nullable ClientAuthenticationMethod getClientAuthenticationMethod() {
+		return this.clientAuthenticationMethod;
+	}
 }

+ 3 - 1
oauth2-authorization-server/src/main/java/org/springframework/security/oauth2/server/authorization/web/ClientSecretBasicAuthenticationConverter.java

@@ -17,6 +17,7 @@ package org.springframework.security.oauth2.server.authorization.web;
 
 import org.springframework.http.HttpHeaders;
 import org.springframework.security.core.Authentication;
+import org.springframework.security.oauth2.core.ClientAuthenticationMethod;
 import org.springframework.security.oauth2.core.OAuth2AuthenticationException;
 import org.springframework.security.oauth2.core.OAuth2Error;
 import org.springframework.security.oauth2.core.OAuth2ErrorCodes;
@@ -85,7 +86,8 @@ public class ClientSecretBasicAuthenticationConverter implements AuthenticationC
 			throw new OAuth2AuthenticationException(new OAuth2Error(OAuth2ErrorCodes.INVALID_REQUEST), ex);
 		}
 
-		return new OAuth2ClientAuthenticationToken(clientID, clientSecret, extractAdditionalParameters(request));
+		return new OAuth2ClientAuthenticationToken(clientID, clientSecret, ClientAuthenticationMethod.BASIC,
+				extractAdditionalParameters(request));
 	}
 
 	private static Map<String, Object> extractAdditionalParameters(HttpServletRequest request) {

+ 84 - 0
oauth2-authorization-server/src/main/java/org/springframework/security/oauth2/server/authorization/web/ClientSecretPostAuthenticationConverter.java

@@ -0,0 +1,84 @@
+/*
+ * Copyright 2020 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;
+
+import org.springframework.security.core.Authentication;
+import org.springframework.security.oauth2.core.ClientAuthenticationMethod;
+import org.springframework.security.oauth2.core.OAuth2AuthenticationException;
+import org.springframework.security.oauth2.core.OAuth2Error;
+import org.springframework.security.oauth2.core.OAuth2ErrorCodes;
+import org.springframework.security.oauth2.core.endpoint.OAuth2ParameterNames;
+import org.springframework.security.oauth2.server.authorization.authentication.OAuth2ClientAuthenticationToken;
+import org.springframework.security.web.authentication.AuthenticationConverter;
+import org.springframework.util.MultiValueMap;
+import org.springframework.util.StringUtils;
+
+import javax.servlet.http.HttpServletRequest;
+import java.util.Collections;
+import java.util.HashMap;
+import java.util.Map;
+
+/**
+ * Attempts to extract client credentials from POST parameters of {@link HttpServletRequest}
+ * and then converts to an {@link OAuth2ClientAuthenticationToken} used for authenticating the client.
+ *
+ * @author Anoop Garlapati
+ * @since 0.1.0
+ * @see OAuth2ClientAuthenticationToken
+ * @see OAuth2ClientAuthenticationFilter
+ * @see <a target="_blank" href="https://tools.ietf.org/html/rfc6749#section-2.3.1">Section 2.3.1 Client Password</a>
+ */
+public class ClientSecretPostAuthenticationConverter implements AuthenticationConverter {
+
+	@Override
+	public Authentication convert(HttpServletRequest request) {
+		MultiValueMap<String, String> parameters = OAuth2EndpointUtils.getParameters(request);
+
+		// client_id (REQUIRED)
+		String clientId = parameters.getFirst(OAuth2ParameterNames.CLIENT_ID);
+		if (!StringUtils.hasText(clientId)) {
+			return null;
+		}
+
+		if (parameters.get(OAuth2ParameterNames.CLIENT_ID).size() != 1) {
+			throw new OAuth2AuthenticationException(new OAuth2Error(OAuth2ErrorCodes.INVALID_REQUEST));
+		}
+
+		// client_secret (REQUIRED)
+		String clientSecret = parameters.getFirst(OAuth2ParameterNames.CLIENT_SECRET);
+		if (!StringUtils.hasText(clientSecret)) {
+			return null;
+		}
+
+		if (parameters.get(OAuth2ParameterNames.CLIENT_SECRET).size() != 1) {
+			throw new OAuth2AuthenticationException(new OAuth2Error(OAuth2ErrorCodes.INVALID_REQUEST));
+		}
+
+		return new OAuth2ClientAuthenticationToken(clientId, clientSecret, ClientAuthenticationMethod.POST,
+				extractAdditionalParameters(request));
+	}
+
+	private static Map<String, Object> extractAdditionalParameters(HttpServletRequest request) {
+		Map<String, Object> additionalParameters = Collections.emptyMap();
+		if (OAuth2EndpointUtils.matchesPkceTokenRequest(request)) {
+			// Confidential clients can also leverage PKCE
+			additionalParameters = new HashMap<>(OAuth2EndpointUtils.getParameters(request).toSingleValueMap());
+			additionalParameters.remove(OAuth2ParameterNames.CLIENT_ID);
+			additionalParameters.remove(OAuth2ParameterNames.CLIENT_SECRET);
+		}
+		return additionalParameters;
+	}
+}

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

@@ -77,6 +77,7 @@ public class OAuth2ClientAuthenticationFilter extends OncePerRequestFilter {
 		this.authenticationConverter = new DelegatingAuthenticationConverter(
 				Arrays.asList(
 						new ClientSecretBasicAuthenticationConverter(),
+						new ClientSecretPostAuthenticationConverter(),
 						new PublicClientAuthenticationConverter()));
 		this.authenticationSuccessHandler = this::onAuthenticationSuccess;
 		this.authenticationFailureHandler = this::onAuthenticationFailure;

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

@@ -181,9 +181,7 @@ public class OAuth2AuthorizationCodeGrantTests {
 	public void requestWhenPublicClientWithPkceThenReturnAccessTokenResponse() throws Exception {
 		this.spring.register(AuthorizationServerConfiguration.class).autowire();
 
-		RegisteredClient registeredClient = TestRegisteredClients.registeredClient()
-				.clientSecret(null)
-				.clientSettings(clientSettings -> clientSettings.requireProofKey(true))
+		RegisteredClient registeredClient = TestRegisteredClients.registeredPublicClient()
 				.tokenSettings(tokenSettings -> tokenSettings.enableRefreshTokens(false))
 				.build();
 		when(registeredClientRepository.findByClientId(eq(registeredClient.getClientId())))

+ 2 - 1
oauth2-authorization-server/src/test/java/org/springframework/security/oauth2/server/authorization/authentication/OAuth2AuthorizationCodeAuthenticationProviderTests.java

@@ -19,6 +19,7 @@ import org.junit.Before;
 import org.junit.Test;
 import org.mockito.ArgumentCaptor;
 import org.springframework.security.authentication.TestingAuthenticationToken;
+import org.springframework.security.oauth2.core.ClientAuthenticationMethod;
 import org.springframework.security.oauth2.core.OAuth2AuthenticationException;
 import org.springframework.security.oauth2.core.OAuth2ErrorCodes;
 import org.springframework.security.oauth2.core.endpoint.OAuth2AuthorizationRequest;
@@ -119,7 +120,7 @@ public class OAuth2AuthorizationCodeAuthenticationProviderTests {
 	public void authenticateWhenClientPrincipalNotAuthenticatedThenThrowOAuth2AuthenticationException() {
 		RegisteredClient registeredClient = TestRegisteredClients.registeredClient().build();
 		OAuth2ClientAuthenticationToken clientPrincipal = new OAuth2ClientAuthenticationToken(
-				registeredClient.getClientId(), registeredClient.getClientSecret(), null);
+				registeredClient.getClientId(), registeredClient.getClientSecret(), ClientAuthenticationMethod.BASIC, null);
 		OAuth2AuthorizationCodeAuthenticationToken authentication =
 				new OAuth2AuthorizationCodeAuthenticationToken(AUTHORIZATION_CODE, clientPrincipal, null, null);
 		assertThatThrownBy(() -> this.authenticationProvider.authenticate(authentication))

+ 24 - 7
oauth2-authorization-server/src/test/java/org/springframework/security/oauth2/server/authorization/authentication/OAuth2ClientAuthenticationProviderTests.java

@@ -18,6 +18,7 @@ package org.springframework.security.oauth2.server.authorization.authentication;
 import org.junit.Before;
 import org.junit.Test;
 import org.springframework.security.oauth2.core.AuthorizationGrantType;
+import org.springframework.security.oauth2.core.ClientAuthenticationMethod;
 import org.springframework.security.oauth2.core.OAuth2AuthenticationException;
 import org.springframework.security.oauth2.core.OAuth2ErrorCodes;
 import org.springframework.security.oauth2.core.endpoint.OAuth2ParameterNames;
@@ -45,6 +46,7 @@ import static org.mockito.Mockito.when;
  * @author Patryk Kostrzewa
  * @author Joe Grandja
  * @author Daniel Garnier-Moiroux
+ * @author Anoop Garlapati
  */
 public class OAuth2ClientAuthenticationProviderTests {
 	private static final String PLAIN_CODE_VERIFIER = "pkce-key";
@@ -95,7 +97,7 @@ public class OAuth2ClientAuthenticationProviderTests {
 				.thenReturn(registeredClient);
 
 		OAuth2ClientAuthenticationToken authentication = new OAuth2ClientAuthenticationToken(
-				registeredClient.getClientId() + "-invalid", registeredClient.getClientSecret(), null);
+				registeredClient.getClientId() + "-invalid", registeredClient.getClientSecret(), ClientAuthenticationMethod.BASIC, null);
 		assertThatThrownBy(() -> this.authenticationProvider.authenticate(authentication))
 				.isInstanceOf(OAuth2AuthenticationException.class)
 				.extracting(ex -> ((OAuth2AuthenticationException) ex).getError())
@@ -110,7 +112,7 @@ public class OAuth2ClientAuthenticationProviderTests {
 				.thenReturn(registeredClient);
 
 		OAuth2ClientAuthenticationToken authentication = new OAuth2ClientAuthenticationToken(
-				registeredClient.getClientId(), registeredClient.getClientSecret() + "-invalid", null);
+				registeredClient.getClientId(), registeredClient.getClientSecret() + "-invalid", ClientAuthenticationMethod.BASIC, null);
 		assertThatThrownBy(() -> this.authenticationProvider.authenticate(authentication))
 				.isInstanceOf(OAuth2AuthenticationException.class)
 				.extracting(ex -> ((OAuth2AuthenticationException) ex).getError())
@@ -140,7 +142,7 @@ public class OAuth2ClientAuthenticationProviderTests {
 				.thenReturn(registeredClient);
 
 		OAuth2ClientAuthenticationToken authentication = new OAuth2ClientAuthenticationToken(
-				registeredClient.getClientId(), registeredClient.getClientSecret(), null);
+				registeredClient.getClientId(), registeredClient.getClientSecret(), ClientAuthenticationMethod.BASIC, null);
 		OAuth2ClientAuthenticationToken authenticationResult =
 				(OAuth2ClientAuthenticationToken) this.authenticationProvider.authenticate(authentication);
 		assertThat(authenticationResult.isAuthenticated()).isTrue();
@@ -275,7 +277,7 @@ public class OAuth2ClientAuthenticationProviderTests {
 
 	@Test
 	public void authenticateWhenPkceAndPlainMethodAndValidCodeVerifierThenAuthenticated() {
-		RegisteredClient registeredClient = TestRegisteredClients.registeredClient().build();
+		RegisteredClient registeredClient = TestRegisteredClients.registeredPublicClient().build();
 		when(this.registeredClientRepository.findByClientId(eq(registeredClient.getClientId())))
 				.thenReturn(registeredClient);
 
@@ -300,7 +302,7 @@ public class OAuth2ClientAuthenticationProviderTests {
 
 	@Test
 	public void authenticateWhenPkceAndMissingMethodThenDefaultPlainMethodAndAuthenticated() {
-		RegisteredClient registeredClient = TestRegisteredClients.registeredClient().build();
+		RegisteredClient registeredClient = TestRegisteredClients.registeredPublicClient().build();
 		when(this.registeredClientRepository.findByClientId(eq(registeredClient.getClientId())))
 				.thenReturn(registeredClient);
 
@@ -327,7 +329,7 @@ public class OAuth2ClientAuthenticationProviderTests {
 
 	@Test
 	public void authenticateWhenPkceAndS256MethodAndValidCodeVerifierThenAuthenticated() {
-		RegisteredClient registeredClient = TestRegisteredClients.registeredClient().build();
+		RegisteredClient registeredClient = TestRegisteredClients.registeredPublicClient().build();
 		when(this.registeredClientRepository.findByClientId(eq(registeredClient.getClientId())))
 				.thenReturn(registeredClient);
 
@@ -352,7 +354,7 @@ public class OAuth2ClientAuthenticationProviderTests {
 
 	@Test
 	public void authenticateWhenPkceAndUnsupportedCodeChallengeMethodThenThrowOAuth2AuthenticationException() {
-		RegisteredClient registeredClient = TestRegisteredClients.registeredClient().build();
+		RegisteredClient registeredClient = TestRegisteredClients.registeredPublicClient().build();
 		when(this.registeredClientRepository.findByClientId(eq(registeredClient.getClientId())))
 				.thenReturn(registeredClient);
 
@@ -377,6 +379,21 @@ public class OAuth2ClientAuthenticationProviderTests {
 				.isEqualTo(OAuth2ErrorCodes.SERVER_ERROR);
 	}
 
+	@Test
+	public void authenticateWhenClientAuthenticationWithUnregisteredClientAuthenticationMethodThenThrowOAuth2AuthenticationException() {
+		RegisteredClient registeredClient = TestRegisteredClients.registeredClient().build();
+		when(this.registeredClientRepository.findByClientId(eq(registeredClient.getClientId())))
+				.thenReturn(registeredClient);
+
+		OAuth2ClientAuthenticationToken authentication = new OAuth2ClientAuthenticationToken(
+				registeredClient.getClientId(), registeredClient.getClientSecret(), ClientAuthenticationMethod.POST, null);
+		assertThatThrownBy(() -> this.authenticationProvider.authenticate(authentication))
+				.isInstanceOf(OAuth2AuthenticationException.class)
+				.extracting(ex -> ((OAuth2AuthenticationException) ex).getError())
+				.extracting("errorCode")
+				.isEqualTo(OAuth2ErrorCodes.INVALID_CLIENT);
+	}
+
 	private static Map<String, Object> createPkceTokenParameters(String codeVerifier) {
 		Map<String, Object> parameters = new HashMap<>();
 		parameters.put(OAuth2ParameterNames.GRANT_TYPE, AuthorizationGrantType.AUTHORIZATION_CODE.getValue());

+ 26 - 3
oauth2-authorization-server/src/test/java/org/springframework/security/oauth2/server/authorization/authentication/OAuth2ClientAuthenticationTokenTests.java

@@ -16,6 +16,7 @@
 package org.springframework.security.oauth2.server.authorization.authentication;
 
 import org.junit.Test;
+import org.springframework.security.oauth2.core.ClientAuthenticationMethod;
 import org.springframework.security.oauth2.server.authorization.client.RegisteredClient;
 import org.springframework.security.oauth2.server.authorization.client.TestRegisteredClients;
 
@@ -29,23 +30,31 @@ import static org.assertj.core.api.Assertions.assertThatThrownBy;
  * Tests for {@link OAuth2ClientAuthenticationToken}.
  *
  * @author Joe Grandja
+ * @author Anoop Garlapati
  */
 public class OAuth2ClientAuthenticationTokenTests {
 
 	@Test
 	public void constructorWhenClientIdNullThenThrowIllegalArgumentException() {
-		assertThatThrownBy(() -> new OAuth2ClientAuthenticationToken(null, "secret", null))
+		assertThatThrownBy(() -> new OAuth2ClientAuthenticationToken(null, "secret", ClientAuthenticationMethod.BASIC, null))
 				.isInstanceOf(IllegalArgumentException.class)
 				.hasMessage("clientId cannot be empty");
 	}
 
 	@Test
 	public void constructorWhenClientSecretNullThenThrowIllegalArgumentException() {
-		assertThatThrownBy(() -> new OAuth2ClientAuthenticationToken("clientId", null, null))
+		assertThatThrownBy(() -> new OAuth2ClientAuthenticationToken("clientId", null, ClientAuthenticationMethod.BASIC, null))
 				.isInstanceOf(IllegalArgumentException.class)
 				.hasMessage("clientSecret cannot be empty");
 	}
 
+	@Test
+	public void constructorWhenClientAuthenticationMethodNullThenThrowIllegalArgumentException() {
+		assertThatThrownBy(() -> new OAuth2ClientAuthenticationToken("clientId", "clientSecret", null, null))
+				.isInstanceOf(IllegalArgumentException.class)
+				.hasMessage("clientAuthenticationMethod cannot be null");
+	}
+
 	@Test
 	public void constructorWhenRegisteredClientNullThenThrowIllegalArgumentException() {
 		assertThatThrownBy(() -> new OAuth2ClientAuthenticationToken(null))
@@ -55,11 +64,13 @@ public class OAuth2ClientAuthenticationTokenTests {
 
 	@Test
 	public void constructorWhenClientCredentialsProvidedThenCreated() {
-		OAuth2ClientAuthenticationToken authentication = new OAuth2ClientAuthenticationToken("clientId", "secret", null);
+		OAuth2ClientAuthenticationToken authentication = new OAuth2ClientAuthenticationToken("clientId", "secret",
+				ClientAuthenticationMethod.BASIC, null);
 		assertThat(authentication.isAuthenticated()).isFalse();
 		assertThat(authentication.getPrincipal().toString()).isEqualTo("clientId");
 		assertThat(authentication.getCredentials()).isEqualTo("secret");
 		assertThat(authentication.getRegisteredClient()).isNull();
+		assertThat(authentication.getClientAuthenticationMethod()).isEqualTo(ClientAuthenticationMethod.BASIC);
 	}
 
 	@Test
@@ -72,6 +83,7 @@ public class OAuth2ClientAuthenticationTokenTests {
 		assertThat(authentication.getCredentials()).isNull();
 		assertThat(authentication.getAdditionalParameters()).isEqualTo(additionalParameters);
 		assertThat(authentication.getRegisteredClient()).isNull();
+		assertThat(authentication.getClientAuthenticationMethod()).isEqualTo(ClientAuthenticationMethod.NONE);
 	}
 
 	@Test
@@ -83,4 +95,15 @@ public class OAuth2ClientAuthenticationTokenTests {
 		assertThat(authentication.getCredentials()).isNull();
 		assertThat(authentication.getRegisteredClient()).isEqualTo(registeredClient);
 	}
+
+	@Test
+	public void constructorWhenClientCredentialsAndClientAuthenticationMethodProvidedThenCreated() {
+		OAuth2ClientAuthenticationToken authentication = new OAuth2ClientAuthenticationToken("clientId", "secret",
+				ClientAuthenticationMethod.BASIC, null);
+		assertThat(authentication.isAuthenticated()).isFalse();
+		assertThat(authentication.getPrincipal().toString()).isEqualTo("clientId");
+		assertThat(authentication.getCredentials()).isEqualTo("secret");
+		assertThat(authentication.getRegisteredClient()).isNull();
+		assertThat(authentication.getClientAuthenticationMethod()).isEqualTo(ClientAuthenticationMethod.BASIC);
+	}
 }

+ 2 - 1
oauth2-authorization-server/src/test/java/org/springframework/security/oauth2/server/authorization/authentication/OAuth2ClientCredentialsAuthenticationProviderTests.java

@@ -20,6 +20,7 @@ import org.junit.Test;
 import org.mockito.ArgumentCaptor;
 import org.springframework.security.authentication.TestingAuthenticationToken;
 import org.springframework.security.oauth2.core.AuthorizationGrantType;
+import org.springframework.security.oauth2.core.ClientAuthenticationMethod;
 import org.springframework.security.oauth2.core.OAuth2AuthenticationException;
 import org.springframework.security.oauth2.core.OAuth2ErrorCodes;
 import org.springframework.security.oauth2.jose.JoseHeaderNames;
@@ -104,7 +105,7 @@ public class OAuth2ClientCredentialsAuthenticationProviderTests {
 	public void authenticateWhenClientPrincipalNotAuthenticatedThenThrowOAuth2AuthenticationException() {
 		RegisteredClient registeredClient = TestRegisteredClients.registeredClient2().build();
 		OAuth2ClientAuthenticationToken clientPrincipal = new OAuth2ClientAuthenticationToken(
-				registeredClient.getClientId(), registeredClient.getClientSecret(), null);
+				registeredClient.getClientId(), registeredClient.getClientSecret(), ClientAuthenticationMethod.BASIC, null);
 		OAuth2ClientCredentialsAuthenticationToken authentication = new OAuth2ClientCredentialsAuthenticationToken(clientPrincipal);
 
 		assertThatThrownBy(() -> this.authenticationProvider.authenticate(authentication))

+ 2 - 1
oauth2-authorization-server/src/test/java/org/springframework/security/oauth2/server/authorization/authentication/OAuth2RefreshTokenAuthenticationProviderTests.java

@@ -20,6 +20,7 @@ import org.junit.Test;
 import org.mockito.ArgumentCaptor;
 import org.springframework.security.authentication.TestingAuthenticationToken;
 import org.springframework.security.oauth2.core.AuthorizationGrantType;
+import org.springframework.security.oauth2.core.ClientAuthenticationMethod;
 import org.springframework.security.oauth2.core.OAuth2AuthenticationException;
 import org.springframework.security.oauth2.core.OAuth2ErrorCodes;
 import org.springframework.security.oauth2.core.OAuth2RefreshToken;
@@ -234,7 +235,7 @@ public class OAuth2RefreshTokenAuthenticationProviderTests {
 	public void authenticateWhenClientPrincipalNotAuthenticatedThenThrowOAuth2AuthenticationException() {
 		RegisteredClient registeredClient = TestRegisteredClients.registeredClient().build();
 		OAuth2ClientAuthenticationToken clientPrincipal = new OAuth2ClientAuthenticationToken(
-				registeredClient.getClientId(), registeredClient.getClientSecret(), null);
+				registeredClient.getClientId(), registeredClient.getClientSecret(), ClientAuthenticationMethod.BASIC, null);
 		OAuth2RefreshTokenAuthenticationToken authentication = new OAuth2RefreshTokenAuthenticationToken(
 				"refresh-token", clientPrincipal);
 

+ 2 - 1
oauth2-authorization-server/src/test/java/org/springframework/security/oauth2/server/authorization/authentication/OAuth2TokenRevocationAuthenticationProviderTests.java

@@ -19,6 +19,7 @@ import org.junit.Before;
 import org.junit.Test;
 import org.mockito.ArgumentCaptor;
 import org.springframework.security.authentication.TestingAuthenticationToken;
+import org.springframework.security.oauth2.core.ClientAuthenticationMethod;
 import org.springframework.security.oauth2.core.OAuth2AccessToken;
 import org.springframework.security.oauth2.core.OAuth2AuthenticationException;
 import org.springframework.security.oauth2.core.OAuth2ErrorCodes;
@@ -86,7 +87,7 @@ public class OAuth2TokenRevocationAuthenticationProviderTests {
 	public void authenticateWhenClientPrincipalNotAuthenticatedThenThrowOAuth2AuthenticationException() {
 		RegisteredClient registeredClient = TestRegisteredClients.registeredClient().build();
 		OAuth2ClientAuthenticationToken clientPrincipal = new OAuth2ClientAuthenticationToken(
-				registeredClient.getClientId(), registeredClient.getClientSecret(), null);
+				registeredClient.getClientId(), registeredClient.getClientSecret(), ClientAuthenticationMethod.BASIC, null);
 		OAuth2TokenRevocationAuthenticationToken authentication = new OAuth2TokenRevocationAuthenticationToken(
 				"token", clientPrincipal, TokenType.ACCESS_TOKEN.getValue());
 		assertThatThrownBy(() -> this.authenticationProvider.authenticate(authentication))

+ 12 - 0
oauth2-authorization-server/src/test/java/org/springframework/security/oauth2/server/authorization/client/TestRegisteredClients.java

@@ -51,4 +51,16 @@ public class TestRegisteredClients {
 				.scope("scope1")
 				.scope("scope2");
 	}
+
+	public static RegisteredClient.Builder registeredPublicClient() {
+		return RegisteredClient.withId("registration-3")
+				.clientId("client-3")
+				.authorizationGrantType(AuthorizationGrantType.AUTHORIZATION_CODE)
+				.clientAuthenticationMethod(ClientAuthenticationMethod.NONE)
+				.redirectUri("https://example.com")
+				.scope("openid")
+				.scope("profile")
+				.scope("email")
+				.clientSettings(clientSettings -> clientSettings.requireProofKey(true));
+	}
 }

+ 3 - 0
oauth2-authorization-server/src/test/java/org/springframework/security/oauth2/server/authorization/web/ClientSecretBasicAuthenticationConverterTests.java

@@ -20,6 +20,7 @@ import org.springframework.http.HttpHeaders;
 import org.springframework.mock.web.MockHttpServletRequest;
 import org.springframework.security.core.Authentication;
 import org.springframework.security.oauth2.core.AuthorizationGrantType;
+import org.springframework.security.oauth2.core.ClientAuthenticationMethod;
 import org.springframework.security.oauth2.core.OAuth2AuthenticationException;
 import org.springframework.security.oauth2.core.OAuth2ErrorCodes;
 import org.springframework.security.oauth2.core.endpoint.OAuth2ParameterNames;
@@ -98,6 +99,7 @@ public class ClientSecretBasicAuthenticationConverterTests {
 		OAuth2ClientAuthenticationToken authentication = (OAuth2ClientAuthenticationToken) this.converter.convert(request);
 		assertThat(authentication.getPrincipal()).isEqualTo("clientId");
 		assertThat(authentication.getCredentials()).isEqualTo("secret");
+		assertThat(authentication.getClientAuthenticationMethod()).isEqualTo(ClientAuthenticationMethod.BASIC);
 	}
 
 	@Test
@@ -107,6 +109,7 @@ public class ClientSecretBasicAuthenticationConverterTests {
 		OAuth2ClientAuthenticationToken authentication = (OAuth2ClientAuthenticationToken) this.converter.convert(request);
 		assertThat(authentication.getPrincipal()).isEqualTo("clientId");
 		assertThat(authentication.getCredentials()).isEqualTo("secret");
+		assertThat(authentication.getClientAuthenticationMethod()).isEqualTo(ClientAuthenticationMethod.BASIC);
 		assertThat(authentication.getAdditionalParameters())
 				.containsOnly(
 						entry(OAuth2ParameterNames.GRANT_TYPE, AuthorizationGrantType.AUTHORIZATION_CODE.getValue()),

+ 115 - 0
oauth2-authorization-server/src/test/java/org/springframework/security/oauth2/server/authorization/web/ClientSecretPostAuthenticationConverterTests.java

@@ -0,0 +1,115 @@
+/*
+ * Copyright 2020 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;
+
+import org.junit.Test;
+import org.springframework.mock.web.MockHttpServletRequest;
+import org.springframework.security.core.Authentication;
+import org.springframework.security.oauth2.core.AuthorizationGrantType;
+import org.springframework.security.oauth2.core.ClientAuthenticationMethod;
+import org.springframework.security.oauth2.core.OAuth2AuthenticationException;
+import org.springframework.security.oauth2.core.OAuth2ErrorCodes;
+import org.springframework.security.oauth2.core.endpoint.OAuth2ParameterNames;
+import org.springframework.security.oauth2.core.endpoint.PkceParameterNames;
+import org.springframework.security.oauth2.server.authorization.authentication.OAuth2ClientAuthenticationToken;
+
+import static org.assertj.core.api.Assertions.assertThat;
+import static org.assertj.core.api.Assertions.assertThatThrownBy;
+import static org.assertj.core.api.Assertions.entry;
+
+/**
+ * Tests for {@link ClientSecretPostAuthenticationConverter}.
+ *
+ * @author Anoop Garlapati
+ */
+public class ClientSecretPostAuthenticationConverterTests {
+	private final ClientSecretPostAuthenticationConverter converter = new ClientSecretPostAuthenticationConverter();
+
+	@Test
+	public void convertWhenMissingClientIdThenReturnNull() {
+		MockHttpServletRequest request = new MockHttpServletRequest();
+		Authentication authentication = this.converter.convert(request);
+		assertThat(authentication).isNull();
+	}
+
+	@Test
+	public void convertWhenMultipleClientIdsThenInvalidRequestError() {
+		MockHttpServletRequest request = new MockHttpServletRequest();
+		request.addParameter(OAuth2ParameterNames.CLIENT_ID, "client-1");
+		request.addParameter(OAuth2ParameterNames.CLIENT_ID, "client-2");
+		assertThatThrownBy(() -> this.converter.convert(request))
+				.isInstanceOf(OAuth2AuthenticationException.class)
+				.extracting(ex -> ((OAuth2AuthenticationException) ex).getError())
+				.extracting("errorCode")
+				.isEqualTo(OAuth2ErrorCodes.INVALID_REQUEST);
+	}
+
+	@Test
+	public void convertWhenMissingClientSecretThenReturnNull() {
+		MockHttpServletRequest request = new MockHttpServletRequest();
+		request.addParameter(OAuth2ParameterNames.CLIENT_ID, "client-1");
+		Authentication authentication = this.converter.convert(request);
+		assertThat(authentication).isNull();
+	}
+
+	@Test
+	public void convertWhenMultipleClientSecretsThenInvalidRequestError() {
+		MockHttpServletRequest request = new MockHttpServletRequest();
+		request.addParameter(OAuth2ParameterNames.CLIENT_ID, "client-1");
+		request.addParameter(OAuth2ParameterNames.CLIENT_SECRET, "client-secret-1");
+		request.addParameter(OAuth2ParameterNames.CLIENT_SECRET, "client-secret-2");
+		assertThatThrownBy(() -> this.converter.convert(request))
+				.isInstanceOf(OAuth2AuthenticationException.class)
+				.extracting(ex -> ((OAuth2AuthenticationException) ex).getError())
+				.extracting("errorCode")
+				.isEqualTo(OAuth2ErrorCodes.INVALID_REQUEST);
+	}
+
+	@Test
+	public void convertWhenPostWithValidCredentialsThenReturnClientAuthenticationToken() {
+		MockHttpServletRequest request = new MockHttpServletRequest();
+		request.addParameter(OAuth2ParameterNames.CLIENT_ID, "client-1");
+		request.addParameter(OAuth2ParameterNames.CLIENT_SECRET, "client-secret");
+		OAuth2ClientAuthenticationToken authentication = (OAuth2ClientAuthenticationToken) this.converter.convert(request);
+		assertThat(authentication.getPrincipal()).isEqualTo("client-1");
+		assertThat(authentication.getCredentials()).isEqualTo("client-secret");
+		assertThat(authentication.getClientAuthenticationMethod()).isEqualTo(ClientAuthenticationMethod.POST);
+	}
+
+	@Test
+	public void convertWhenConfidentialClientWithPkceParametersThenAdditionalParametersIncluded() {
+		MockHttpServletRequest request = createPkceTokenRequest();
+		request.addParameter(OAuth2ParameterNames.CLIENT_ID, "client-1");
+		request.addParameter(OAuth2ParameterNames.CLIENT_SECRET, "client-secret");
+		OAuth2ClientAuthenticationToken authentication = (OAuth2ClientAuthenticationToken) this.converter.convert(request);
+		assertThat(authentication.getPrincipal()).isEqualTo("client-1");
+		assertThat(authentication.getCredentials()).isEqualTo("client-secret");
+		assertThat(authentication.getClientAuthenticationMethod()).isEqualTo(ClientAuthenticationMethod.POST);
+		assertThat(authentication.getAdditionalParameters())
+				.containsOnly(
+						entry(OAuth2ParameterNames.GRANT_TYPE, AuthorizationGrantType.AUTHORIZATION_CODE.getValue()),
+						entry(OAuth2ParameterNames.CODE, "code"),
+						entry(PkceParameterNames.CODE_VERIFIER, "code-verifier-1"));
+	}
+
+	private static MockHttpServletRequest createPkceTokenRequest() {
+		MockHttpServletRequest request = new MockHttpServletRequest();
+		request.addParameter(OAuth2ParameterNames.GRANT_TYPE, AuthorizationGrantType.AUTHORIZATION_CODE.getValue());
+		request.addParameter(OAuth2ParameterNames.CODE, "code");
+		request.addParameter(PkceParameterNames.CODE_VERIFIER, "code-verifier-1");
+		return request;
+	}
+}

+ 3 - 2
oauth2-authorization-server/src/test/java/org/springframework/security/oauth2/server/authorization/web/OAuth2ClientAuthenticationFilterTests.java

@@ -27,6 +27,7 @@ import org.springframework.mock.web.MockHttpServletResponse;
 import org.springframework.security.authentication.AuthenticationManager;
 import org.springframework.security.core.Authentication;
 import org.springframework.security.core.context.SecurityContextHolder;
+import org.springframework.security.oauth2.core.ClientAuthenticationMethod;
 import org.springframework.security.oauth2.core.OAuth2AuthenticationException;
 import org.springframework.security.oauth2.core.OAuth2Error;
 import org.springframework.security.oauth2.core.OAuth2ErrorCodes;
@@ -162,7 +163,7 @@ public class OAuth2ClientAuthenticationFilterTests {
 	@Test
 	public void doFilterWhenRequestMatchesAndBadCredentialsThenInvalidClientError() throws Exception {
 		when(this.authenticationConverter.convert(any(HttpServletRequest.class))).thenReturn(
-				new OAuth2ClientAuthenticationToken("clientId", "invalid-secret", null));
+				new OAuth2ClientAuthenticationToken("clientId", "invalid-secret", ClientAuthenticationMethod.BASIC, null));
 		when(this.authenticationManager.authenticate(any(Authentication.class))).thenThrow(
 				new OAuth2AuthenticationException(new OAuth2Error(OAuth2ErrorCodes.INVALID_CLIENT)));
 
@@ -185,7 +186,7 @@ public class OAuth2ClientAuthenticationFilterTests {
 	public void doFilterWhenRequestMatchesAndValidCredentialsThenProcessed() throws Exception {
 		RegisteredClient registeredClient = TestRegisteredClients.registeredClient().build();
 		when(this.authenticationConverter.convert(any(HttpServletRequest.class))).thenReturn(
-				new OAuth2ClientAuthenticationToken(registeredClient.getClientId(), registeredClient.getClientSecret(), null));
+				new OAuth2ClientAuthenticationToken(registeredClient.getClientId(), registeredClient.getClientSecret(), ClientAuthenticationMethod.BASIC, null));
 		when(this.authenticationManager.authenticate(any(Authentication.class))).thenReturn(
 				new OAuth2ClientAuthenticationToken(registeredClient));
 

+ 2 - 0
oauth2-authorization-server/src/test/java/org/springframework/security/oauth2/server/authorization/web/PublicClientAuthenticationConverterTests.java

@@ -19,6 +19,7 @@ import org.junit.Test;
 import org.springframework.mock.web.MockHttpServletRequest;
 import org.springframework.security.core.Authentication;
 import org.springframework.security.oauth2.core.AuthorizationGrantType;
+import org.springframework.security.oauth2.core.ClientAuthenticationMethod;
 import org.springframework.security.oauth2.core.OAuth2AuthenticationException;
 import org.springframework.security.oauth2.core.OAuth2ErrorCodes;
 import org.springframework.security.oauth2.core.endpoint.OAuth2ParameterNames;
@@ -82,6 +83,7 @@ public class PublicClientAuthenticationConverterTests {
 		MockHttpServletRequest request = createPkceTokenRequest();
 		OAuth2ClientAuthenticationToken authentication = (OAuth2ClientAuthenticationToken) this.converter.convert(request);
 		assertThat(authentication.getPrincipal()).isEqualTo("client-1");
+		assertThat(authentication.getClientAuthenticationMethod()).isEqualTo(ClientAuthenticationMethod.NONE);
 		assertThat(authentication.getAdditionalParameters())
 				.containsOnly(
 						entry(OAuth2ParameterNames.GRANT_TYPE, AuthorizationGrantType.AUTHORIZATION_CODE.getValue()),