|
@@ -15,10 +15,20 @@
|
|
|
*/
|
|
|
package org.springframework.security.oauth2.server.authorization.authentication;
|
|
|
|
|
|
+import java.nio.charset.StandardCharsets;
|
|
|
import java.time.Instant;
|
|
|
+import java.time.temporal.ChronoUnit;
|
|
|
+import java.util.Collections;
|
|
|
import java.util.HashMap;
|
|
|
import java.util.Map;
|
|
|
|
|
|
+import javax.crypto.SecretKey;
|
|
|
+import javax.crypto.spec.SecretKeySpec;
|
|
|
+
|
|
|
+import com.nimbusds.jose.jwk.JWKSet;
|
|
|
+import com.nimbusds.jose.jwk.OctetSequenceKey;
|
|
|
+import com.nimbusds.jose.jwk.source.JWKSource;
|
|
|
+import com.nimbusds.jose.proc.SecurityContext;
|
|
|
import org.junit.Before;
|
|
|
import org.junit.Test;
|
|
|
|
|
@@ -31,25 +41,35 @@ import org.springframework.security.oauth2.core.OAuth2ErrorCodes;
|
|
|
import org.springframework.security.oauth2.core.OAuth2TokenType;
|
|
|
import org.springframework.security.oauth2.core.endpoint.OAuth2ParameterNames;
|
|
|
import org.springframework.security.oauth2.core.endpoint.PkceParameterNames;
|
|
|
+import org.springframework.security.oauth2.jose.TestJwks;
|
|
|
+import org.springframework.security.oauth2.jose.TestKeys;
|
|
|
+import org.springframework.security.oauth2.jose.jws.MacAlgorithm;
|
|
|
+import org.springframework.security.oauth2.jose.jws.SignatureAlgorithm;
|
|
|
+import org.springframework.security.oauth2.jwt.BadJwtException;
|
|
|
+import org.springframework.security.oauth2.jwt.JoseHeader;
|
|
|
import org.springframework.security.oauth2.jwt.Jwt;
|
|
|
-import org.springframework.security.oauth2.jwt.JwtDecoderFactory;
|
|
|
+import org.springframework.security.oauth2.jwt.JwtClaimsSet;
|
|
|
+import org.springframework.security.oauth2.jwt.JwtEncoder;
|
|
|
+import org.springframework.security.oauth2.jwt.JwtValidationException;
|
|
|
+import org.springframework.security.oauth2.jwt.NimbusJwsEncoder;
|
|
|
import org.springframework.security.oauth2.server.authorization.OAuth2Authorization;
|
|
|
import org.springframework.security.oauth2.server.authorization.OAuth2AuthorizationService;
|
|
|
import org.springframework.security.oauth2.server.authorization.TestOAuth2Authorizations;
|
|
|
import org.springframework.security.oauth2.server.authorization.client.RegisteredClient;
|
|
|
import org.springframework.security.oauth2.server.authorization.client.RegisteredClientRepository;
|
|
|
import org.springframework.security.oauth2.server.authorization.client.TestRegisteredClients;
|
|
|
+import org.springframework.security.oauth2.server.authorization.config.ClientSettings;
|
|
|
import org.springframework.security.oauth2.server.authorization.config.ProviderSettings;
|
|
|
-import org.springframework.test.util.ReflectionTestUtils;
|
|
|
+import org.springframework.web.util.UriComponentsBuilder;
|
|
|
|
|
|
import static org.assertj.core.api.Assertions.assertThat;
|
|
|
import static org.assertj.core.api.Assertions.assertThatThrownBy;
|
|
|
import static org.mockito.ArgumentMatchers.any;
|
|
|
import static org.mockito.ArgumentMatchers.eq;
|
|
|
import static org.mockito.Mockito.mock;
|
|
|
-import static org.mockito.Mockito.never;
|
|
|
import static org.mockito.Mockito.spy;
|
|
|
import static org.mockito.Mockito.verify;
|
|
|
+import static org.mockito.Mockito.verifyNoInteractions;
|
|
|
import static org.mockito.Mockito.when;
|
|
|
|
|
|
/**
|
|
@@ -80,13 +100,14 @@ public class OAuth2ClientAuthenticationProviderTests {
|
|
|
private OAuth2AuthorizationService authorizationService;
|
|
|
private OAuth2ClientAuthenticationProvider authenticationProvider;
|
|
|
private PasswordEncoder passwordEncoder;
|
|
|
- private JwtDecoderFactory<RegisteredClient> jwtDecoderFactory;
|
|
|
+ private ProviderSettings providerSettings;
|
|
|
|
|
|
@Before
|
|
|
public void setUp() {
|
|
|
this.registeredClientRepository = mock(RegisteredClientRepository.class);
|
|
|
this.authorizationService = mock(OAuth2AuthorizationService.class);
|
|
|
- this.authenticationProvider = new OAuth2ClientAuthenticationProvider(this.registeredClientRepository, this.authorizationService);
|
|
|
+ this.authenticationProvider = new OAuth2ClientAuthenticationProvider(
|
|
|
+ this.registeredClientRepository, this.authorizationService);
|
|
|
this.passwordEncoder = spy(new PasswordEncoder() {
|
|
|
@Override
|
|
|
public String encode(CharSequence rawPassword) {
|
|
@@ -99,9 +120,8 @@ public class OAuth2ClientAuthenticationProviderTests {
|
|
|
}
|
|
|
});
|
|
|
this.authenticationProvider.setPasswordEncoder(this.passwordEncoder);
|
|
|
- this.authenticationProvider.setProviderSettings(ProviderSettings.builder().issuer("https://auth-server.com").build());
|
|
|
- this.jwtDecoderFactory = mock(JwtDecoderFactory.class);
|
|
|
- ReflectionTestUtils.setField(this.authenticationProvider, "jwtDecoderFactory", this.jwtDecoderFactory);
|
|
|
+ this.providerSettings = ProviderSettings.builder().issuer("https://auth-server.com").build();
|
|
|
+ this.authenticationProvider.setProviderSettings(this.providerSettings);
|
|
|
}
|
|
|
|
|
|
@Test
|
|
@@ -147,6 +167,23 @@ public class OAuth2ClientAuthenticationProviderTests {
|
|
|
});
|
|
|
}
|
|
|
|
|
|
+ @Test
|
|
|
+ public void authenticateWhenUnsupportedClientAuthenticationMethodThenThrowOAuth2AuthenticationException() {
|
|
|
+ RegisteredClient registeredClient = TestRegisteredClients.registeredClient().build();
|
|
|
+ when(this.registeredClientRepository.findByClientId(eq(registeredClient.getClientId())))
|
|
|
+ .thenReturn(registeredClient);
|
|
|
+
|
|
|
+ OAuth2ClientAuthenticationToken authentication = new OAuth2ClientAuthenticationToken(
|
|
|
+ registeredClient.getClientId(), ClientAuthenticationMethod.CLIENT_SECRET_POST, registeredClient.getClientSecret(), null);
|
|
|
+ assertThatThrownBy(() -> this.authenticationProvider.authenticate(authentication))
|
|
|
+ .isInstanceOf(OAuth2AuthenticationException.class)
|
|
|
+ .extracting(ex -> ((OAuth2AuthenticationException) ex).getError())
|
|
|
+ .satisfies(error -> {
|
|
|
+ assertThat(error.getErrorCode()).isEqualTo(OAuth2ErrorCodes.INVALID_CLIENT);
|
|
|
+ assertThat(error.getDescription()).contains("authentication_method");
|
|
|
+ });
|
|
|
+ }
|
|
|
+
|
|
|
@Test
|
|
|
public void authenticateWhenInvalidClientSecretThenThrowOAuth2AuthenticationException() {
|
|
|
RegisteredClient registeredClient = TestRegisteredClients.registeredClient().build();
|
|
@@ -220,146 +257,6 @@ public class OAuth2ClientAuthenticationProviderTests {
|
|
|
assertThat(authenticationResult.getRegisteredClient()).isEqualTo(registeredClient);
|
|
|
}
|
|
|
|
|
|
- @Test
|
|
|
- public void authenticateWhenJwtBearerAndClientNotSupportingItThenThrowOAuth2AuthenticationException() {
|
|
|
- RegisteredClient registeredClient = TestRegisteredClients.registeredClient().build();
|
|
|
- when(this.registeredClientRepository.findByClientId(eq(registeredClient.getClientId())))
|
|
|
- .thenReturn(registeredClient);
|
|
|
- OAuth2ClientAuthenticationToken authentication = new OAuth2ClientAuthenticationToken(
|
|
|
- registeredClient.getClientId(), JWT_CLIENT_ASSERTION_AUTHENTICATION_METHOD,
|
|
|
- registeredClient.getClientSecret(), null);
|
|
|
-
|
|
|
- assertThatThrownBy(() -> this.authenticationProvider.authenticate(authentication))
|
|
|
- .isInstanceOf(OAuth2AuthenticationException.class)
|
|
|
- .extracting(ex -> ((OAuth2AuthenticationException) ex).getError())
|
|
|
- .extracting("errorCode")
|
|
|
- .isEqualTo(OAuth2ErrorCodes.INVALID_CLIENT);
|
|
|
-
|
|
|
- verify(this.jwtDecoderFactory, never()).createDecoder(any());
|
|
|
- }
|
|
|
-
|
|
|
- @Test
|
|
|
- public void authenticateWhenClientJwtAssertionAndPrivateJwtAndFailedCreateDecoderThenThrowOAuth2AuthenticationException() {
|
|
|
- RegisteredClient registeredClient = TestRegisteredClients.registeredClient()
|
|
|
- .clientAuthenticationMethod(ClientAuthenticationMethod.PRIVATE_KEY_JWT)
|
|
|
- .build();
|
|
|
- when(this.registeredClientRepository.findByClientId(eq(registeredClient.getClientId())))
|
|
|
- .thenReturn(registeredClient);
|
|
|
- when(this.jwtDecoderFactory.createDecoder(any()))
|
|
|
- .thenThrow(new OAuth2AuthenticationException(OAuth2ErrorCodes.INVALID_CLIENT));
|
|
|
- OAuth2ClientAuthenticationToken authentication = new OAuth2ClientAuthenticationToken(
|
|
|
- "https://auth-server.com", registeredClient.getClientId(), JWT_CLIENT_ASSERTION_AUTHENTICATION_METHOD,
|
|
|
- registeredClient.getClientSecret(), null);
|
|
|
-
|
|
|
- assertThatThrownBy(() -> this.authenticationProvider.authenticate(authentication))
|
|
|
- .isInstanceOf(OAuth2AuthenticationException.class)
|
|
|
- .extracting(ex -> ((OAuth2AuthenticationException) ex).getError())
|
|
|
- .extracting("errorCode")
|
|
|
- .isEqualTo(OAuth2ErrorCodes.INVALID_CLIENT);
|
|
|
- }
|
|
|
-
|
|
|
- @Test
|
|
|
- public void authenticateWhenClientJwtAssertionAndPrivateKeyJwtAndFailedVerifyTokenThenThrowOAuth2AuthenticationException() {
|
|
|
- RegisteredClient registeredClient = TestRegisteredClients.registeredClient()
|
|
|
- .clientAuthenticationMethod(ClientAuthenticationMethod.PRIVATE_KEY_JWT)
|
|
|
- .build();
|
|
|
- when(this.registeredClientRepository.findByClientId(eq(registeredClient.getClientId())))
|
|
|
- .thenReturn(registeredClient);
|
|
|
- when(this.jwtDecoderFactory.createDecoder(any()))
|
|
|
- .thenReturn(s -> { throw new OAuth2AuthenticationException(OAuth2ErrorCodes.INVALID_CLIENT); });
|
|
|
- OAuth2ClientAuthenticationToken authentication = new OAuth2ClientAuthenticationToken(
|
|
|
- "https://auth-server.com", registeredClient.getClientId(), JWT_CLIENT_ASSERTION_AUTHENTICATION_METHOD,
|
|
|
- registeredClient.getClientSecret(), null);
|
|
|
-
|
|
|
- assertThatThrownBy(() -> this.authenticationProvider.authenticate(authentication))
|
|
|
- .isInstanceOf(OAuth2AuthenticationException.class)
|
|
|
- .extracting(ex -> ((OAuth2AuthenticationException) ex).getError())
|
|
|
- .extracting("errorCode")
|
|
|
- .isEqualTo(OAuth2ErrorCodes.INVALID_CLIENT);
|
|
|
- }
|
|
|
-
|
|
|
- @Test
|
|
|
- public void authenticateWhenClientJwtAssertionAndBadAudienceThenThrowOAuth2AuthenticationException() {
|
|
|
- RegisteredClient registeredClient = TestRegisteredClients.registeredClient()
|
|
|
- .clientAuthenticationMethod(ClientAuthenticationMethod.PRIVATE_KEY_JWT).build();
|
|
|
- when(this.registeredClientRepository.findByClientId(eq(registeredClient.getClientId())))
|
|
|
- .thenReturn(registeredClient);
|
|
|
- when(this.jwtDecoderFactory.createDecoder(any()))
|
|
|
- .thenReturn(s -> createJwtToken("client-1", "https://bad-server.com/oauth2/token"));
|
|
|
- OAuth2ClientAuthenticationToken authentication = new OAuth2ClientAuthenticationToken(
|
|
|
- "/oauth2/token", registeredClient.getClientId(),
|
|
|
- JWT_CLIENT_ASSERTION_AUTHENTICATION_METHOD, registeredClient.getClientSecret(), null);
|
|
|
-
|
|
|
- assertThatThrownBy(() -> this.authenticationProvider.authenticate(authentication))
|
|
|
- .isInstanceOf(OAuth2AuthenticationException.class)
|
|
|
- .extracting(ex -> ((OAuth2AuthenticationException) ex).getError())
|
|
|
- .extracting("errorCode")
|
|
|
- .isEqualTo(OAuth2ErrorCodes.INVALID_CLIENT);
|
|
|
- }
|
|
|
-
|
|
|
- @Test
|
|
|
- public void authenticateWhenClientJwtAssertionAndPrivateJwtVerificationSuccessThenAuthenticate() {
|
|
|
- RegisteredClient registeredClient = TestRegisteredClients.registeredClient()
|
|
|
- .clientAuthenticationMethod(ClientAuthenticationMethod.PRIVATE_KEY_JWT).build();
|
|
|
- when(this.registeredClientRepository.findByClientId(eq(registeredClient.getClientId())))
|
|
|
- .thenReturn(registeredClient);
|
|
|
- when(this.jwtDecoderFactory.createDecoder(any()))
|
|
|
- .thenReturn(s -> createJwtToken("client-1", "https://auth-server.com/oauth2/token"));
|
|
|
- OAuth2ClientAuthenticationToken authentication = new OAuth2ClientAuthenticationToken(
|
|
|
- "https://auth-server.com/oauth2/token", registeredClient.getClientId(),
|
|
|
- JWT_CLIENT_ASSERTION_AUTHENTICATION_METHOD, registeredClient.getClientSecret(), null);
|
|
|
-
|
|
|
- OAuth2ClientAuthenticationToken authenticationResult =
|
|
|
- (OAuth2ClientAuthenticationToken) this.authenticationProvider.authenticate(authentication);
|
|
|
-
|
|
|
- assertThat(authenticationResult.isAuthenticated()).isTrue();
|
|
|
- assertThat(authenticationResult.getPrincipal().toString()).isEqualTo(registeredClient.getClientId());
|
|
|
- assertThat(authenticationResult.getCredentials().toString()).isEqualTo(registeredClient.getClientSecret());
|
|
|
- assertThat(authenticationResult.getRegisteredClient()).isEqualTo(registeredClient);
|
|
|
- }
|
|
|
-
|
|
|
- @Test
|
|
|
- public void authenticateWhenClientJwtAssertionAndClientSecretJwtAndFailedVerifyTokenThenThrowOAuth2Exception() {
|
|
|
- RegisteredClient registeredClient = TestRegisteredClients.registeredClient()
|
|
|
- .clientAuthenticationMethod(ClientAuthenticationMethod.PRIVATE_KEY_JWT)
|
|
|
- .clientAuthenticationMethod(ClientAuthenticationMethod.CLIENT_SECRET_JWT)
|
|
|
- .build();
|
|
|
- when(this.registeredClientRepository.findByClientId(eq(registeredClient.getClientId())))
|
|
|
- .thenReturn(registeredClient);
|
|
|
- when(this.jwtDecoderFactory.createDecoder(any()))
|
|
|
- .thenReturn(s -> { throw new OAuth2AuthenticationException(OAuth2ErrorCodes.INVALID_CLIENT); });
|
|
|
- OAuth2ClientAuthenticationToken authentication = new OAuth2ClientAuthenticationToken(
|
|
|
- "https://auth-server.com", registeredClient.getClientId(), JWT_CLIENT_ASSERTION_AUTHENTICATION_METHOD,
|
|
|
- registeredClient.getClientSecret(), null);
|
|
|
-
|
|
|
- assertThatThrownBy(() -> this.authenticationProvider.authenticate(authentication))
|
|
|
- .isInstanceOf(OAuth2AuthenticationException.class)
|
|
|
- .extracting(ex -> ((OAuth2AuthenticationException) ex).getError())
|
|
|
- .extracting("errorCode")
|
|
|
- .isEqualTo(OAuth2ErrorCodes.INVALID_CLIENT);
|
|
|
- }
|
|
|
-
|
|
|
- @Test
|
|
|
- public void authenticateWhenClientJwtAssertionAndClientSecretJwtVerificationSuccess() {
|
|
|
- RegisteredClient registeredClient = TestRegisteredClients.registeredClient()
|
|
|
- .clientAuthenticationMethod(ClientAuthenticationMethod.CLIENT_SECRET_JWT).build();
|
|
|
- when(this.registeredClientRepository.findByClientId(eq(registeredClient.getClientId())))
|
|
|
- .thenReturn(registeredClient);
|
|
|
- when(this.jwtDecoderFactory.createDecoder(any()))
|
|
|
- .thenReturn(s -> createJwtToken("client-1", "https://auth-server.com/oauth2/token"));
|
|
|
- OAuth2ClientAuthenticationToken authentication = new OAuth2ClientAuthenticationToken(
|
|
|
- "https://auth-server.com/oauth2/token", registeredClient.getClientId(), JWT_CLIENT_ASSERTION_AUTHENTICATION_METHOD,
|
|
|
- registeredClient.getClientSecret(), null);
|
|
|
-
|
|
|
- OAuth2ClientAuthenticationToken authenticationResult =
|
|
|
- (OAuth2ClientAuthenticationToken) this.authenticationProvider.authenticate(authentication);
|
|
|
-
|
|
|
- assertThat(authenticationResult.isAuthenticated()).isTrue();
|
|
|
- assertThat(authenticationResult.getPrincipal().toString()).isEqualTo(registeredClient.getClientId());
|
|
|
- assertThat(authenticationResult.getCredentials().toString()).isEqualTo(registeredClient.getClientSecret());
|
|
|
- assertThat(authenticationResult.getRegisteredClient()).isEqualTo(registeredClient);
|
|
|
- }
|
|
|
-
|
|
|
@Test
|
|
|
public void authenticateWhenPkceAndInvalidCodeThenThrowOAuth2AuthenticationException() {
|
|
|
RegisteredClient registeredClient = TestRegisteredClients.registeredPublicClient().build();
|
|
@@ -596,13 +493,34 @@ public class OAuth2ClientAuthenticationProviderTests {
|
|
|
}
|
|
|
|
|
|
@Test
|
|
|
- public void authenticateWhenClientAuthenticationMethodNotConfiguredThenThrowOAuth2AuthenticationException() {
|
|
|
+ public void authenticateWhenJwtClientAssertionAndInvalidClientIdThenThrowOAuth2AuthenticationException() {
|
|
|
+ // @formatter:off
|
|
|
+ RegisteredClient registeredClient = TestRegisteredClients.registeredClient()
|
|
|
+ .clientAuthenticationMethod(ClientAuthenticationMethod.CLIENT_SECRET_JWT)
|
|
|
+ .build();
|
|
|
+ // @formatter:on
|
|
|
+ when(this.registeredClientRepository.findByClientId(eq(registeredClient.getClientId())))
|
|
|
+ .thenReturn(registeredClient);
|
|
|
+
|
|
|
+ OAuth2ClientAuthenticationToken authentication = new OAuth2ClientAuthenticationToken(
|
|
|
+ registeredClient.getClientId() + "-invalid", JWT_CLIENT_ASSERTION_AUTHENTICATION_METHOD, "jwt-assertion", null);
|
|
|
+ assertThatThrownBy(() -> this.authenticationProvider.authenticate(authentication))
|
|
|
+ .isInstanceOf(OAuth2AuthenticationException.class)
|
|
|
+ .extracting(ex -> ((OAuth2AuthenticationException) ex).getError())
|
|
|
+ .satisfies(error -> {
|
|
|
+ assertThat(error.getErrorCode()).isEqualTo(OAuth2ErrorCodes.INVALID_CLIENT);
|
|
|
+ assertThat(error.getDescription()).contains(OAuth2ParameterNames.CLIENT_ID);
|
|
|
+ });
|
|
|
+ }
|
|
|
+
|
|
|
+ @Test
|
|
|
+ public void authenticateWhenJwtClientAssertionAndUnsupportedClientAuthenticationMethodThenThrowOAuth2AuthenticationException() {
|
|
|
RegisteredClient registeredClient = TestRegisteredClients.registeredClient().build();
|
|
|
when(this.registeredClientRepository.findByClientId(eq(registeredClient.getClientId())))
|
|
|
.thenReturn(registeredClient);
|
|
|
|
|
|
OAuth2ClientAuthenticationToken authentication = new OAuth2ClientAuthenticationToken(
|
|
|
- registeredClient.getClientId(), ClientAuthenticationMethod.CLIENT_SECRET_POST, registeredClient.getClientSecret(), null);
|
|
|
+ registeredClient.getClientId(), JWT_CLIENT_ASSERTION_AUTHENTICATION_METHOD, "jwt-assertion", null);
|
|
|
assertThatThrownBy(() -> this.authenticationProvider.authenticate(authentication))
|
|
|
.isInstanceOf(OAuth2AuthenticationException.class)
|
|
|
.extracting(ex -> ((OAuth2AuthenticationException) ex).getError())
|
|
@@ -612,6 +530,218 @@ public class OAuth2ClientAuthenticationProviderTests {
|
|
|
});
|
|
|
}
|
|
|
|
|
|
+ @Test
|
|
|
+ public void authenticateWhenJwtClientAssertionAndMissingJwkSetUrlThenThrowOAuth2AuthenticationException() {
|
|
|
+ // @formatter:off
|
|
|
+ RegisteredClient registeredClient = TestRegisteredClients.registeredClient()
|
|
|
+ .clientAuthenticationMethod(ClientAuthenticationMethod.PRIVATE_KEY_JWT)
|
|
|
+ .clientSettings(
|
|
|
+ ClientSettings.builder()
|
|
|
+ .tokenEndpointAuthenticationSigningAlgorithm(SignatureAlgorithm.RS256)
|
|
|
+ .build()
|
|
|
+ )
|
|
|
+ .build();
|
|
|
+ // @formatter:on
|
|
|
+ when(this.registeredClientRepository.findByClientId(eq(registeredClient.getClientId())))
|
|
|
+ .thenReturn(registeredClient);
|
|
|
+
|
|
|
+ OAuth2ClientAuthenticationToken authentication = new OAuth2ClientAuthenticationToken(
|
|
|
+ registeredClient.getClientId(), JWT_CLIENT_ASSERTION_AUTHENTICATION_METHOD, "jwt-assertion", null);
|
|
|
+ assertThatThrownBy(() -> this.authenticationProvider.authenticate(authentication))
|
|
|
+ .isInstanceOf(OAuth2AuthenticationException.class)
|
|
|
+ .extracting(ex -> ((OAuth2AuthenticationException) ex).getError())
|
|
|
+ .satisfies(error -> {
|
|
|
+ assertThat(error.getErrorCode()).isEqualTo(OAuth2ErrorCodes.INVALID_CLIENT);
|
|
|
+ assertThat(error.getDescription()).isEqualTo("Failed to find a Signature Verifier for Client: '" +
|
|
|
+ registeredClient.getId() + "'. Check to ensure you have configured the JWK Set URL.");
|
|
|
+ });
|
|
|
+ }
|
|
|
+
|
|
|
+ @Test
|
|
|
+ public void authenticateWhenJwtClientAssertionAndMissingClientSecretThenThrowOAuth2AuthenticationException() {
|
|
|
+ // @formatter:off
|
|
|
+ RegisteredClient registeredClient = TestRegisteredClients.registeredClient()
|
|
|
+ .clientSecret(null)
|
|
|
+ .clientAuthenticationMethod(ClientAuthenticationMethod.CLIENT_SECRET_JWT)
|
|
|
+ .clientSettings(
|
|
|
+ ClientSettings.builder()
|
|
|
+ .tokenEndpointAuthenticationSigningAlgorithm(MacAlgorithm.HS256)
|
|
|
+ .build()
|
|
|
+ )
|
|
|
+ .build();
|
|
|
+ // @formatter:on
|
|
|
+ when(this.registeredClientRepository.findByClientId(eq(registeredClient.getClientId())))
|
|
|
+ .thenReturn(registeredClient);
|
|
|
+
|
|
|
+ OAuth2ClientAuthenticationToken authentication = new OAuth2ClientAuthenticationToken(
|
|
|
+ registeredClient.getClientId(), JWT_CLIENT_ASSERTION_AUTHENTICATION_METHOD, "jwt-assertion", null);
|
|
|
+ assertThatThrownBy(() -> this.authenticationProvider.authenticate(authentication))
|
|
|
+ .isInstanceOf(OAuth2AuthenticationException.class)
|
|
|
+ .extracting(ex -> ((OAuth2AuthenticationException) ex).getError())
|
|
|
+ .satisfies(error -> {
|
|
|
+ assertThat(error.getErrorCode()).isEqualTo(OAuth2ErrorCodes.INVALID_CLIENT);
|
|
|
+ assertThat(error.getDescription()).isEqualTo("Failed to find a Signature Verifier for Client: '" +
|
|
|
+ registeredClient.getId() + "'. Check to ensure you have configured the client secret.");
|
|
|
+ });
|
|
|
+ }
|
|
|
+
|
|
|
+ @Test
|
|
|
+ public void authenticateWhenJwtClientAssertionAndMissingSigningAlgorithmThenThrowOAuth2AuthenticationException() {
|
|
|
+ // @formatter:off
|
|
|
+ RegisteredClient registeredClient = TestRegisteredClients.registeredClient()
|
|
|
+ .clientSecret(TestKeys.DEFAULT_ENCODED_SECRET_KEY)
|
|
|
+ .clientAuthenticationMethod(ClientAuthenticationMethod.CLIENT_SECRET_JWT)
|
|
|
+ .build();
|
|
|
+ // @formatter:on
|
|
|
+ when(this.registeredClientRepository.findByClientId(eq(registeredClient.getClientId())))
|
|
|
+ .thenReturn(registeredClient);
|
|
|
+
|
|
|
+ OAuth2ClientAuthenticationToken authentication = new OAuth2ClientAuthenticationToken(
|
|
|
+ registeredClient.getClientId(), JWT_CLIENT_ASSERTION_AUTHENTICATION_METHOD, "jwt-assertion", null);
|
|
|
+ assertThatThrownBy(() -> this.authenticationProvider.authenticate(authentication))
|
|
|
+ .isInstanceOf(OAuth2AuthenticationException.class)
|
|
|
+ .extracting(ex -> ((OAuth2AuthenticationException) ex).getError())
|
|
|
+ .satisfies(error -> {
|
|
|
+ assertThat(error.getErrorCode()).isEqualTo(OAuth2ErrorCodes.INVALID_CLIENT);
|
|
|
+ assertThat(error.getDescription()).isEqualTo("Failed to find a Signature Verifier for Client: '" +
|
|
|
+ registeredClient.getId() + "'. Check to ensure you have configured a valid JWS Algorithm: 'null'.");
|
|
|
+ });
|
|
|
+ }
|
|
|
+
|
|
|
+ @Test
|
|
|
+ public void authenticateWhenJwtClientAssertionAndInvalidCredentialsThenThrowOAuth2AuthenticationException() {
|
|
|
+ // @formatter:off
|
|
|
+ RegisteredClient registeredClient = TestRegisteredClients.registeredClient()
|
|
|
+ .clientSecret(TestKeys.DEFAULT_ENCODED_SECRET_KEY)
|
|
|
+ .clientAuthenticationMethod(ClientAuthenticationMethod.CLIENT_SECRET_JWT)
|
|
|
+ .clientSettings(
|
|
|
+ ClientSettings.builder()
|
|
|
+ .tokenEndpointAuthenticationSigningAlgorithm(MacAlgorithm.HS256)
|
|
|
+ .build()
|
|
|
+ )
|
|
|
+ .build();
|
|
|
+ // @formatter:on
|
|
|
+ when(this.registeredClientRepository.findByClientId(eq(registeredClient.getClientId())))
|
|
|
+ .thenReturn(registeredClient);
|
|
|
+
|
|
|
+ OAuth2ClientAuthenticationToken authentication = new OAuth2ClientAuthenticationToken(
|
|
|
+ registeredClient.getClientId(), JWT_CLIENT_ASSERTION_AUTHENTICATION_METHOD, "invalid-jwt-assertion", null);
|
|
|
+ assertThatThrownBy(() -> this.authenticationProvider.authenticate(authentication))
|
|
|
+ .isInstanceOf(OAuth2AuthenticationException.class)
|
|
|
+ .hasCauseInstanceOf(BadJwtException.class)
|
|
|
+ .extracting(ex -> ((OAuth2AuthenticationException) ex).getError())
|
|
|
+ .satisfies(error -> {
|
|
|
+ assertThat(error.getErrorCode()).isEqualTo(OAuth2ErrorCodes.INVALID_CLIENT);
|
|
|
+ assertThat(error.getDescription()).contains(OAuth2ParameterNames.CLIENT_ASSERTION);
|
|
|
+ });
|
|
|
+ }
|
|
|
+
|
|
|
+ @Test
|
|
|
+ public void authenticateWhenJwtClientAssertionAndInvalidClaimsThenThrowOAuth2AuthenticationException() {
|
|
|
+ // @formatter:off
|
|
|
+ RegisteredClient registeredClient = TestRegisteredClients.registeredClient()
|
|
|
+ .clientSecret(TestKeys.DEFAULT_ENCODED_SECRET_KEY)
|
|
|
+ .clientAuthenticationMethod(ClientAuthenticationMethod.CLIENT_SECRET_JWT)
|
|
|
+ .clientSettings(
|
|
|
+ ClientSettings.builder()
|
|
|
+ .tokenEndpointAuthenticationSigningAlgorithm(MacAlgorithm.HS256)
|
|
|
+ .build()
|
|
|
+ )
|
|
|
+ .build();
|
|
|
+ // @formatter:on
|
|
|
+ when(this.registeredClientRepository.findByClientId(eq(registeredClient.getClientId())))
|
|
|
+ .thenReturn(registeredClient);
|
|
|
+
|
|
|
+ // @formatter:off
|
|
|
+ JoseHeader joseHeader = JoseHeader.withAlgorithm(MacAlgorithm.HS256)
|
|
|
+ .build();
|
|
|
+ JwtClaimsSet jwtClaimsSet = JwtClaimsSet.builder()
|
|
|
+ .issuer("invalid-iss")
|
|
|
+ .subject("invalid-sub")
|
|
|
+ .audience(Collections.singletonList("invalid-aud"))
|
|
|
+ .build();
|
|
|
+ // @formatter:on
|
|
|
+
|
|
|
+ JwtEncoder jwsEncoder = createEncoder(TestKeys.DEFAULT_ENCODED_SECRET_KEY, "HmacSHA256");
|
|
|
+ Jwt jwtAssertion = jwsEncoder.encode(joseHeader, jwtClaimsSet);
|
|
|
+
|
|
|
+ OAuth2ClientAuthenticationToken authentication = new OAuth2ClientAuthenticationToken(
|
|
|
+ registeredClient.getClientId(), JWT_CLIENT_ASSERTION_AUTHENTICATION_METHOD, jwtAssertion.getTokenValue(), null);
|
|
|
+ assertThatThrownBy(() -> this.authenticationProvider.authenticate(authentication))
|
|
|
+ .isInstanceOf(OAuth2AuthenticationException.class)
|
|
|
+ .hasCauseInstanceOf(JwtValidationException.class)
|
|
|
+ .extracting(ex -> (OAuth2AuthenticationException) ex)
|
|
|
+ .satisfies(ex -> {
|
|
|
+ assertThat(ex.getError().getErrorCode()).isEqualTo(OAuth2ErrorCodes.INVALID_CLIENT);
|
|
|
+ assertThat(ex.getError().getDescription()).contains(OAuth2ParameterNames.CLIENT_ASSERTION);
|
|
|
+ JwtValidationException jwtValidationException = (JwtValidationException) ex.getCause();
|
|
|
+ assertThat(jwtValidationException.getErrors()).hasSize(4); // iss, sub, aud, exp
|
|
|
+ });
|
|
|
+ }
|
|
|
+
|
|
|
+ @Test
|
|
|
+ public void authenticateWhenJwtClientAssertionAndValidCredentialsThenAuthenticated() {
|
|
|
+ // @formatter:off
|
|
|
+ RegisteredClient registeredClient = TestRegisteredClients.registeredClient()
|
|
|
+ .clientSecret(TestKeys.DEFAULT_ENCODED_SECRET_KEY)
|
|
|
+ .clientAuthenticationMethod(ClientAuthenticationMethod.CLIENT_SECRET_JWT)
|
|
|
+ .clientSettings(
|
|
|
+ ClientSettings.builder()
|
|
|
+ .tokenEndpointAuthenticationSigningAlgorithm(MacAlgorithm.HS256)
|
|
|
+ .build()
|
|
|
+ )
|
|
|
+ .build();
|
|
|
+ // @formatter:on
|
|
|
+ when(this.registeredClientRepository.findByClientId(eq(registeredClient.getClientId())))
|
|
|
+ .thenReturn(registeredClient);
|
|
|
+
|
|
|
+ // @formatter:off
|
|
|
+ JoseHeader joseHeader = JoseHeader.withAlgorithm(MacAlgorithm.HS256)
|
|
|
+ .build();
|
|
|
+ JwtClaimsSet jwtClaimsSet = jwtClientAssertionClaims(registeredClient)
|
|
|
+ .build();
|
|
|
+ // @formatter:on
|
|
|
+
|
|
|
+ JwtEncoder jwsEncoder = createEncoder(TestKeys.DEFAULT_ENCODED_SECRET_KEY, "HmacSHA256");
|
|
|
+ Jwt jwtAssertion = jwsEncoder.encode(joseHeader, jwtClaimsSet);
|
|
|
+
|
|
|
+ OAuth2ClientAuthenticationToken authentication = new OAuth2ClientAuthenticationToken(
|
|
|
+ registeredClient.getClientId(), JWT_CLIENT_ASSERTION_AUTHENTICATION_METHOD, jwtAssertion.getTokenValue(), null);
|
|
|
+ OAuth2ClientAuthenticationToken authenticationResult =
|
|
|
+ (OAuth2ClientAuthenticationToken) this.authenticationProvider.authenticate(authentication);
|
|
|
+
|
|
|
+ verifyNoInteractions(this.passwordEncoder);
|
|
|
+
|
|
|
+ assertThat(authenticationResult.isAuthenticated()).isTrue();
|
|
|
+ assertThat(authenticationResult.getPrincipal().toString()).isEqualTo(registeredClient.getClientId());
|
|
|
+ assertThat(authenticationResult.getCredentials()).isInstanceOf(Jwt.class);
|
|
|
+ assertThat(authenticationResult.getRegisteredClient()).isEqualTo(registeredClient);
|
|
|
+ assertThat(authenticationResult.getClientAuthenticationMethod()).isEqualTo(ClientAuthenticationMethod.CLIENT_SECRET_JWT);
|
|
|
+ }
|
|
|
+
|
|
|
+ private JwtClaimsSet.Builder jwtClientAssertionClaims(RegisteredClient registeredClient) {
|
|
|
+ Instant issuedAt = Instant.now();
|
|
|
+ Instant expiresAt = issuedAt.plus(1, ChronoUnit.HOURS);
|
|
|
+ return JwtClaimsSet.builder()
|
|
|
+ .issuer(registeredClient.getClientId())
|
|
|
+ .subject(registeredClient.getClientId())
|
|
|
+ .audience(Collections.singletonList(asUrl(this.providerSettings.getIssuer(), this.providerSettings.getTokenEndpoint())))
|
|
|
+ .issuedAt(issuedAt)
|
|
|
+ .expiresAt(expiresAt);
|
|
|
+ }
|
|
|
+
|
|
|
+ private static JwtEncoder createEncoder(String secret, String algorithm) {
|
|
|
+ SecretKey secretKey = new SecretKeySpec(secret.getBytes(StandardCharsets.UTF_8), algorithm);
|
|
|
+ OctetSequenceKey secretKeyJwk = TestJwks.jwk(secretKey).build();
|
|
|
+ JWKSource<SecurityContext> jwkSource = (jwkSelector, securityContext) ->
|
|
|
+ jwkSelector.select(new JWKSet(secretKeyJwk));
|
|
|
+ return new NimbusJwsEncoder(jwkSource);
|
|
|
+ }
|
|
|
+
|
|
|
+ private static String asUrl(String uri, String path) {
|
|
|
+ return UriComponentsBuilder.fromUriString(uri).path(path).build().toUriString();
|
|
|
+ }
|
|
|
+
|
|
|
private static Map<String, Object> createAuthorizationCodeTokenParameters() {
|
|
|
Map<String, Object> parameters = new HashMap<>();
|
|
|
parameters.put(OAuth2ParameterNames.GRANT_TYPE, AuthorizationGrantType.AUTHORIZATION_CODE.getValue());
|
|
@@ -639,12 +769,4 @@ public class OAuth2ClientAuthenticationProviderTests {
|
|
|
return parameters;
|
|
|
}
|
|
|
|
|
|
- private static Jwt createJwtToken(String subject, String audience) {
|
|
|
- Map<String, Object> headers = new HashMap<>();
|
|
|
- headers.put("kid", "123");
|
|
|
- Map<String, Object> claims = new HashMap<>();
|
|
|
- claims.put("sub", subject);
|
|
|
- claims.put("aud", audience);
|
|
|
- return new Jwt("123", Instant.now().minusSeconds(30), Instant.now().plusSeconds(30), headers, claims);
|
|
|
- }
|
|
|
}
|