Implement Client Configuration Endpoint

See: https://openid.net/specs/openid-connect-registration-1_0.html#ClientConfigurationEndpoint

Generate registration_client_uri and registration_access_token when registering a new client (see: https://openid.net/specs/openid-connect-registration-1_0.html#ClientRegistration)

Closes gh-355
This commit is contained in:
Ovidiu Popa
2021-08-31 16:48:44 +03:00
committed by Joe Grandja
parent 72c5e24ab8
commit 37e45619ae
14 changed files with 1160 additions and 76 deletions

View File

@@ -18,16 +18,17 @@ package org.springframework.security.config.annotation.web.configurers.oauth2.se
import java.net.URLEncoder;
import java.nio.charset.StandardCharsets;
import java.util.Base64;
import java.util.stream.Collectors;
import com.nimbusds.jose.jwk.JWKSet;
import com.nimbusds.jose.jwk.source.JWKSource;
import com.nimbusds.jose.proc.SecurityContext;
import org.junit.After;
import org.junit.AfterClass;
import org.junit.BeforeClass;
import org.junit.Rule;
import org.junit.Test;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Bean;
import org.springframework.http.HttpHeaders;
@@ -67,6 +68,7 @@ import org.springframework.security.oauth2.server.authorization.client.JdbcRegis
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.ProviderSettings;
import org.springframework.security.web.SecurityFilterChain;
import org.springframework.security.web.util.matcher.RequestMatcher;
import org.springframework.test.web.servlet.MockMvc;
@@ -74,6 +76,7 @@ import org.springframework.test.web.servlet.MvcResult;
import static org.assertj.core.api.Assertions.assertThat;
import static org.hamcrest.CoreMatchers.containsString;
import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.get;
import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.post;
import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.header;
import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.jsonPath;
@@ -199,8 +202,277 @@ public class OidcClientRegistrationTests {
.isEqualTo(ClientAuthenticationMethod.CLIENT_SECRET_BASIC.getValue());
assertThat(clientRegistrationResponse.getIdTokenSignedResponseAlgorithm())
.isEqualTo(SignatureAlgorithm.RS256.getName());
assertThat(clientRegistrationResponse.getRegistrationClientUri())
.isNotNull();
assertThat(clientRegistrationResponse.getRegistrationAccessToken()).isNotEmpty();
}
@Test
public void requestWhenClientConfigurationRequestAndRegisteredClientNotEqualToAuthorizationRegisteredClientThenUnauthorized() throws Exception {
this.spring.register(AuthorizationServerConfiguration.class).autowire();
// ***** (1) Obtain the registration access token used for fetching the registered client configuration
String clientConfigurationRequestScope = "client.read";
RegisteredClient registeredClient = TestRegisteredClients.registeredClient()
.scope(clientConfigurationRequestScope)
.authorizationGrantType(AuthorizationGrantType.CLIENT_CREDENTIALS)
.build();
this.registeredClientRepository.save(registeredClient);
RegisteredClient unauthorizedRegisteredClient = TestRegisteredClients.registeredClient()
.id("registration-2")
.clientId("client-2")
.scope(clientConfigurationRequestScope)
.authorizationGrantType(AuthorizationGrantType.CLIENT_CREDENTIALS)
.build();
this.registeredClientRepository.save(unauthorizedRegisteredClient);
MvcResult mvcResult = this.mvc.perform(post(DEFAULT_TOKEN_ENDPOINT_URI)
.param(OAuth2ParameterNames.GRANT_TYPE, AuthorizationGrantType.CLIENT_CREDENTIALS.getValue())
.param(OAuth2ParameterNames.SCOPE, clientConfigurationRequestScope)
.header(HttpHeaders.AUTHORIZATION, "Basic " + encodeBasicAuth(
registeredClient.getClientId(), registeredClient.getClientSecret())))
.andExpect(status().isOk())
.andExpect(jsonPath("$.access_token").isNotEmpty())
.andExpect(jsonPath("$.scope").value(clientConfigurationRequestScope))
.andReturn();
OAuth2AccessToken accessToken = readAccessTokenResponse(mvcResult.getResponse()).getAccessToken();
HttpHeaders httpHeaders = new HttpHeaders();
httpHeaders.setBearerAuth(accessToken.getTokenValue());
// ***** (2) Get RegisteredClient Configuration
this.mvc.perform(get(DEFAULT_OIDC_CLIENT_REGISTRATION_ENDPOINT_URI)
.headers(httpHeaders)
.queryParam(OAuth2ParameterNames.CLIENT_ID, unauthorizedRegisteredClient.getClientId()))
.andExpect(status().isUnauthorized());
}
@Test
public void requestWhenClientConfigurationRequestAuthorizedThenClientRegistrationResponse() throws Exception {
this.spring.register(AuthorizationServerConfiguration.class).autowire();
// ***** (1) Obtain the registration access token used for fetching the registered client configuration
String clientConfigurationRequestScope = "client.read";
RegisteredClient registeredClient = TestRegisteredClients.registeredClient()
.scope(clientConfigurationRequestScope)
.authorizationGrantType(AuthorizationGrantType.CLIENT_CREDENTIALS)
.build();
this.registeredClientRepository.save(registeredClient);
MvcResult mvcResult = this.mvc.perform(post(DEFAULT_TOKEN_ENDPOINT_URI)
.param(OAuth2ParameterNames.GRANT_TYPE, AuthorizationGrantType.CLIENT_CREDENTIALS.getValue())
.param(OAuth2ParameterNames.SCOPE, clientConfigurationRequestScope)
.header(HttpHeaders.AUTHORIZATION, "Basic " + encodeBasicAuth(
registeredClient.getClientId(), registeredClient.getClientSecret())))
.andExpect(status().isOk())
.andExpect(jsonPath("$.access_token").isNotEmpty())
.andExpect(jsonPath("$.scope").value(clientConfigurationRequestScope))
.andReturn();
OAuth2AccessToken accessToken = readAccessTokenResponse(mvcResult.getResponse()).getAccessToken();
HttpHeaders httpHeaders = new HttpHeaders();
httpHeaders.setBearerAuth(accessToken.getTokenValue());
// ***** (2) Get RegisteredClient Configuration
mvcResult = this.mvc.perform(get(DEFAULT_OIDC_CLIENT_REGISTRATION_ENDPOINT_URI)
.headers(httpHeaders)
.queryParam(OAuth2ParameterNames.CLIENT_ID, registeredClient.getClientId()))
.andExpect(status().isOk())
.andExpect(header().string(HttpHeaders.CACHE_CONTROL, containsString("no-store")))
.andExpect(header().string(HttpHeaders.PRAGMA, containsString("no-cache")))
.andReturn();
OidcClientRegistration clientConfigurationResponse = readClientRegistrationResponse(mvcResult.getResponse());
assertThat(clientConfigurationResponse.getClientId()).isNotNull().isEqualTo(registeredClient.getClientId());
assertThat(clientConfigurationResponse.getClientIdIssuedAt()).isNotNull();
assertThat(clientConfigurationResponse.getClientSecret()).isNotNull().isEqualTo(registeredClient.getClientSecret());
assertThat(clientConfigurationResponse.getClientSecretExpiresAt()).isNull();
assertThat(clientConfigurationResponse.getClientName()).isEqualTo(registeredClient.getClientName());
assertThat(clientConfigurationResponse.getRedirectUris())
.containsExactlyInAnyOrderElementsOf(registeredClient.getRedirectUris());
assertThat(clientConfigurationResponse.getGrantTypes())
.containsExactlyInAnyOrderElementsOf(registeredClient.getAuthorizationGrantTypes().stream().map(AuthorizationGrantType::getValue).collect(Collectors.toList()));
assertThat(clientConfigurationResponse.getResponseTypes())
.containsExactly(OAuth2AuthorizationResponseType.CODE.getValue());
assertThat(clientConfigurationResponse.getScopes())
.containsExactlyInAnyOrderElementsOf(registeredClient.getScopes());
assertThat(clientConfigurationResponse.getTokenEndpointAuthenticationMethod())
.isEqualTo(ClientAuthenticationMethod.CLIENT_SECRET_BASIC.getValue());
assertThat(clientConfigurationResponse.getIdTokenSignedResponseAlgorithm())
.isEqualTo(SignatureAlgorithm.RS256.getName());
assertThat(clientConfigurationResponse.getRegistrationClientUri())
.isNotNull();
assertThat(clientConfigurationResponse.getRegistrationAccessToken()).isNull();
}
@Test
public void requestWhenClientConfigurationRequestTwiceSameAccessTokenAuthorizedThenClientRegistrationResponse() throws Exception {
this.spring.register(AuthorizationServerConfiguration.class).autowire();
// ***** (1) Obtain the registration access token used for fetching the registered client configuration
String clientConfigurationRequestScope = "client.read";
RegisteredClient registeredClient = TestRegisteredClients.registeredClient()
.scope(clientConfigurationRequestScope)
.authorizationGrantType(AuthorizationGrantType.CLIENT_CREDENTIALS)
.build();
this.registeredClientRepository.save(registeredClient);
MvcResult mvcResult = this.mvc.perform(post(DEFAULT_TOKEN_ENDPOINT_URI)
.param(OAuth2ParameterNames.GRANT_TYPE, AuthorizationGrantType.CLIENT_CREDENTIALS.getValue())
.param(OAuth2ParameterNames.SCOPE, clientConfigurationRequestScope)
.header(HttpHeaders.AUTHORIZATION, "Basic " + encodeBasicAuth(
registeredClient.getClientId(), registeredClient.getClientSecret())))
.andExpect(status().isOk())
.andExpect(jsonPath("$.access_token").isNotEmpty())
.andExpect(jsonPath("$.scope").value(clientConfigurationRequestScope))
.andReturn();
OAuth2AccessToken accessToken = readAccessTokenResponse(mvcResult.getResponse()).getAccessToken();
HttpHeaders httpHeaders = new HttpHeaders();
httpHeaders.setBearerAuth(accessToken.getTokenValue());
// ***** (2) Get RegisteredClient Configuration
mvcResult = this.mvc.perform(get(DEFAULT_OIDC_CLIENT_REGISTRATION_ENDPOINT_URI)
.headers(httpHeaders)
.queryParam(OAuth2ParameterNames.CLIENT_ID, registeredClient.getClientId()))
.andExpect(status().isOk())
.andExpect(header().string(HttpHeaders.CACHE_CONTROL, containsString("no-store")))
.andExpect(header().string(HttpHeaders.PRAGMA, containsString("no-cache")))
.andReturn();
assertClientConfigurationResponse(registeredClient, mvcResult);
// ***** (3) Get RegisteredClient Configuration with the same access token
mvcResult = this.mvc.perform(get(DEFAULT_OIDC_CLIENT_REGISTRATION_ENDPOINT_URI)
.headers(httpHeaders)
.queryParam(OAuth2ParameterNames.CLIENT_ID, registeredClient.getClientId()))
.andExpect(status().isOk())
.andExpect(header().string(HttpHeaders.CACHE_CONTROL, containsString("no-store")))
.andExpect(header().string(HttpHeaders.PRAGMA, containsString("no-cache")))
.andReturn();
assertClientConfigurationResponse(registeredClient, mvcResult);
}
@Test
public void requestWhenClientRegistrationRequestAndClientConfigurationRequestAuthorizedThenClientRegistrationResponse() throws Exception {
this.spring.register(AuthorizationServerConfiguration.class).autowire();
// ***** (1) Obtain the "initial" access token used for registering the client
String clientRegistrationScope = "client.create";
RegisteredClient registeredClient = TestRegisteredClients.registeredClient2()
.scope(clientRegistrationScope)
.build();
this.registeredClientRepository.save(registeredClient);
MvcResult mvcResult = this.mvc.perform(post(DEFAULT_TOKEN_ENDPOINT_URI)
.param(OAuth2ParameterNames.GRANT_TYPE, AuthorizationGrantType.CLIENT_CREDENTIALS.getValue())
.param(OAuth2ParameterNames.SCOPE, clientRegistrationScope)
.header(HttpHeaders.AUTHORIZATION, "Basic " + encodeBasicAuth(
registeredClient.getClientId(), registeredClient.getClientSecret())))
.andExpect(status().isOk())
.andExpect(jsonPath("$.access_token").isNotEmpty())
.andExpect(jsonPath("$.scope").value(clientRegistrationScope))
.andReturn();
OAuth2AccessToken accessToken = readAccessTokenResponse(mvcResult.getResponse()).getAccessToken();
// ***** (2) Register the client
// @formatter:off
OidcClientRegistration clientRegistration = OidcClientRegistration.builder()
.clientName("client-name")
.redirectUri("https://client.example.com")
.grantType(AuthorizationGrantType.AUTHORIZATION_CODE.getValue())
.grantType(AuthorizationGrantType.CLIENT_CREDENTIALS.getValue())
.scope("scope1")
.scope("scope2")
.build();
// @formatter:on
HttpHeaders httpHeaders = new HttpHeaders();
httpHeaders.setBearerAuth(accessToken.getTokenValue());
// Register the client
mvcResult = this.mvc.perform(post(DEFAULT_OIDC_CLIENT_REGISTRATION_ENDPOINT_URI)
.headers(httpHeaders)
.contentType(MediaType.APPLICATION_JSON)
.content(getClientRegistrationRequestContent(clientRegistration)))
.andExpect(status().isCreated())
.andExpect(header().string(HttpHeaders.CACHE_CONTROL, containsString("no-store")))
.andExpect(header().string(HttpHeaders.PRAGMA, containsString("no-cache")))
.andReturn();
OidcClientRegistration clientRegistrationResponse = readClientRegistrationResponse(mvcResult.getResponse());
httpHeaders = new HttpHeaders();
httpHeaders.setBearerAuth(clientRegistrationResponse.getRegistrationAccessToken());
// ***** (3) Get RegisteredClient Configuration
mvcResult = this.mvc.perform(get(clientRegistrationResponse.getRegistrationClientUri().toString())
.headers(httpHeaders))
.andExpect(status().isOk())
.andExpect(header().string(HttpHeaders.CACHE_CONTROL, containsString("no-store")))
.andExpect(header().string(HttpHeaders.PRAGMA, containsString("no-cache")))
.andReturn();
OidcClientRegistration clientConfigurationResponse = readClientRegistrationResponse(mvcResult.getResponse());
assertThat(clientConfigurationResponse.getClientId()).isNotNull().isEqualTo(clientRegistrationResponse.getClientId());
assertThat(clientConfigurationResponse.getClientIdIssuedAt()).isNotNull();
assertThat(clientConfigurationResponse.getClientSecret()).isNotNull().isEqualTo(clientRegistrationResponse.getClientSecret());
assertThat(clientConfigurationResponse.getClientSecretExpiresAt()).isNull();
assertThat(clientConfigurationResponse.getClientName()).isEqualTo(clientRegistrationResponse.getClientName());
assertThat(clientConfigurationResponse.getRedirectUris())
.containsExactlyInAnyOrderElementsOf(clientRegistrationResponse.getRedirectUris());
assertThat(clientConfigurationResponse.getGrantTypes())
.containsExactlyInAnyOrderElementsOf(clientRegistrationResponse.getGrantTypes());
assertThat(clientConfigurationResponse.getResponseTypes())
.containsExactly(OAuth2AuthorizationResponseType.CODE.getValue());
assertThat(clientConfigurationResponse.getScopes())
.containsExactlyInAnyOrderElementsOf(clientRegistrationResponse.getScopes());
assertThat(clientConfigurationResponse.getTokenEndpointAuthenticationMethod())
.isEqualTo(ClientAuthenticationMethod.CLIENT_SECRET_BASIC.getValue());
assertThat(clientConfigurationResponse.getIdTokenSignedResponseAlgorithm())
.isEqualTo(SignatureAlgorithm.RS256.getName());
assertThat(clientConfigurationResponse.getRegistrationClientUri())
.isNotNull();
assertThat(clientConfigurationResponse.getRegistrationAccessToken()).isNull();
}
private static void assertClientConfigurationResponse(RegisteredClient registeredClient, MvcResult mvcResult) throws Exception {
OidcClientRegistration clientConfigurationResponse;
clientConfigurationResponse = readClientRegistrationResponse(mvcResult.getResponse());
assertThat(clientConfigurationResponse.getClientId()).isNotNull().isEqualTo(registeredClient.getClientId());
assertThat(clientConfigurationResponse.getClientIdIssuedAt()).isNotNull();
assertThat(clientConfigurationResponse.getClientSecret()).isNotNull().isEqualTo(registeredClient.getClientSecret());
assertThat(clientConfigurationResponse.getClientSecretExpiresAt()).isNull();
assertThat(clientConfigurationResponse.getClientName()).isEqualTo(registeredClient.getClientName());
assertThat(clientConfigurationResponse.getRedirectUris())
.containsExactlyInAnyOrderElementsOf(registeredClient.getRedirectUris());
assertThat(clientConfigurationResponse.getGrantTypes())
.containsExactlyInAnyOrderElementsOf(registeredClient.getAuthorizationGrantTypes().stream().map(AuthorizationGrantType::getValue).collect(Collectors.toList()));
assertThat(clientConfigurationResponse.getResponseTypes())
.containsExactly(OAuth2AuthorizationResponseType.CODE.getValue());
assertThat(clientConfigurationResponse.getScopes())
.containsExactlyInAnyOrderElementsOf(registeredClient.getScopes());
assertThat(clientConfigurationResponse.getTokenEndpointAuthenticationMethod())
.isEqualTo(ClientAuthenticationMethod.CLIENT_SECRET_BASIC.getValue());
assertThat(clientConfigurationResponse.getIdTokenSignedResponseAlgorithm())
.isEqualTo(SignatureAlgorithm.RS256.getName());
assertThat(clientConfigurationResponse.getRegistrationClientUri())
.isNotNull();
assertThat(clientConfigurationResponse.getRegistrationAccessToken()).isNull();
}
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());
@@ -278,6 +550,12 @@ public class OidcClientRegistrationTests {
return OAuth2AuthorizationServerConfiguration.jwtDecoder(jwkSource);
}
@Bean
ProviderSettings providerSettings() {
return ProviderSettings.builder().issuer("http://auth-server:9000")
.oidcClientRegistrationEndpoint("/connect/register").build();
}
@Bean
PasswordEncoder passwordEncoder() {
return NoOpPasswordEncoder.getInstance();

View File

@@ -64,6 +64,8 @@ public class OidcClientRegistrationTests {
.scope("scope2")
.idTokenSignedResponseAlgorithm(SignatureAlgorithm.RS256.getName())
.claim("a-claim", "a-value")
.registrationAccessToken("registration-access-token")
.registrationClientUri("https://auth-server.com/connect/register?client_id=1")
.build();
// @formatter:on
@@ -79,6 +81,8 @@ public class OidcClientRegistrationTests {
assertThat(clientRegistration.getScopes()).containsExactlyInAnyOrder("scope1", "scope2");
assertThat(clientRegistration.getIdTokenSignedResponseAlgorithm()).isEqualTo("RS256");
assertThat(clientRegistration.getClaimAsString("a-claim")).isEqualTo("a-value");
assertThat(clientRegistration.getRegistrationAccessToken()).isEqualTo("registration-access-token");
assertThat(clientRegistration.getRegistrationClientUri().toString()).isEqualTo("https://auth-server.com/connect/register?client_id=1");
}
@Test
@@ -105,6 +109,8 @@ public class OidcClientRegistrationTests {
claims.put(OidcClientMetadataClaimNames.SCOPE, Arrays.asList("scope1", "scope2"));
claims.put(OidcClientMetadataClaimNames.ID_TOKEN_SIGNED_RESPONSE_ALG, SignatureAlgorithm.RS256.getName());
claims.put("a-claim", "a-value");
claims.put(OidcClientMetadataClaimNames.REGISTRATION_ACCESS_TOKEN, "registration-access-token");
claims.put(OidcClientMetadataClaimNames.REGISTRATION_CLIENT_URI, "https://auth-server.com/connect/register?client_id=1");
OidcClientRegistration clientRegistration = OidcClientRegistration.withClaims(claims).build();
@@ -120,6 +126,8 @@ public class OidcClientRegistrationTests {
assertThat(clientRegistration.getScopes()).containsExactlyInAnyOrder("scope1", "scope2");
assertThat(clientRegistration.getIdTokenSignedResponseAlgorithm()).isEqualTo("RS256");
assertThat(clientRegistration.getClaimAsString("a-claim")).isEqualTo("a-value");
assertThat(clientRegistration.getRegistrationAccessToken()).isEqualTo("registration-access-token");
assertThat(clientRegistration.getRegistrationClientUri().toString()).isEqualTo("https://auth-server.com/connect/register?client_id=1");
}
@Test

View File

@@ -185,6 +185,8 @@ public class OidcClientRegistrationHttpMessageConverterTests {
.scope("scope2")
.idTokenSignedResponseAlgorithm(SignatureAlgorithm.RS256.getName())
.claim("a-claim", "a-value")
.registrationClientUri("https://auth-server.com/connect/register?client_id=1")
.registrationAccessToken("registration-access-token")
.build();
// @formatter:on
@@ -204,6 +206,8 @@ public class OidcClientRegistrationHttpMessageConverterTests {
assertThat(clientRegistrationResponse).contains("\"scope\":\"scope1 scope2\"");
assertThat(clientRegistrationResponse).contains("\"id_token_signed_response_alg\":\"RS256\"");
assertThat(clientRegistrationResponse).contains("\"a-claim\":\"a-value\"");
assertThat(clientRegistrationResponse).contains("\"registration_access_token\":\"registration-access-token\"");
assertThat(clientRegistrationResponse).contains("\"registration_client_uri\":\"https://auth-server.com/connect/register?client_id=1\"");
}
@Test

View File

@@ -23,7 +23,6 @@ import java.util.Set;
import org.junit.Before;
import org.junit.Test;
import org.mockito.ArgumentCaptor;
import org.springframework.security.authentication.TestingAuthenticationToken;
import org.springframework.security.core.authority.AuthorityUtils;
import org.springframework.security.oauth2.core.AuthorizationGrantType;
@@ -39,6 +38,7 @@ import org.springframework.security.oauth2.jose.jws.SignatureAlgorithm;
import org.springframework.security.oauth2.jwt.JoseHeader;
import org.springframework.security.oauth2.jwt.Jwt;
import org.springframework.security.oauth2.jwt.JwtClaimsSet;
import org.springframework.security.oauth2.jwt.JwtEncoder;
import org.springframework.security.oauth2.jwt.TestJoseHeaders;
import org.springframework.security.oauth2.jwt.TestJwtClaimsSets;
import org.springframework.security.oauth2.server.authorization.OAuth2Authorization;
@@ -47,13 +47,17 @@ import org.springframework.security.oauth2.server.authorization.TestOAuth2Author
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.ProviderSettings;
import org.springframework.security.oauth2.server.resource.authentication.JwtAuthenticationToken;
import org.springframework.web.util.UriComponentsBuilder;
import static org.assertj.core.api.Assertions.assertThat;
import static org.assertj.core.api.Assertions.assertThatIllegalArgumentException;
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.times;
import static org.mockito.Mockito.verify;
import static org.mockito.Mockito.when;
@@ -67,43 +71,56 @@ public class OidcClientRegistrationAuthenticationProviderTests {
private RegisteredClientRepository registeredClientRepository;
private OAuth2AuthorizationService authorizationService;
private OidcClientRegistrationAuthenticationProvider authenticationProvider;
private JwtEncoder jwtEncoder;
private ProviderSettings providerSettings;
@Before
public void setUp() {
this.registeredClientRepository = mock(RegisteredClientRepository.class);
this.authorizationService = mock(OAuth2AuthorizationService.class);
this.jwtEncoder = mock(JwtEncoder.class);
this.providerSettings = ProviderSettings.builder().issuer("http://auth-server:9000").build();
this.authenticationProvider = new OidcClientRegistrationAuthenticationProvider(
this.registeredClientRepository, this.authorizationService);
this.registeredClientRepository, this.authorizationService,
this.jwtEncoder);
this.authenticationProvider.setProviderSettings(providerSettings);
}
@Test
public void constructorWhenRegisteredClientRepositoryNullThenThrowIllegalArgumentException() {
assertThatIllegalArgumentException()
.isThrownBy(() -> new OidcClientRegistrationAuthenticationProvider(null, this.authorizationService))
.isThrownBy(() -> new OidcClientRegistrationAuthenticationProvider(null, this.authorizationService, jwtEncoder))
.withMessage("registeredClientRepository cannot be null");
}
@Test
public void constructorWhenAuthorizationServiceNullThenThrowIllegalArgumentException() {
assertThatIllegalArgumentException()
.isThrownBy(() -> new OidcClientRegistrationAuthenticationProvider(this.registeredClientRepository, null))
.isThrownBy(() -> new OidcClientRegistrationAuthenticationProvider(this.registeredClientRepository, null, jwtEncoder))
.withMessage("authorizationService cannot be null");
}
@Test
public void constructorWhenJwtEncoderNullThenThrowIllegalArgumentException() {
assertThatIllegalArgumentException()
.isThrownBy(() -> new OidcClientRegistrationAuthenticationProvider(this.registeredClientRepository, this.authorizationService, null))
.withMessage("jwtEncoder cannot be null");
}
@Test
public void supportsWhenTypeOidcClientRegistrationAuthenticationTokenThenReturnTrue() {
assertThat(this.authenticationProvider.supports(OidcClientRegistrationAuthenticationToken.class)).isTrue();
}
@Test
public void authenticateWhenPrincipalNotOAuth2TokenAuthenticationTokenThenThrowOAuth2AuthenticationException() {
public void authenticateWhenClientRegistrationRequestAndPrincipalNotOAuth2TokenAuthenticationTokenThenThrowOAuth2AuthenticationException() {
TestingAuthenticationToken principal = new TestingAuthenticationToken("principal", "credentials");
OidcClientRegistration clientRegistration = OidcClientRegistration.builder()
.redirectUri("https://client.example.com")
.build();
OidcClientRegistrationAuthenticationToken authentication = new OidcClientRegistrationAuthenticationToken(
principal, clientRegistration);
OidcClientRegistrationAuthenticationToken authentication = new OidcClientRegistrationAuthenticationToken(principal, clientRegistration);
assertThatThrownBy(() -> this.authenticationProvider.authenticate(authentication))
.isInstanceOf(OAuth2AuthenticationException.class)
@@ -112,14 +129,13 @@ public class OidcClientRegistrationAuthenticationProviderTests {
}
@Test
public void authenticateWhenPrincipalNotAuthenticatedThenThrowOAuth2AuthenticationException() {
JwtAuthenticationToken principal = new JwtAuthenticationToken(createJwt());
public void authenticateWhenClientRegistrationRequestAndPrincipalNotAuthenticatedThenThrowOAuth2AuthenticationException() {
JwtAuthenticationToken principal = new JwtAuthenticationToken(createJwtClientRegistration());
OidcClientRegistration clientRegistration = OidcClientRegistration.builder()
.redirectUri("https://client.example.com")
.build();
OidcClientRegistrationAuthenticationToken authentication = new OidcClientRegistrationAuthenticationToken(
principal, clientRegistration);
OidcClientRegistrationAuthenticationToken authentication = new OidcClientRegistrationAuthenticationToken(principal, clientRegistration);
assertThatThrownBy(() -> this.authenticationProvider.authenticate(authentication))
.isInstanceOf(OAuth2AuthenticationException.class)
@@ -128,16 +144,15 @@ public class OidcClientRegistrationAuthenticationProviderTests {
}
@Test
public void authenticateWhenAccessTokenNotFoundThenThrowOAuth2AuthenticationException() {
Jwt jwt = createJwt();
public void authenticateWhenClientRegistrationRequestAndAccessTokenNotFoundThenThrowOAuth2AuthenticationException() {
Jwt jwt = createJwtClientRegistration();
JwtAuthenticationToken principal = new JwtAuthenticationToken(
jwt, AuthorityUtils.createAuthorityList("SCOPE_client.create"));
OidcClientRegistration clientRegistration = OidcClientRegistration.builder()
.redirectUri("https://client.example.com")
.build();
OidcClientRegistrationAuthenticationToken authentication = new OidcClientRegistrationAuthenticationToken(
principal, clientRegistration);
OidcClientRegistrationAuthenticationToken authentication = new OidcClientRegistrationAuthenticationToken(principal, clientRegistration);
assertThatThrownBy(() -> this.authenticationProvider.authenticate(authentication))
.isInstanceOf(OAuth2AuthenticationException.class)
@@ -148,8 +163,8 @@ public class OidcClientRegistrationAuthenticationProviderTests {
}
@Test
public void authenticateWhenAccessTokenNotActiveThenThrowOAuth2AuthenticationException() {
Jwt jwt = createJwt();
public void authenticateWhenClientRegistrationRequestAndAccessTokenNotActiveThenThrowOAuth2AuthenticationException() {
Jwt jwt = createJwtClientRegistration();
OAuth2AccessToken jwtAccessToken = new OAuth2AccessToken(OAuth2AccessToken.TokenType.BEARER,
jwt.getTokenValue(), jwt.getIssuedAt(),
jwt.getExpiresAt(), jwt.getClaim(OAuth2ParameterNames.SCOPE));
@@ -167,8 +182,7 @@ public class OidcClientRegistrationAuthenticationProviderTests {
.redirectUri("https://client.example.com")
.build();
OidcClientRegistrationAuthenticationToken authentication = new OidcClientRegistrationAuthenticationToken(
principal, clientRegistration);
OidcClientRegistrationAuthenticationToken authentication = new OidcClientRegistrationAuthenticationToken(principal, clientRegistration);
assertThatThrownBy(() -> this.authenticationProvider.authenticate(authentication))
.isInstanceOf(OAuth2AuthenticationException.class)
@@ -179,7 +193,7 @@ public class OidcClientRegistrationAuthenticationProviderTests {
}
@Test
public void authenticateWhenAccessTokenNotAuthorizedThenThrowOAuth2AuthenticationException() {
public void authenticateWhenClientRegistrationRequestAndAccessTokenNotAuthorizedThenThrowOAuth2AuthenticationException() {
Jwt jwt = createJwt(Collections.singleton("unauthorized.scope"));
OAuth2AccessToken jwtAccessToken = new OAuth2AccessToken(OAuth2AccessToken.TokenType.BEARER,
jwt.getTokenValue(), jwt.getIssuedAt(),
@@ -197,8 +211,7 @@ public class OidcClientRegistrationAuthenticationProviderTests {
.redirectUri("https://client.example.com")
.build();
OidcClientRegistrationAuthenticationToken authentication = new OidcClientRegistrationAuthenticationToken(
principal, clientRegistration);
OidcClientRegistrationAuthenticationToken authentication = new OidcClientRegistrationAuthenticationToken(principal, clientRegistration);
assertThatThrownBy(() -> this.authenticationProvider.authenticate(authentication))
.isInstanceOf(OAuth2AuthenticationException.class)
@@ -209,8 +222,8 @@ public class OidcClientRegistrationAuthenticationProviderTests {
}
@Test
public void authenticateWhenInvalidRedirectUriThenThrowOAuth2AuthenticationException() {
Jwt jwt = createJwt();
public void authenticateWhenClientRegistrationRequestAndInvalidRedirectUriThenThrowOAuth2AuthenticationException() {
Jwt jwt = createJwtClientRegistration();
OAuth2AccessToken jwtAccessToken = new OAuth2AccessToken(OAuth2AccessToken.TokenType.BEARER,
jwt.getTokenValue(), jwt.getIssuedAt(),
jwt.getExpiresAt(), jwt.getClaim(OAuth2ParameterNames.SCOPE));
@@ -229,8 +242,7 @@ public class OidcClientRegistrationAuthenticationProviderTests {
.build();
// @formatter:on
OidcClientRegistrationAuthenticationToken authentication = new OidcClientRegistrationAuthenticationToken(
principal, clientRegistration);
OidcClientRegistrationAuthenticationToken authentication = new OidcClientRegistrationAuthenticationToken(principal, clientRegistration);
assertThatThrownBy(() -> this.authenticationProvider.authenticate(authentication))
.isInstanceOf(OAuth2AuthenticationException.class)
@@ -241,8 +253,8 @@ public class OidcClientRegistrationAuthenticationProviderTests {
}
@Test
public void authenticateWhenRedirectUriContainsFragmentThenThrowOAuth2AuthenticationException() {
Jwt jwt = createJwt();
public void authenticateWhenClientRegistrationRequestAndRedirectUriContainsFragmentThenThrowOAuth2AuthenticationException() {
Jwt jwt = createJwtClientRegistration();
OAuth2AccessToken jwtAccessToken = new OAuth2AccessToken(OAuth2AccessToken.TokenType.BEARER,
jwt.getTokenValue(), jwt.getIssuedAt(),
jwt.getExpiresAt(), jwt.getClaim(OAuth2ParameterNames.SCOPE));
@@ -261,8 +273,7 @@ public class OidcClientRegistrationAuthenticationProviderTests {
.build();
// @formatter:on
OidcClientRegistrationAuthenticationToken authentication = new OidcClientRegistrationAuthenticationToken(
principal, clientRegistration);
OidcClientRegistrationAuthenticationToken authentication = new OidcClientRegistrationAuthenticationToken(principal, clientRegistration);
assertThatThrownBy(() -> this.authenticationProvider.authenticate(authentication))
.isInstanceOf(OAuth2AuthenticationException.class)
@@ -273,8 +284,8 @@ public class OidcClientRegistrationAuthenticationProviderTests {
}
@Test
public void authenticateWhenValidAccessTokenThenReturnClientRegistration() {
Jwt jwt = createJwt();
public void authenticateWhenClientRegistrationRequestAndValidAccessTokenThenReturnClientRegistration() {
Jwt jwt = createJwtClientRegistration();
OAuth2AccessToken jwtAccessToken = new OAuth2AccessToken(OAuth2AccessToken.TokenType.BEARER,
jwt.getTokenValue(), jwt.getIssuedAt(),
jwt.getExpiresAt(), jwt.getClaim(OAuth2ParameterNames.SCOPE));
@@ -284,6 +295,7 @@ public class OidcClientRegistrationAuthenticationProviderTests {
when(this.authorizationService.findByToken(
eq(jwtAccessToken.getTokenValue()), eq(OAuth2TokenType.ACCESS_TOKEN)))
.thenReturn(authorization);
when(this.jwtEncoder.encode(any(), any())).thenReturn(createJwt(Collections.singleton("client.read")));
JwtAuthenticationToken principal = new JwtAuthenticationToken(
jwt, AuthorityUtils.createAuthorityList("SCOPE_client.create"));
@@ -298,8 +310,7 @@ public class OidcClientRegistrationAuthenticationProviderTests {
.build();
// @formatter:on
OidcClientRegistrationAuthenticationToken authentication = new OidcClientRegistrationAuthenticationToken(
principal, clientRegistration);
OidcClientRegistrationAuthenticationToken authentication = new OidcClientRegistrationAuthenticationToken(principal, clientRegistration);
OidcClientRegistrationAuthenticationToken authenticationResult =
(OidcClientRegistrationAuthenticationToken) this.authenticationProvider.authenticate(authentication);
@@ -309,14 +320,23 @@ public class OidcClientRegistrationAuthenticationProviderTests {
verify(this.authorizationService).findByToken(
eq(jwtAccessToken.getTokenValue()), eq(OAuth2TokenType.ACCESS_TOKEN));
verify(this.registeredClientRepository).save(registeredClientCaptor.capture());
verify(this.authorizationService).save(authorizationCaptor.capture());
verify(this.authorizationService, times(2)).save(authorizationCaptor.capture());
verify(this.jwtEncoder).encode(any(), any());
OAuth2Authorization authorizationResult = authorizationCaptor.getValue();
// assert access token
OAuth2Authorization authorizationResult = authorizationCaptor.getAllValues().get(0);
assertThat(authorizationResult.getAccessToken().isInvalidated()).isTrue();
if (authorizationResult.getRefreshToken() != null) {
assertThat(authorizationResult.getRefreshToken().isInvalidated()).isTrue();
}
// assert registration access token which should be used for subsequent calls to client configuration endpoint
authorizationResult = authorizationCaptor.getAllValues().get(1);
assertThat(authorizationResult.getAccessToken().isInvalidated()).isFalse();
assertThat(authorizationResult.getRefreshToken()).isNull();
assertThat(authorizationResult.getAccessToken().getToken().getScopes())
.containsExactly("client.read");
RegisteredClient registeredClientResult = registeredClientCaptor.getValue();
assertThat(registeredClientResult.getId()).isNotNull();
assertThat(registeredClientResult.getClientId()).isNotNull();
@@ -354,12 +374,249 @@ public class OidcClientRegistrationAuthenticationProviderTests {
.isEqualTo(registeredClientResult.getClientAuthenticationMethods().iterator().next().getValue());
assertThat(clientRegistrationResult.getIdTokenSignedResponseAlgorithm())
.isEqualTo(registeredClientResult.getTokenSettings().getIdTokenSignatureAlgorithm().getName());
String expectedRegistrationClientUri = UriComponentsBuilder.fromUriString(this.providerSettings.getIssuer())
.path(this.providerSettings.getOidcClientRegistrationEndpoint())
.queryParam("client_id", registeredClientResult.getClientId()).toUriString();
assertThat(clientRegistrationResult.getRegistrationClientUri().toString()).isEqualTo(expectedRegistrationClientUri);
assertThat(clientRegistrationResult.getRegistrationAccessToken()).isNotEmpty().isEqualTo(jwt.getTokenValue());
}
private static Jwt createJwt() {
@Test
public void authenticateWhenClientConfigurationRequestAndPrincipalNotOAuth2TokenAuthenticationTokenThenThrowOAuth2AuthenticationException() {
TestingAuthenticationToken principal = new TestingAuthenticationToken("principal", "credentials");
OidcClientRegistrationAuthenticationToken authentication = new OidcClientRegistrationAuthenticationToken(principal, "client-1");
assertThatThrownBy(() -> this.authenticationProvider.authenticate(authentication))
.isInstanceOf(OAuth2AuthenticationException.class)
.extracting(ex -> ((OAuth2AuthenticationException) ex).getError()).extracting("errorCode")
.isEqualTo(OAuth2ErrorCodes.INVALID_TOKEN);
}
@Test
public void authenticateWhenClientConfigurationRequestAndPrincipalNotAuthenticatedThenThrowOAuth2AuthenticationException() {
JwtAuthenticationToken principal = new JwtAuthenticationToken(createJwtClientConfiguration());
OidcClientRegistrationAuthenticationToken authentication = new OidcClientRegistrationAuthenticationToken(principal, "client-1");
assertThatThrownBy(() -> this.authenticationProvider.authenticate(authentication))
.isInstanceOf(OAuth2AuthenticationException.class)
.extracting(ex -> ((OAuth2AuthenticationException) ex).getError()).extracting("errorCode")
.isEqualTo(OAuth2ErrorCodes.INVALID_TOKEN);
}
@Test
public void authenticateWhenClientConfigurationRequestAndAccessTokenNotFoundThenThrowOAuth2AuthenticationException() {
Jwt jwt = createJwtClientConfiguration();
JwtAuthenticationToken principal = new JwtAuthenticationToken(
jwt, AuthorityUtils.createAuthorityList("SCOPE_client.read"));
OidcClientRegistrationAuthenticationToken authentication = new OidcClientRegistrationAuthenticationToken(principal, "client-1");
assertThatThrownBy(() -> this.authenticationProvider.authenticate(authentication))
.isInstanceOf(OAuth2AuthenticationException.class)
.extracting(ex -> ((OAuth2AuthenticationException) ex).getError()).extracting("errorCode")
.isEqualTo(OAuth2ErrorCodes.INVALID_TOKEN);
verify(this.authorizationService).findByToken(
eq(jwt.getTokenValue()), eq(OAuth2TokenType.ACCESS_TOKEN));
}
@Test
public void authenticateWhenClientConfigurationRequestAndAccessTokenNotActiveThenThrowOAuth2AuthenticationException() {
Jwt jwt = createJwtClientConfiguration();
OAuth2AccessToken jwtAccessToken = new OAuth2AccessToken(OAuth2AccessToken.TokenType.BEARER,
jwt.getTokenValue(), jwt.getIssuedAt(),
jwt.getExpiresAt(), jwt.getClaim(OAuth2ParameterNames.SCOPE));
RegisteredClient registeredClient = TestRegisteredClients.registeredClient().build();
OAuth2Authorization authorization = TestOAuth2Authorizations.authorization(
registeredClient, jwtAccessToken, jwt.getClaims()).build();
authorization = OidcAuthenticationProviderUtils.invalidate(authorization, jwtAccessToken);
when(this.authorizationService.findByToken(
eq(jwtAccessToken.getTokenValue()), eq(OAuth2TokenType.ACCESS_TOKEN)))
.thenReturn(authorization);
JwtAuthenticationToken principal = new JwtAuthenticationToken(
jwt, AuthorityUtils.createAuthorityList("SCOPE_client.read"));
OidcClientRegistrationAuthenticationToken authentication = new OidcClientRegistrationAuthenticationToken(principal, "client-1");
assertThatThrownBy(() -> this.authenticationProvider.authenticate(authentication))
.isInstanceOf(OAuth2AuthenticationException.class)
.extracting(ex -> ((OAuth2AuthenticationException) ex).getError()).extracting("errorCode")
.isEqualTo(OAuth2ErrorCodes.INVALID_TOKEN);
verify(this.authorizationService).findByToken(
eq(jwtAccessToken.getTokenValue()), eq(OAuth2TokenType.ACCESS_TOKEN));
}
@Test
public void authenticateWhenClientConfigurationRequestAndAccessTokenNotAuthorizedThenThrowOAuth2AuthenticationException() {
Jwt jwt = createJwt(Collections.singleton("unauthorized.scope"));
OAuth2AccessToken jwtAccessToken = new OAuth2AccessToken(OAuth2AccessToken.TokenType.BEARER,
jwt.getTokenValue(), jwt.getIssuedAt(),
jwt.getExpiresAt(), jwt.getClaim(OAuth2ParameterNames.SCOPE));
RegisteredClient registeredClient = TestRegisteredClients.registeredClient().build();
OAuth2Authorization authorization = TestOAuth2Authorizations.authorization(
registeredClient, jwtAccessToken, jwt.getClaims()).build();
when(this.authorizationService.findByToken(
eq(jwtAccessToken.getTokenValue()), eq(OAuth2TokenType.ACCESS_TOKEN)))
.thenReturn(authorization);
JwtAuthenticationToken principal = new JwtAuthenticationToken(
jwt, AuthorityUtils.createAuthorityList("SCOPE_unauthorized.scope"));
OidcClientRegistrationAuthenticationToken authentication = new OidcClientRegistrationAuthenticationToken(principal, registeredClient.getClientId());
assertThatThrownBy(() -> this.authenticationProvider.authenticate(authentication))
.isInstanceOf(OAuth2AuthenticationException.class)
.extracting(ex -> ((OAuth2AuthenticationException) ex).getError()).extracting("errorCode")
.isEqualTo(OAuth2ErrorCodes.INSUFFICIENT_SCOPE);
verify(this.authorizationService).findByToken(
eq(jwtAccessToken.getTokenValue()), eq(OAuth2TokenType.ACCESS_TOKEN));
}
@Test
public void authenticateWhenClientConfigurationRequestAndRegisteredClientNotFoundThenThrowOAuth2AuthenticationException() {
Jwt jwt = createJwtClientConfiguration();
OAuth2AccessToken jwtAccessToken = new OAuth2AccessToken(OAuth2AccessToken.TokenType.BEARER,
jwt.getTokenValue(), jwt.getIssuedAt(),
jwt.getExpiresAt(), jwt.getClaim(OAuth2ParameterNames.SCOPE));
RegisteredClient registeredClient = TestRegisteredClients.registeredClient()
.build();
OAuth2Authorization authorization = TestOAuth2Authorizations.authorization(
registeredClient, jwtAccessToken, jwt.getClaims()).build();
when(this.authorizationService.findByToken(
eq(jwtAccessToken.getTokenValue()), eq(OAuth2TokenType.ACCESS_TOKEN)))
.thenReturn(authorization);
when(this.registeredClientRepository.findByClientId(eq(registeredClient.getClientId())))
.thenReturn(null);
JwtAuthenticationToken principal = new JwtAuthenticationToken(
jwt, AuthorityUtils.createAuthorityList("SCOPE_client.read"));
OidcClientRegistrationAuthenticationToken authentication = new OidcClientRegistrationAuthenticationToken(principal, registeredClient.getClientId());
assertThatThrownBy(() -> this.authenticationProvider.authenticate(authentication))
.isInstanceOf(OAuth2AuthenticationException.class)
.extracting(ex -> ((OAuth2AuthenticationException) ex).getError()).extracting("errorCode")
.isEqualTo(OAuth2ErrorCodes.INVALID_CLIENT);
verify(this.authorizationService).findByToken(
eq(jwtAccessToken.getTokenValue()), eq(OAuth2TokenType.ACCESS_TOKEN));
verify(this.registeredClientRepository).findByClientId(
eq(registeredClient.getClientId()));
}
@Test
public void authenticateWhenClientConfigurationRequestRegisteredClientNotEqualToAuthorizationRegisteredClientThenThrowOAuth2AuthenticationException() {
Jwt jwt = createJwtClientConfiguration();
OAuth2AccessToken jwtAccessToken = new OAuth2AccessToken(OAuth2AccessToken.TokenType.BEARER,
jwt.getTokenValue(), jwt.getIssuedAt(),
jwt.getExpiresAt(), jwt.getClaim(OAuth2ParameterNames.SCOPE));
RegisteredClient registeredClient = TestRegisteredClients.registeredClient()
.id("registration-1").clientId("client-1").build();
RegisteredClient authorizationRegisteredClient = TestRegisteredClients.registeredClient()
.id("registration-2").clientId("client-2").build();
OAuth2Authorization authorization = TestOAuth2Authorizations.authorization(
authorizationRegisteredClient, jwtAccessToken, jwt.getClaims()).build();
when(this.authorizationService.findByToken(
eq(jwtAccessToken.getTokenValue()), eq(OAuth2TokenType.ACCESS_TOKEN)))
.thenReturn(authorization);
when(this.registeredClientRepository.findByClientId(eq(registeredClient.getClientId())))
.thenReturn(registeredClient);
JwtAuthenticationToken principal = new JwtAuthenticationToken(
jwt, AuthorityUtils.createAuthorityList("SCOPE_client.read"));
OidcClientRegistrationAuthenticationToken authentication = new OidcClientRegistrationAuthenticationToken(principal, registeredClient.getClientId());
assertThatThrownBy(() -> this.authenticationProvider.authenticate(authentication))
.isInstanceOf(OAuth2AuthenticationException.class)
.extracting(ex -> ((OAuth2AuthenticationException) ex).getError()).extracting("errorCode")
.isEqualTo(OAuth2ErrorCodes.INVALID_CLIENT);
verify(this.authorizationService).findByToken(
eq(jwtAccessToken.getTokenValue()), eq(OAuth2TokenType.ACCESS_TOKEN));
verify(this.registeredClientRepository).findByClientId(
eq(registeredClient.getClientId()));
}
@Test
public void authenticateWhenClientConfigurationRequestAndValidAccessTokenThenReturnClientRegistration() {
Jwt jwt = createJwtClientConfiguration();
OAuth2AccessToken jwtAccessToken = new OAuth2AccessToken(OAuth2AccessToken.TokenType.BEARER,
jwt.getTokenValue(), jwt.getIssuedAt(),
jwt.getExpiresAt(), jwt.getClaim(OAuth2ParameterNames.SCOPE));
RegisteredClient registeredClient = TestRegisteredClients.registeredClient()
.clientName("client-name")
.build();
OAuth2Authorization authorization = TestOAuth2Authorizations.authorization(
registeredClient, jwtAccessToken, jwt.getClaims()).build();
when(this.authorizationService.findByToken(
eq(jwtAccessToken.getTokenValue()), eq(OAuth2TokenType.ACCESS_TOKEN)))
.thenReturn(authorization);
JwtAuthenticationToken principal = new JwtAuthenticationToken(
jwt, AuthorityUtils.createAuthorityList("SCOPE_client.read"));
when(this.registeredClientRepository.findByClientId(eq(registeredClient.getClientId())))
.thenReturn(registeredClient);
OidcClientRegistrationAuthenticationToken authentication =
new OidcClientRegistrationAuthenticationToken(principal, registeredClient.getClientId());
OidcClientRegistrationAuthenticationToken authenticationResult =
(OidcClientRegistrationAuthenticationToken) this.authenticationProvider.authenticate(authentication);
verify(this.authorizationService).findByToken(
eq(jwtAccessToken.getTokenValue()), eq(OAuth2TokenType.ACCESS_TOKEN));
verify(this.registeredClientRepository).findByClientId(
eq(registeredClient.getClientId()));
// verify that the registration access token is not invalidated after its used
verify(this.authorizationService, times(0)).save(eq(authorization));
assertThat(authorization.getAccessToken().isInvalidated()).isFalse();
OidcClientRegistration clientRegistrationResult = authenticationResult.getClientRegistration();
assertThat(clientRegistrationResult.getClientId()).isEqualTo(registeredClient.getClientId());
assertThat(clientRegistrationResult.getClientIdIssuedAt()).isEqualTo(registeredClient.getClientIdIssuedAt());
assertThat(clientRegistrationResult.getClientSecret()).isEqualTo(registeredClient.getClientSecret());
assertThat(clientRegistrationResult.getClientSecretExpiresAt()).isEqualTo(registeredClient.getClientSecretExpiresAt());
assertThat(clientRegistrationResult.getClientName()).isEqualTo(registeredClient.getClientName());
assertThat(clientRegistrationResult.getRedirectUris())
.containsExactlyInAnyOrderElementsOf(registeredClient.getRedirectUris());
List<String> grantTypes = new ArrayList<>();
registeredClient.getAuthorizationGrantTypes().forEach(authorizationGrantType ->
grantTypes.add(authorizationGrantType.getValue()));
assertThat(clientRegistrationResult.getGrantTypes()).containsExactlyInAnyOrderElementsOf(grantTypes);
assertThat(clientRegistrationResult.getResponseTypes())
.containsExactly(OAuth2AuthorizationResponseType.CODE.getValue());
assertThat(clientRegistrationResult.getScopes())
.containsExactlyInAnyOrderElementsOf(registeredClient.getScopes());
assertThat(clientRegistrationResult.getTokenEndpointAuthenticationMethod())
.isEqualTo(registeredClient.getClientAuthenticationMethods().iterator().next().getValue());
assertThat(clientRegistrationResult.getIdTokenSignedResponseAlgorithm())
.isEqualTo(registeredClient.getTokenSettings().getIdTokenSignatureAlgorithm().getName());
String expectedRegistrationClientUri = UriComponentsBuilder.fromUriString(this.providerSettings.getIssuer())
.path(this.providerSettings.getOidcClientRegistrationEndpoint())
.queryParam("client_id", registeredClient.getClientId()).toUriString();
assertThat(clientRegistrationResult.getRegistrationClientUri().toString()).isEqualTo(expectedRegistrationClientUri);
assertThat(clientRegistrationResult.getRegistrationAccessToken()).isNull();
}
private static Jwt createJwtClientRegistration() {
return createJwt(Collections.singleton("client.create"));
}
private static Jwt createJwtClientConfiguration() {
return createJwt(Collections.singleton("client.read"));
}
private static Jwt createJwt(Set<String> scopes) {
// @formatter:off
JoseHeader joseHeader = TestJoseHeaders.joseHeader()

View File

@@ -43,18 +43,45 @@ public class OidcClientRegistrationAuthenticationTokenTests {
@Test
public void constructorWhenClientRegistrationNullThenThrowIllegalArgumentException() {
assertThatIllegalArgumentException()
.isThrownBy(() -> new OidcClientRegistrationAuthenticationToken(this.principal, null))
.isThrownBy(() -> new OidcClientRegistrationAuthenticationToken(this.principal, (OidcClientRegistration) null))
.withMessage("clientRegistration cannot be null");
}
@Test
public void constructorWhenAllValuesProvidedThenCreated() {
public void constructorWhenClientIdNullThenThrowIllegalArgumentException() {
assertThatIllegalArgumentException()
.isThrownBy(() -> new OidcClientRegistrationAuthenticationToken(this.principal, (String) null))
.withMessage("clientId cannot be null or empty");
}
@Test
public void constructorWhenClientIdEmptyThenThrowIllegalArgumentException() {
assertThatIllegalArgumentException()
.isThrownBy(() -> new OidcClientRegistrationAuthenticationToken(this.principal, ""))
.withMessage("clientId cannot be null or empty");
}
@Test
public void constructorWhenOidcClientRegistrationProvidedThenCreated() {
OidcClientRegistrationAuthenticationToken authentication = new OidcClientRegistrationAuthenticationToken(
this.principal, this.clientRegistration);
assertThat(authentication.getPrincipal()).isEqualTo(this.principal);
assertThat(authentication.getCredentials().toString()).isEmpty();
assertThat(authentication.getClientRegistration()).isEqualTo(this.clientRegistration);
assertThat(authentication.getClientId()).isNull();
assertThat(authentication.isAuthenticated()).isEqualTo(this.principal.isAuthenticated());
}
@Test
public void constructorWhenClientIdProvidedThenCreated() {
OidcClientRegistrationAuthenticationToken authentication = new OidcClientRegistrationAuthenticationToken(
this.principal, "client-1");
assertThat(authentication.getPrincipal()).isEqualTo(this.principal);
assertThat(authentication.getCredentials().toString()).isEmpty();
assertThat(authentication.getClientRegistration()).isNull();
assertThat(authentication.getClientId()).isEqualTo("client-1");
assertThat(authentication.isAuthenticated()).isEqualTo(this.principal.isAuthenticated());
}

View File

@@ -25,7 +25,6 @@ import javax.servlet.http.HttpServletResponse;
import org.junit.After;
import org.junit.Before;
import org.junit.Test;
import org.springframework.http.HttpStatus;
import org.springframework.http.converter.HttpMessageConverter;
import org.springframework.mock.http.client.MockClientHttpRequest;
@@ -162,7 +161,7 @@ public class OidcClientRegistrationEndpointFilterTests {
private void doFilterWhenClientRegistrationRequestInvalidThenError(
String errorCode, HttpStatus status) throws Exception {
Jwt jwt = createJwt();
Jwt jwt = createJwt("client.create");
JwtAuthenticationToken principal = new JwtAuthenticationToken(
jwt, AuthorityUtils.createAuthorityList("SCOPE_client.create"));
@@ -220,10 +219,12 @@ public class OidcClientRegistrationEndpointFilterTests {
.tokenEndpointAuthenticationMethod(ClientAuthenticationMethod.CLIENT_SECRET_BASIC.getValue())
.responseType(OAuth2AuthorizationResponseType.CODE.getValue())
.idTokenSignedResponseAlgorithm(SignatureAlgorithm.RS256.getName())
.registrationClientUri("http://auth-server:9000/connect/register?client_id=client-id")
.registrationAccessToken("registration-access-token")
.build();
// @formatter:on
Jwt jwt = createJwt();
Jwt jwt = createJwt("client.create");
JwtAuthenticationToken principal = new JwtAuthenticationToken(
jwt, AuthorityUtils.createAuthorityList("SCOPE_client.create"));
@@ -269,6 +270,198 @@ public class OidcClientRegistrationEndpointFilterTests {
.isEqualTo(expectedClientRegistrationResponse.getTokenEndpointAuthenticationMethod());
assertThat(clientRegistrationResponse.getIdTokenSignedResponseAlgorithm())
.isEqualTo(expectedClientRegistrationResponse.getIdTokenSignedResponseAlgorithm());
assertThat(clientRegistrationResponse.getRegistrationClientUri())
.isEqualTo(expectedClientRegistrationResponse.getRegistrationClientUri());
assertThat(clientRegistrationResponse.getRegistrationAccessToken())
.isEqualTo(expectedClientRegistrationResponse.getRegistrationAccessToken());
}
@Test
public void doFilterWhenNotClientConfigurationRequestThenNotProcessed() throws Exception {
String requestUri = "/path";
MockHttpServletRequest request = new MockHttpServletRequest("GET", requestUri);
request.setServletPath(requestUri);
MockHttpServletResponse response = new MockHttpServletResponse();
FilterChain filterChain = mock(FilterChain.class);
this.filter.doFilter(request, response, filterChain);
verify(filterChain).doFilter(any(HttpServletRequest.class), any(HttpServletResponse.class));
}
@Test
public void doFilterWhenClientConfigurationRequestPutThenNotProcessed() throws Exception {
String requestUri = DEFAULT_OIDC_CLIENT_REGISTRATION_ENDPOINT_URI;
MockHttpServletRequest request = new MockHttpServletRequest("PUT", requestUri);
request.setServletPath(requestUri);
MockHttpServletResponse response = new MockHttpServletResponse();
FilterChain filterChain = mock(FilterChain.class);
this.filter.doFilter(request, response, filterChain);
verify(filterChain).doFilter(any(HttpServletRequest.class), any(HttpServletResponse.class));
}
@Test
public void doFilterWhenClientConfigurationRequestMissingClientIdThenNotProcessed() throws Exception {
String requestUri = DEFAULT_OIDC_CLIENT_REGISTRATION_ENDPOINT_URI;
MockHttpServletRequest request = new MockHttpServletRequest("GET", requestUri);
request.setServletPath(requestUri);
MockHttpServletResponse response = new MockHttpServletResponse();
FilterChain filterChain = mock(FilterChain.class);
this.filter.doFilter(request, response, filterChain);
verify(filterChain).doFilter(any(HttpServletRequest.class), any(HttpServletResponse.class));
}
@Test
public void doFilterWhenClientConfigurationRequestEmptyClientIdThenNotProcessed() throws Exception {
String requestUri = DEFAULT_OIDC_CLIENT_REGISTRATION_ENDPOINT_URI;
MockHttpServletRequest request = new MockHttpServletRequest("GET", requestUri);
request.setServletPath(requestUri);
request.addParameter(OAuth2ParameterNames.CLIENT_ID, "");
MockHttpServletResponse response = new MockHttpServletResponse();
FilterChain filterChain = mock(FilterChain.class);
this.filter.doFilter(request, response, filterChain);
verify(filterChain).doFilter(any(HttpServletRequest.class), any(HttpServletResponse.class));
}
@Test
public void doFilterWhenClientConfigurationRequestMultipleClientIdParametersThenInvalidClientError() throws Exception {
String requestUri = DEFAULT_OIDC_CLIENT_REGISTRATION_ENDPOINT_URI;
MockHttpServletRequest request = new MockHttpServletRequest("GET", requestUri);
request.setServletPath(requestUri);
request.addParameter(OAuth2ParameterNames.CLIENT_ID, "client-id");
request.addParameter(OAuth2ParameterNames.CLIENT_ID, "client-id2");
MockHttpServletResponse response = new MockHttpServletResponse();
FilterChain filterChain = mock(FilterChain.class);
this.filter.doFilter(request, response, filterChain);
verifyNoInteractions(filterChain);
assertThat(response.getStatus()).isEqualTo(HttpStatus.UNAUTHORIZED.value());
OAuth2Error error = readError(response);
assertThat(error.getErrorCode()).isEqualTo(OAuth2ErrorCodes.INVALID_CLIENT);
}
@Test
public void doFilterWhenClientConfigurationRequestInvalidTokenThenUnauthorizedError() throws Exception {
doFilterWhenClientConfigurationRequestInvalidThenError(
OAuth2ErrorCodes.INVALID_TOKEN, HttpStatus.UNAUTHORIZED);
}
@Test
public void doFilterWhenClientConfigurationRequestInsufficientTokenScopeThenForbiddenError() throws Exception {
doFilterWhenClientConfigurationRequestInvalidThenError(
OAuth2ErrorCodes.INSUFFICIENT_SCOPE, HttpStatus.FORBIDDEN);
}
@Test
public void doFilterWhenClientConfigurationRequestInvalidClientThenUnauthorizedError() throws Exception {
doFilterWhenClientConfigurationRequestInvalidThenError(
OAuth2ErrorCodes.INVALID_CLIENT, HttpStatus.UNAUTHORIZED);
}
@Test
public void doFilterWhenClientConfigurationRequestValidThenSuccessResponse() throws Exception {
// @formatter:off
OidcClientRegistration expectedClientRegistrationResponse = OidcClientRegistration.builder()
.clientId("client-id")
.clientIdIssuedAt(Instant.now())
.clientSecret("client-secret")
.clientName("client-name")
.redirectUri("https://client.example.com")
.grantType(AuthorizationGrantType.AUTHORIZATION_CODE.getValue())
.grantType(AuthorizationGrantType.CLIENT_CREDENTIALS.getValue())
.tokenEndpointAuthenticationMethod(ClientAuthenticationMethod.CLIENT_SECRET_BASIC.getValue())
.responseType(OAuth2AuthorizationResponseType.CODE.getValue())
.idTokenSignedResponseAlgorithm(SignatureAlgorithm.RS256.getName())
.scope("scope1")
.scope("scope2")
.registrationClientUri("http://auth-server:9000/connect/register?client_id=client-id")
.build();
// @formatter:on
Jwt jwt = createJwt("client.read");
JwtAuthenticationToken principal = new JwtAuthenticationToken(
jwt, AuthorityUtils.createAuthorityList("SCOPE_client.read"));
OidcClientRegistrationAuthenticationToken clientConfigurationAuthenticationResult =
new OidcClientRegistrationAuthenticationToken(principal, expectedClientRegistrationResponse);
when(this.authenticationManager.authenticate(any())).thenReturn(clientConfigurationAuthenticationResult);
SecurityContext securityContext = SecurityContextHolder.createEmptyContext();
securityContext.setAuthentication(principal);
SecurityContextHolder.setContext(securityContext);
String requestUri = DEFAULT_OIDC_CLIENT_REGISTRATION_ENDPOINT_URI;
MockHttpServletRequest request = new MockHttpServletRequest("GET", requestUri);
request.setServletPath(requestUri);
request.setParameter(OAuth2ParameterNames.CLIENT_ID, "client-id");
MockHttpServletResponse response = new MockHttpServletResponse();
FilterChain filterChain = mock(FilterChain.class);
this.filter.doFilter(request, response, filterChain);
verifyNoInteractions(filterChain);
assertThat(response.getStatus()).isEqualTo(HttpStatus.OK.value());
OidcClientRegistration clientRegistrationResponse = readClientRegistrationResponse(response);
assertThat(clientRegistrationResponse.getClientId()).isEqualTo(expectedClientRegistrationResponse.getClientId());
assertThat(clientRegistrationResponse.getClientIdIssuedAt()).isBetween(
expectedClientRegistrationResponse.getClientIdIssuedAt().minusSeconds(1),
expectedClientRegistrationResponse.getClientIdIssuedAt().plusSeconds(1));
assertThat(clientRegistrationResponse.getClientSecret()).isEqualTo(expectedClientRegistrationResponse.getClientSecret());
assertThat(clientRegistrationResponse.getClientSecretExpiresAt()).isEqualTo(expectedClientRegistrationResponse.getClientSecretExpiresAt());
assertThat(clientRegistrationResponse.getClientName()).isEqualTo(expectedClientRegistrationResponse.getClientName());
assertThat(clientRegistrationResponse.getRedirectUris())
.containsExactlyInAnyOrderElementsOf(expectedClientRegistrationResponse.getRedirectUris());
assertThat(clientRegistrationResponse.getGrantTypes())
.containsExactlyInAnyOrderElementsOf(expectedClientRegistrationResponse.getGrantTypes());
assertThat(clientRegistrationResponse.getResponseTypes())
.containsExactlyInAnyOrderElementsOf(expectedClientRegistrationResponse.getResponseTypes());
assertThat(clientRegistrationResponse.getScopes())
.containsExactlyInAnyOrderElementsOf(expectedClientRegistrationResponse.getScopes());
assertThat(clientRegistrationResponse.getTokenEndpointAuthenticationMethod())
.isEqualTo(expectedClientRegistrationResponse.getTokenEndpointAuthenticationMethod());
assertThat(clientRegistrationResponse.getIdTokenSignedResponseAlgorithm())
.isEqualTo(expectedClientRegistrationResponse.getIdTokenSignedResponseAlgorithm());
assertThat(clientRegistrationResponse.getRegistrationClientUri())
.isEqualTo(expectedClientRegistrationResponse.getRegistrationClientUri());
}
private void doFilterWhenClientConfigurationRequestInvalidThenError(
String errorCode, HttpStatus status) throws Exception {
Jwt jwt = createJwt("client.read");
JwtAuthenticationToken principal = new JwtAuthenticationToken(
jwt, AuthorityUtils.createAuthorityList("SCOPE_client.read"));
SecurityContext securityContext = SecurityContextHolder.createEmptyContext();
securityContext.setAuthentication(principal);
SecurityContextHolder.setContext(securityContext);
when(this.authenticationManager.authenticate(any()))
.thenThrow(new OAuth2AuthenticationException(errorCode));
String requestUri = DEFAULT_OIDC_CLIENT_REGISTRATION_ENDPOINT_URI;
MockHttpServletRequest request = new MockHttpServletRequest("GET", requestUri);
request.setServletPath(requestUri);
request.setParameter(OAuth2ParameterNames.CLIENT_ID, "client1");
MockHttpServletResponse response = new MockHttpServletResponse();
FilterChain filterChain = mock(FilterChain.class);
this.filter.doFilter(request, response, filterChain);
verifyNoInteractions(filterChain);
assertThat(response.getStatus()).isEqualTo(status.value());
OAuth2Error error = readError(response);
assertThat(error.getErrorCode()).isEqualTo(errorCode);
}
private OAuth2Error readError(MockHttpServletResponse response) throws Exception {
@@ -290,12 +483,12 @@ public class OidcClientRegistrationEndpointFilterTests {
return this.clientRegistrationHttpMessageConverter.read(OidcClientRegistration.class, httpResponse);
}
private static Jwt createJwt() {
private static Jwt createJwt(String scope) {
// @formatter:off
JoseHeader joseHeader = TestJoseHeaders.joseHeader()
.build();
JwtClaimsSet jwtClaimsSet = TestJwtClaimsSets.jwtClaimsSet()
.claim(OAuth2ParameterNames.SCOPE, Collections.singleton("client.create"))
.claim(OAuth2ParameterNames.SCOPE, Collections.singleton(scope))
.build();
Jwt jwt = Jwt.withTokenValue("jwt-access-token")
.headers(headers -> headers.putAll(joseHeader.getHeaders()))