Polish gh-189

This commit is contained in:
Joe Grandja
2021-04-30 20:51:49 -04:00
parent 8224a0d971
commit e7feb6c0ed
36 changed files with 2410 additions and 1627 deletions

View File

@@ -22,6 +22,7 @@ import org.junit.Before;
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.context.annotation.Import;

View File

@@ -53,11 +53,10 @@ import org.springframework.security.oauth2.core.endpoint.OAuth2ParameterNames;
import org.springframework.security.oauth2.core.endpoint.PkceParameterNames;
import org.springframework.security.oauth2.core.http.converter.OAuth2AccessTokenResponseHttpMessageConverter;
import org.springframework.security.oauth2.jose.TestJwks;
import org.springframework.security.oauth2.jose.TestKeys;
import org.springframework.security.oauth2.jwt.Jwt;
import org.springframework.security.oauth2.jwt.JwtDecoder;
import org.springframework.security.oauth2.jwt.JwtEncoder;
import org.springframework.security.oauth2.jwt.NimbusJwsEncoder;
import org.springframework.security.oauth2.jwt.NimbusJwtDecoder;
import org.springframework.security.oauth2.server.authorization.JwtEncodingContext;
import org.springframework.security.oauth2.server.authorization.OAuth2Authorization;
import org.springframework.security.oauth2.server.authorization.OAuth2AuthorizationCode;
@@ -111,7 +110,6 @@ public class OAuth2AuthorizationCodeGrantTests {
private static OAuth2AuthorizationService authorizationService;
private static JWKSource<SecurityContext> jwkSource;
private static NimbusJwsEncoder jwtEncoder;
private static NimbusJwtDecoder jwtDecoder;
private static ProviderSettings providerSettings;
private static HttpMessageConverter<OAuth2AccessTokenResponse> accessTokenHttpResponseConverter =
new OAuth2AccessTokenResponseHttpMessageConverter();
@@ -122,6 +120,9 @@ public class OAuth2AuthorizationCodeGrantTests {
@Autowired
private MockMvc mvc;
@Autowired
private JwtDecoder jwtDecoder;
@BeforeClass
public static void init() {
registeredClientRepository = mock(RegisteredClientRepository.class);
@@ -129,7 +130,6 @@ public class OAuth2AuthorizationCodeGrantTests {
JWKSet jwkSet = new JWKSet(TestJwks.DEFAULT_RSA_JWK);
jwkSource = (jwkSelector, securityContext) -> jwkSelector.select(jwkSet);
jwtEncoder = new NimbusJwsEncoder(jwkSource);
jwtDecoder = NimbusJwtDecoder.withPublicKey(TestKeys.DEFAULT_PUBLIC_KEY).build();
providerSettings = new ProviderSettings()
.authorizationEndpoint("/test/authorize")
.tokenEndpoint("/test/token");
@@ -206,7 +206,7 @@ public class OAuth2AuthorizationCodeGrantTests {
registeredClient, authorization, OAuth2TokenEndpointFilter.DEFAULT_TOKEN_ENDPOINT_URI);
// Assert user authorities was propagated as claim in JWT
Jwt jwt = jwtDecoder.decode(accessTokenResponse.getAccessToken().getTokenValue());
Jwt jwt = this.jwtDecoder.decode(accessTokenResponse.getAccessToken().getTokenValue());
List<String> authoritiesClaim = jwt.getClaim(AUTHORITIES_CLAIM);
Authentication principal = authorization.getAttribute(Principal.class.getName());
Set<String> userAuthorities = principal.getAuthorities().stream()

View File

@@ -15,6 +15,10 @@
*/
package org.springframework.security.config.annotation.web.configurers.oauth2.server.authorization;
import java.net.URLEncoder;
import java.nio.charset.StandardCharsets;
import java.util.Base64;
import com.nimbusds.jose.jwk.JWKSet;
import com.nimbusds.jose.jwk.source.JWKSource;
import com.nimbusds.jose.proc.SecurityContext;
@@ -22,6 +26,7 @@ import org.junit.Before;
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.context.annotation.Import;
@@ -42,10 +47,6 @@ import org.springframework.security.oauth2.server.authorization.web.OAuth2TokenE
import org.springframework.test.web.servlet.MockMvc;
import org.springframework.test.web.servlet.request.MockMvcRequestBuilders;
import java.net.URLEncoder;
import java.nio.charset.StandardCharsets;
import java.util.Base64;
import static org.mockito.ArgumentMatchers.any;
import static org.mockito.ArgumentMatchers.eq;
import static org.mockito.Mockito.mock;

View File

@@ -15,6 +15,14 @@
*/
package org.springframework.security.config.annotation.web.configurers.oauth2.server.authorization;
import java.net.URLEncoder;
import java.nio.charset.StandardCharsets;
import java.security.Principal;
import java.util.Base64;
import java.util.List;
import java.util.Set;
import java.util.stream.Collectors;
import com.nimbusds.jose.jwk.JWKSet;
import com.nimbusds.jose.jwk.source.JWKSource;
import com.nimbusds.jose.proc.SecurityContext;
@@ -22,6 +30,7 @@ import org.junit.Before;
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.context.annotation.Import;
@@ -58,14 +67,6 @@ import org.springframework.test.web.servlet.MvcResult;
import org.springframework.util.LinkedMultiValueMap;
import org.springframework.util.MultiValueMap;
import java.net.URLEncoder;
import java.nio.charset.StandardCharsets;
import java.security.Principal;
import java.util.Base64;
import java.util.List;
import java.util.Set;
import java.util.stream.Collectors;
import static org.assertj.core.api.Assertions.assertThat;
import static org.hamcrest.CoreMatchers.containsString;
import static org.mockito.ArgumentMatchers.any;

View File

@@ -15,6 +15,10 @@
*/
package org.springframework.security.config.annotation.web.configurers.oauth2.server.authorization;
import java.net.URLEncoder;
import java.nio.charset.StandardCharsets;
import java.util.Base64;
import com.nimbusds.jose.jwk.JWKSet;
import com.nimbusds.jose.jwk.source.JWKSource;
import com.nimbusds.jose.proc.SecurityContext;
@@ -23,6 +27,7 @@ import org.junit.BeforeClass;
import org.junit.Rule;
import org.junit.Test;
import org.mockito.ArgumentCaptor;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Import;
@@ -48,10 +53,6 @@ import org.springframework.test.web.servlet.MockMvc;
import org.springframework.util.LinkedMultiValueMap;
import org.springframework.util.MultiValueMap;
import java.net.URLEncoder;
import java.nio.charset.StandardCharsets;
import java.util.Base64;
import static org.assertj.core.api.Assertions.assertThat;
import static org.mockito.ArgumentMatchers.eq;
import static org.mockito.ArgumentMatchers.isNull;

View File

@@ -15,8 +15,10 @@
*/
package org.springframework.security.config.annotation.web.configurers.oauth2.server.authorization;
import com.fasterxml.jackson.core.JsonProcessingException;
import com.fasterxml.jackson.databind.ObjectMapper;
import java.net.URLEncoder;
import java.nio.charset.StandardCharsets;
import java.util.Base64;
import com.nimbusds.jose.jwk.JWKSet;
import com.nimbusds.jose.jwk.source.JWKSource;
import com.nimbusds.jose.proc.SecurityContext;
@@ -25,6 +27,7 @@ import org.junit.BeforeClass;
import org.junit.Rule;
import org.junit.Test;
import org.mockito.ArgumentCaptor;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Import;
@@ -32,6 +35,7 @@ import org.springframework.http.HttpHeaders;
import org.springframework.http.HttpStatus;
import org.springframework.http.MediaType;
import org.springframework.http.converter.HttpMessageConverter;
import org.springframework.mock.http.MockHttpOutputMessage;
import org.springframework.mock.http.client.MockClientHttpResponse;
import org.springframework.mock.web.MockHttpServletResponse;
import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity;
@@ -48,25 +52,19 @@ import org.springframework.security.oauth2.core.http.converter.OAuth2AccessToken
import org.springframework.security.oauth2.core.oidc.OidcClientRegistration;
import org.springframework.security.oauth2.core.oidc.http.converter.OidcClientRegistrationHttpMessageConverter;
import org.springframework.security.oauth2.jose.TestJwks;
import org.springframework.security.oauth2.jose.TestKeys;
import org.springframework.security.oauth2.jwt.JwtDecoder;
import org.springframework.security.oauth2.jwt.NimbusJwtDecoder;
import org.springframework.security.oauth2.jose.jws.SignatureAlgorithm;
import org.springframework.security.oauth2.server.authorization.OAuth2Authorization;
import org.springframework.security.oauth2.server.authorization.OAuth2AuthorizationService;
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.authorization.oidc.web.OidcClientRegistrationEndpointFilter;
import org.springframework.security.oauth2.server.authorization.web.OAuth2TokenEndpointFilter;
import org.springframework.test.web.servlet.MockMvc;
import org.springframework.test.web.servlet.MvcResult;
import java.net.URLEncoder;
import java.nio.charset.StandardCharsets;
import java.util.Base64;
import java.util.Map;
import static org.assertj.core.api.Assertions.assertThat;
import static org.hamcrest.CoreMatchers.containsString;
import static org.mockito.ArgumentMatchers.any;
import static org.mockito.ArgumentMatchers.eq;
import static org.mockito.Mockito.doNothing;
@@ -75,35 +73,24 @@ import static org.mockito.Mockito.reset;
import static org.mockito.Mockito.verify;
import static org.mockito.Mockito.when;
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;
import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status;
/**
* Integration tests for OpenID Connect 1.0 Client Registration Endpoint.
* Integration tests for OpenID Connect Dynamic Client Registration 1.0.
*
* @author Ovidiu Popa
* @since 0.1.1
* @author Joe Grandja
*/
public class OidcClientRegistrationTests {
private static final OidcClientRegistration.Builder OIDC_CLIENT_REGISTRATION = OidcClientRegistration.builder()
.redirectUri("https://localhost:8080/client")
.responseType(OAuth2AuthorizationResponseType.CODE.getValue())
.grantType(AuthorizationGrantType.AUTHORIZATION_CODE.getValue())
.tokenEndpointAuthenticationMethod(ClientAuthenticationMethod.BASIC.getValue())
.scope("test");
private static final HttpMessageConverter<OAuth2AccessTokenResponse> accessTokenHttpResponseConverter =
new OAuth2AccessTokenResponseHttpMessageConverter();
private static final OidcClientRegistrationHttpMessageConverter clientRegistrationHttpMessageConverter =
private static final HttpMessageConverter<OidcClientRegistration> clientRegistrationHttpMessageConverter =
new OidcClientRegistrationHttpMessageConverter();
private static final OAuth2TokenType ACCESS_TOKEN_TOKEN_TYPE = new OAuth2TokenType(OAuth2ParameterNames.ACCESS_TOKEN);
private static RegisteredClientRepository registeredClientRepository;
private static OAuth2AuthorizationService authorizationService;
private static JWKSource<SecurityContext> jwkSource;
private static NimbusJwtDecoder jwtDecoder;
@Rule
public final SpringTestRule spring = new SpringTestRule();
@@ -117,7 +104,6 @@ public class OidcClientRegistrationTests {
authorizationService = mock(OAuth2AuthorizationService.class);
JWKSet jwkSet = new JWKSet(TestJwks.DEFAULT_RSA_JWK);
jwkSource = (jwkSelector, securityContext) -> jwkSelector.select(jwkSet);
jwtDecoder = NimbusJwtDecoder.withPublicKey(TestKeys.DEFAULT_PUBLIC_KEY).build();
}
@Before
@@ -127,63 +113,83 @@ public class OidcClientRegistrationTests {
}
@Test
public void requestWhenAuthenticatedThenResponseIncludesRegisteredClientDetails() throws Exception {
this.spring.register(AuthorizationServerConfigurationEnabledClientRegistration.class).autowire();
public void requestWhenClientRegistrationRequestAuthorizedThenClientRegistrationResponse() 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("client.create").build();
.scope(clientRegistrationScope)
.build();
when(registeredClientRepository.findByClientId(eq(registeredClient.getClientId())))
.thenReturn(registeredClient);
// get access token
MvcResult mvcResult = this.mvc.perform(post(OAuth2TokenEndpointFilter.DEFAULT_TOKEN_ENDPOINT_URI)
.param(OAuth2ParameterNames.GRANT_TYPE, AuthorizationGrantType.CLIENT_CREDENTIALS.getValue())
.param(OAuth2ParameterNames.SCOPE, "client.create")
.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("client.create"))
.andExpect(jsonPath("$.scope").value(clientRegistrationScope))
.andReturn();
//assert get access token
OAuth2AccessToken accessToken = readAccessTokenResponse(mvcResult.getResponse()).getAccessToken();
verify(registeredClientRepository).findByClientId(eq(registeredClient.getClientId()));
ArgumentCaptor<OAuth2Authorization> authorizationCaptor = ArgumentCaptor.forClass(OAuth2Authorization.class);
verify(authorizationService).save(authorizationCaptor.capture());
OAuth2Authorization authorization = authorizationCaptor.getValue();
MockHttpServletResponse servletResponse = mvcResult.getResponse();
MockClientHttpResponse httpResponse = new MockClientHttpResponse(
servletResponse.getContentAsByteArray(), HttpStatus.valueOf(servletResponse.getStatus()));
OAuth2AccessTokenResponse accessTokenResponse = accessTokenHttpResponseConverter.read(OAuth2AccessTokenResponse.class, httpResponse);
String tokenValue = accessTokenResponse.getAccessToken().getTokenValue();
// prepare register client request
when(authorizationService.findByToken(
eq(authorization.getToken(OAuth2AccessToken.class).getToken().getTokenValue()),
eq(ACCESS_TOKEN_TOKEN_TYPE)))
// ***** (2) Register the client
when(authorizationService.findByToken(eq(accessToken.getTokenValue()), eq(OAuth2TokenType.ACCESS_TOKEN)))
.thenReturn(authorization);
doNothing().when(registeredClientRepository).saveClient(any(RegisteredClient.class));
mvcResult = this.mvc.perform(post("/connect/register")
.header(HttpHeaders.AUTHORIZATION, "Bearer " + tokenValue)
doNothing().when(registeredClientRepository).save(any(RegisteredClient.class));
// @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(OidcClientRegistrationEndpointFilter.DEFAULT_OIDC_CLIENT_REGISTRATION_ENDPOINT_URI)
.headers(httpHeaders)
.contentType(MediaType.APPLICATION_JSON)
.content(convertToByteArray(OIDC_CLIENT_REGISTRATION.build())))
.andExpect(status().isCreated()).andReturn();
.content(getClientRegistrationRequestContent(clientRegistration)))
.andExpect(status().isCreated())
.andExpect(header().string(HttpHeaders.CACHE_CONTROL, containsString("no-store")))
.andExpect(header().string(HttpHeaders.PRAGMA, containsString("no-cache")))
.andReturn();
servletResponse = mvcResult.getResponse();
httpResponse = new MockClientHttpResponse(
servletResponse.getContentAsByteArray(), HttpStatus.valueOf(servletResponse.getStatus()));
OidcClientRegistration result = clientRegistrationHttpMessageConverter.read(OidcClientRegistration.class, httpResponse);
assertThat(result).isNotNull();
assertThat(result.getClaimAsString("client_id")).isNotEmpty();
assertThat(result.getClaimAsString("client_id_issued_at")).isNotEmpty();
assertThat(result.getClaimAsString("client_secret")).isNotEmpty();
assertThat(result.getClaimAsString("client_secret_expires_at")).isNotNull().isEqualTo("0.0");
assertThat(result.getRedirectUris()).isNotEmpty().containsExactly("https://localhost:8080/client");
assertThat(result.getResponseTypes()).isNotEmpty().containsExactly(OAuth2AuthorizationResponseType.CODE.getValue());
assertThat(result.getGrantTypes()).isNotEmpty().containsExactly(AuthorizationGrantType.AUTHORIZATION_CODE.getValue());
assertThat(result.getTokenEndpointAuthenticationMethod()).isNotEmpty().isEqualTo(ClientAuthenticationMethod.BASIC.getValue());
assertThat(result.getScope()).isNotEmpty().isEqualTo("test");
OidcClientRegistration clientRegistrationResponse = readClientRegistrationResponse(mvcResult.getResponse());
assertThat(clientRegistrationResponse.getClientId()).isNotNull();
assertThat(clientRegistrationResponse.getClientIdIssuedAt()).isNotNull();
assertThat(clientRegistrationResponse.getClientSecret()).isNotNull();
assertThat(clientRegistrationResponse.getClientSecretExpiresAt()).isNull();
assertThat(clientRegistrationResponse.getClientName()).isEqualTo(clientRegistration.getClientName());
assertThat(clientRegistrationResponse.getRedirectUris())
.containsExactlyInAnyOrderElementsOf(clientRegistration.getRedirectUris());
assertThat(clientRegistrationResponse.getGrantTypes())
.containsExactlyInAnyOrderElementsOf(clientRegistration.getGrantTypes());
assertThat(clientRegistrationResponse.getResponseTypes())
.containsExactly(OAuth2AuthorizationResponseType.CODE.getValue());
assertThat(clientRegistrationResponse.getScopes())
.containsExactlyInAnyOrderElementsOf(clientRegistration.getScopes());
assertThat(clientRegistrationResponse.getTokenEndpointAuthenticationMethod())
.isEqualTo(ClientAuthenticationMethod.BASIC.getValue());
assertThat(clientRegistrationResponse.getIdTokenSignedResponseAlgorithm())
.isEqualTo(SignatureAlgorithm.RS256.getName());
}
private static String encodeBasicAuth(String clientId, String secret) throws Exception {
@@ -194,12 +200,22 @@ public class OidcClientRegistrationTests {
return new String(encodedBytes, StandardCharsets.UTF_8);
}
private static byte[] convertToByteArray(OidcClientRegistration clientRegistration) throws JsonProcessingException {
ObjectMapper objectMapper = new ObjectMapper();
private static OAuth2AccessTokenResponse readAccessTokenResponse(MockHttpServletResponse response) throws Exception {
MockClientHttpResponse httpResponse = new MockClientHttpResponse(
response.getContentAsByteArray(), HttpStatus.valueOf(response.getStatus()));
return accessTokenHttpResponseConverter.read(OAuth2AccessTokenResponse.class, httpResponse);
}
return objectMapper
.writerFor(Map.class)
.writeValueAsBytes(clientRegistration.getClaims());
private static byte[] getClientRegistrationRequestContent(OidcClientRegistration clientRegistration) throws Exception {
MockHttpOutputMessage httpRequest = new MockHttpOutputMessage();
clientRegistrationHttpMessageConverter.write(clientRegistration, null, httpRequest);
return httpRequest.getBodyAsBytes();
}
private static OidcClientRegistration readClientRegistrationResponse(MockHttpServletResponse response) throws Exception {
MockClientHttpResponse httpResponse = new MockClientHttpResponse(
response.getContentAsByteArray(), HttpStatus.valueOf(response.getStatus()));
return clientRegistrationHttpMessageConverter.read(OidcClientRegistration.class, httpResponse);
}
@EnableWebSecurity
@@ -221,21 +237,5 @@ public class OidcClientRegistrationTests {
return jwkSource;
}
}
@EnableWebSecurity
@Import(OAuth2AuthorizationServerConfiguration.class)
static class AuthorizationServerConfigurationEnabledClientRegistration extends AuthorizationServerConfiguration{
@Bean
JwtDecoder jwtDecoder() {
return jwtDecoder;
}
@Bean
ProviderSettings providerSettings() {
return new ProviderSettings().isOidClientRegistrationEndpointEnabled(true);
}
}
}

View File

@@ -54,10 +54,8 @@ import org.springframework.security.oauth2.core.http.converter.OAuth2AccessToken
import org.springframework.security.oauth2.core.oidc.OidcScopes;
import org.springframework.security.oauth2.core.oidc.endpoint.OidcParameterNames;
import org.springframework.security.oauth2.jose.TestJwks;
import org.springframework.security.oauth2.jose.TestKeys;
import org.springframework.security.oauth2.jwt.Jwt;
import org.springframework.security.oauth2.jwt.JwtDecoder;
import org.springframework.security.oauth2.jwt.NimbusJwtDecoder;
import org.springframework.security.oauth2.server.authorization.JwtEncodingContext;
import org.springframework.security.oauth2.server.authorization.OAuth2Authorization;
import org.springframework.security.oauth2.server.authorization.OAuth2AuthorizationCode;
@@ -105,7 +103,6 @@ public class OidcTests {
private static RegisteredClientRepository registeredClientRepository;
private static OAuth2AuthorizationService authorizationService;
private static JWKSource<SecurityContext> jwkSource;
private static NimbusJwtDecoder jwtDecoder;
private static HttpMessageConverter<OAuth2AccessTokenResponse> accessTokenHttpResponseConverter =
new OAuth2AccessTokenResponseHttpMessageConverter();
@@ -115,13 +112,15 @@ public class OidcTests {
@Autowired
private MockMvc mvc;
@Autowired
private JwtDecoder jwtDecoder;
@BeforeClass
public static void init() {
registeredClientRepository = mock(RegisteredClientRepository.class);
authorizationService = mock(OAuth2AuthorizationService.class);
JWKSet jwkSet = new JWKSet(TestJwks.DEFAULT_RSA_JWK);
jwkSource = (jwkSelector, securityContext) -> jwkSelector.select(jwkSet);
jwtDecoder = NimbusJwtDecoder.withPublicKey(TestKeys.DEFAULT_PUBLIC_KEY).build();
}
@Before
@@ -206,7 +205,7 @@ public class OidcTests {
OAuth2AccessTokenResponse accessTokenResponse = accessTokenHttpResponseConverter.read(OAuth2AccessTokenResponse.class, httpResponse);
// Assert user authorities was propagated as claim in ID Token
Jwt idToken = jwtDecoder.decode((String) accessTokenResponse.getAdditionalParameters().get(OidcParameterNames.ID_TOKEN));
Jwt idToken = this.jwtDecoder.decode((String) accessTokenResponse.getAdditionalParameters().get(OidcParameterNames.ID_TOKEN));
List<String> authoritiesClaim = idToken.getClaim(AUTHORITIES_CLAIM);
Authentication principal = authorization.getAttribute(Principal.class.getName());
Set<String> userAuthorities = principal.getAuthorities().stream()
@@ -275,10 +274,6 @@ public class OidcTests {
};
}
@Bean
JwtDecoder jwtDecoder(){
return jwtDecoder;
}
}
@EnableWebSecurity

View File

@@ -15,317 +15,384 @@
*/
package org.springframework.security.oauth2.core.oidc;
import org.junit.Test;
import org.springframework.security.oauth2.core.AuthorizationGrantType;
import org.springframework.security.oauth2.core.ClientAuthenticationMethod;
import org.springframework.security.oauth2.core.endpoint.OAuth2AuthorizationResponseType;
import java.net.URL;
import java.time.Instant;
import java.time.temporal.ChronoUnit;
import java.util.Arrays;
import java.util.Collections;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import org.junit.Test;
import org.springframework.security.oauth2.core.AuthorizationGrantType;
import org.springframework.security.oauth2.core.ClientAuthenticationMethod;
import org.springframework.security.oauth2.core.endpoint.OAuth2AuthorizationResponseType;
import org.springframework.security.oauth2.jose.jws.SignatureAlgorithm;
import static org.assertj.core.api.Assertions.assertThat;
import static org.assertj.core.api.Assertions.assertThatThrownBy;
import static org.assertj.core.api.Assertions.assertThatIllegalArgumentException;
/**
* Tests for {@link OidcClientRegistration}
* Tests for {@link OidcClientRegistration}.
*
* @author Ovidiu Popa
* @since 0.1.1
* @author Joe Grandja
*/
public class OidcClientRegistrationTests {
private final OidcClientRegistration.Builder clientRegistrationBuilder =
OidcClientRegistration.builder();
// @formatter:off
private final OidcClientRegistration.Builder minimalBuilder =
OidcClientRegistration.builder()
.redirectUri("https://client.example.com");
// @formatter:on
@Test
public void buildWhenAllRequiredClaimsAndAdditionalClaimsThenCreated() {
public void buildWhenAllClaimsProvidedThenCreated() {
// @formatter:off
Instant clientIdIssuedAt = Instant.now();
Instant clientSecretExpiresAt = clientIdIssuedAt.plus(30, ChronoUnit.DAYS);
OidcClientRegistration clientRegistration = OidcClientRegistration.builder()
.redirectUri("http://client.example.com")
.clientId("client-id")
.clientIdIssuedAt(clientIdIssuedAt)
.clientSecret("client-secret")
.clientSecretExpiresAt(clientSecretExpiresAt)
.clientName("client-name")
.redirectUri("https://client.example.com")
.tokenEndpointAuthenticationMethod(ClientAuthenticationMethod.BASIC.getValue())
.grantType(AuthorizationGrantType.AUTHORIZATION_CODE.getValue())
.grantType(AuthorizationGrantType.CLIENT_CREDENTIALS.getValue())
.responseType(OAuth2AuthorizationResponseType.CODE.getValue())
.scope("test read")
.tokenEndpointAuthenticationMethod(ClientAuthenticationMethod.BASIC.getValue())
.scope("scope1")
.scope("scope2")
.idTokenSignedResponseAlgorithm(SignatureAlgorithm.RS256.getName())
.claim("a-claim", "a-value")
.build();
// @formatter:on
assertThat(clientRegistration.getRedirectUris())
.containsOnly("http://client.example.com");
assertThat(clientRegistration.getGrantTypes())
.contains(
AuthorizationGrantType.AUTHORIZATION_CODE.getValue(),
AuthorizationGrantType.CLIENT_CREDENTIALS.getValue()
);
assertThat(clientRegistration.getResponseTypes())
.contains(OAuth2AuthorizationResponseType.CODE.getValue());
assertThat(clientRegistration.getScope())
.isEqualTo("test read");
assertThat(clientRegistration.getTokenEndpointAuthenticationMethod())
.isEqualTo(ClientAuthenticationMethod.BASIC.getValue());
assertThat(clientRegistration.getClientId()).isEqualTo("client-id");
assertThat(clientRegistration.getClientIdIssuedAt()).isEqualTo(clientIdIssuedAt);
assertThat(clientRegistration.getClientSecret()).isEqualTo("client-secret");
assertThat(clientRegistration.getClientSecretExpiresAt()).isEqualTo(clientSecretExpiresAt);
assertThat(clientRegistration.getClientName()).isEqualTo("client-name");
assertThat(clientRegistration.getRedirectUris()).containsOnly("https://client.example.com");
assertThat(clientRegistration.getTokenEndpointAuthenticationMethod()).isEqualTo("basic");
assertThat(clientRegistration.getGrantTypes()).containsExactlyInAnyOrder("authorization_code", "client_credentials");
assertThat(clientRegistration.getResponseTypes()).containsOnly("code");
assertThat(clientRegistration.getScopes()).containsExactlyInAnyOrder("scope1", "scope2");
assertThat(clientRegistration.getIdTokenSignedResponseAlgorithm()).isEqualTo("RS256");
assertThat(clientRegistration.getClaimAsString("a-claim")).isEqualTo("a-value");
}
@Test
public void buildWhenAllRequiredClaimsThenCreated() {
OidcClientRegistration clientRegistration = OidcClientRegistration.builder()
.redirectUri("http://client.example.com")
.build();
assertThat(clientRegistration.getRedirectUris())
.containsOnly("http://client.example.com");
assertThat(clientRegistration.getGrantTypes())
.containsOnly(AuthorizationGrantType.AUTHORIZATION_CODE.getValue());
assertThat(clientRegistration.getResponseTypes())
.containsOnly(OAuth2AuthorizationResponseType.CODE.getValue());
assertThat(clientRegistration.getScope())
.isNull();
assertThat(clientRegistration.getTokenEndpointAuthenticationMethod())
.isEqualTo(ClientAuthenticationMethod.BASIC.getValue());
public void buildWhenOnlyRequiredClaimsProvidedThenCreated() {
OidcClientRegistration clientRegistration = this.minimalBuilder.build();
assertThat(clientRegistration.getRedirectUris()).containsOnly("https://client.example.com");
}
@Test
public void buildWhenAllRequiredClaimsAndAuthorizationGrantTypeButMissingResponseTypeThenCreated() {
OidcClientRegistration clientRegistration = OidcClientRegistration.builder()
.redirectUri("http://client.example.com")
.grantType(AuthorizationGrantType.AUTHORIZATION_CODE.getValue())
.build();
assertThat(clientRegistration.getRedirectUris())
.containsOnly("http://client.example.com");
assertThat(clientRegistration.getGrantTypes())
.containsOnly(AuthorizationGrantType.AUTHORIZATION_CODE.getValue());
assertThat(clientRegistration.getResponseTypes())
.containsOnly(OAuth2AuthorizationResponseType.CODE.getValue());
}
@Test
public void buildWhenAllRequiredClaimsAndEmptyGrantTypeListButMissingResponseTypeThenCreated() {
OidcClientRegistration clientRegistration = OidcClientRegistration.builder()
.redirectUri("http://client.example.com")
.grantTypes(List::clear)
.build();
assertThat(clientRegistration.getRedirectUris())
.containsOnly("http://client.example.com");
assertThat(clientRegistration.getGrantTypes())
.containsOnly(AuthorizationGrantType.AUTHORIZATION_CODE.getValue());
assertThat(clientRegistration.getResponseTypes())
.containsOnly(OAuth2AuthorizationResponseType.CODE.getValue());
}
@Test
public void buildWhenAllRequiredClaimsAndResponseTypeButMissingAuthorizationGrantTypeThenCreated() {
OidcClientRegistration clientRegistration = OidcClientRegistration.builder()
.redirectUri("http://client.example.com")
.responseType(OAuth2AuthorizationResponseType.CODE.getValue())
.build();
assertThat(clientRegistration.getRedirectUris())
.containsOnly("http://client.example.com");
assertThat(clientRegistration.getGrantTypes())
.containsOnly(AuthorizationGrantType.AUTHORIZATION_CODE.getValue());
assertThat(clientRegistration.getResponseTypes())
.containsOnly(OAuth2AuthorizationResponseType.CODE.getValue());
}
@Test
public void buildWhenAllRequiredClaimsAndEmptyResponseTypeListButMissingAuthorizationGrantTypeThenCreated() {
OidcClientRegistration clientRegistration = OidcClientRegistration.builder()
.redirectUri("http://client.example.com")
.responseTypes(List::clear)
.build();
assertThat(clientRegistration.getRedirectUris())
.containsOnly("http://client.example.com");
assertThat(clientRegistration.getGrantTypes())
.containsOnly(AuthorizationGrantType.AUTHORIZATION_CODE.getValue());
assertThat(clientRegistration.getResponseTypes())
.containsOnly(OAuth2AuthorizationResponseType.CODE.getValue());
}
@Test
public void buildWhenAllRequiredClaimsAndEmptyScopeThenCreated() {
OidcClientRegistration clientRegistration = OidcClientRegistration.builder()
.redirectUri("http://client.example.com")
.build();
assertThat(clientRegistration.getRedirectUris())
.containsOnly("http://client.example.com");
assertThat(clientRegistration.getScope())
.isNull();
}
@Test
public void buildWhenAllRequiredClaimsAndEmptyTokenEndpointAuthMethodThenCreated() {
OidcClientRegistration clientRegistration = OidcClientRegistration.builder()
.redirectUri("http://client.example.com")
.build();
assertThat(clientRegistration.getRedirectUris())
.containsOnly("http://client.example.com");
assertThat(clientRegistration.getTokenEndpointAuthenticationMethod())
.isEqualTo(ClientAuthenticationMethod.BASIC.getValue());
}
@Test
public void buildWhenClaimsProvidedThenCreated() {
Map<String, Object> claims = new HashMap<>();
claims.put(OidcClientMetadataClaimNames.REDIRECT_URIS, Collections.singletonList("http://client.example.com"));
claims.put(OidcClientMetadataClaimNames.GRANT_TYPES, Arrays.asList(
AuthorizationGrantType.AUTHORIZATION_CODE.getValue(),
AuthorizationGrantType.CLIENT_CREDENTIALS.getValue()
));
claims.put(OidcClientMetadataClaimNames.RESPONSE_TYPES,
Collections.singletonList(OAuth2AuthorizationResponseType.CODE.getValue()));
claims.put(OidcClientMetadataClaimNames.SCOPE, "test read");
public void withClaimsWhenClaimsProvidedThenCreated() {
Instant clientIdIssuedAt = Instant.now();
Instant clientSecretExpiresAt = clientIdIssuedAt.plus(30, ChronoUnit.DAYS);
HashMap<String, Object> claims = new HashMap<>();
claims.put(OidcClientMetadataClaimNames.CLIENT_ID, "client-id");
claims.put(OidcClientMetadataClaimNames.CLIENT_ID_ISSUED_AT, clientIdIssuedAt);
claims.put(OidcClientMetadataClaimNames.CLIENT_SECRET, "client-secret");
claims.put(OidcClientMetadataClaimNames.CLIENT_SECRET_EXPIRES_AT, clientSecretExpiresAt);
claims.put(OidcClientMetadataClaimNames.CLIENT_NAME, "client-name");
claims.put(OidcClientMetadataClaimNames.REDIRECT_URIS, Collections.singletonList("https://client.example.com"));
claims.put(OidcClientMetadataClaimNames.TOKEN_ENDPOINT_AUTH_METHOD, ClientAuthenticationMethod.BASIC.getValue());
claims.put(OidcClientMetadataClaimNames.GRANT_TYPES, Arrays.asList(
AuthorizationGrantType.AUTHORIZATION_CODE.getValue(), AuthorizationGrantType.CLIENT_CREDENTIALS.getValue()));
claims.put(OidcClientMetadataClaimNames.RESPONSE_TYPES, Collections.singletonList("code"));
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");
OidcClientRegistration clientRegistration = OidcClientRegistration.withClaims(claims).build();
assertThat(clientRegistration.getRedirectUris())
.containsOnly("http://client.example.com");
assertThat(clientRegistration.getGrantTypes())
.contains(
AuthorizationGrantType.AUTHORIZATION_CODE.getValue(),
AuthorizationGrantType.CLIENT_CREDENTIALS.getValue()
);
assertThat(clientRegistration.getResponseTypes())
.contains(OAuth2AuthorizationResponseType.CODE.getValue());
assertThat(clientRegistration.getScope())
.isEqualTo("test read");
assertThat(clientRegistration.getTokenEndpointAuthenticationMethod())
.isEqualTo(ClientAuthenticationMethod.BASIC.getValue());
assertThat(clientRegistration.getClientId()).isEqualTo("client-id");
assertThat(clientRegistration.getClientIdIssuedAt()).isEqualTo(clientIdIssuedAt);
assertThat(clientRegistration.getClientSecret()).isEqualTo("client-secret");
assertThat(clientRegistration.getClientSecretExpiresAt()).isEqualTo(clientSecretExpiresAt);
assertThat(clientRegistration.getClientName()).isEqualTo("client-name");
assertThat(clientRegistration.getRedirectUris()).containsOnly("https://client.example.com");
assertThat(clientRegistration.getTokenEndpointAuthenticationMethod()).isEqualTo("basic");
assertThat(clientRegistration.getGrantTypes()).containsExactlyInAnyOrder("authorization_code", "client_credentials");
assertThat(clientRegistration.getResponseTypes()).containsOnly("code");
assertThat(clientRegistration.getScopes()).containsExactlyInAnyOrder("scope1", "scope2");
assertThat(clientRegistration.getIdTokenSignedResponseAlgorithm()).isEqualTo("RS256");
assertThat(clientRegistration.getClaimAsString("a-claim")).isEqualTo("a-value");
}
@Test
public void buildWhenRedirectUriProvidedWithUrlThenCreated() {
Map<String, Object> claims = new HashMap<>();
claims.put(OidcClientMetadataClaimNames.REDIRECT_URIS, Arrays.asList(
url("http://client.example.com"),
url("http://client.example.com/authorized")
)
);
claims.put(OidcClientMetadataClaimNames.GRANT_TYPES, Arrays.asList(
AuthorizationGrantType.AUTHORIZATION_CODE.getValue(),
AuthorizationGrantType.CLIENT_CREDENTIALS.getValue()
));
claims.put(OidcClientMetadataClaimNames.RESPONSE_TYPES,
Collections.singletonList(OAuth2AuthorizationResponseType.CODE.getValue()));
claims.put(OidcClientMetadataClaimNames.SCOPE, "test read");
claims.put(OidcClientMetadataClaimNames.TOKEN_ENDPOINT_AUTH_METHOD, ClientAuthenticationMethod.BASIC.getValue());
OidcClientRegistration clientRegistration = OidcClientRegistration.withClaims(claims).build();
assertThat(clientRegistration.getRedirectUris())
.contains("http://client.example.com", "http://client.example.com/authorized");
assertThat(clientRegistration.getGrantTypes())
.contains(
AuthorizationGrantType.AUTHORIZATION_CODE.getValue(),
AuthorizationGrantType.CLIENT_CREDENTIALS.getValue()
);
assertThat(clientRegistration.getResponseTypes())
.contains(OAuth2AuthorizationResponseType.CODE.getValue());
assertThat(clientRegistration.getScope())
.isEqualTo("test read");
assertThat(clientRegistration.getTokenEndpointAuthenticationMethod())
.isEqualTo(ClientAuthenticationMethod.BASIC.getValue());
public void withClaimsWhenNullThenThrowIllegalArgumentException() {
assertThatIllegalArgumentException()
.isThrownBy(() -> OidcClientRegistration.withClaims(null))
.withMessage("claims cannot be empty");
}
@Test
public void withClaimsNullThenThrowIllegalArgumentException() {
assertThatThrownBy(() -> OidcClientRegistration.withClaims(null))
.isInstanceOf(IllegalArgumentException.class);
public void withClaimsWhenEmptyThenThrowIllegalArgumentException() {
assertThatIllegalArgumentException()
.isThrownBy(() -> OidcClientRegistration.withClaims(Collections.emptyMap()))
.withMessage("claims cannot be empty");
}
@Test
public void withClaimsEmptyThenThrowIllegalArgumentException() {
assertThatThrownBy(() -> OidcClientRegistration.withClaims(Collections.emptyMap()))
.isInstanceOf(IllegalArgumentException.class)
.hasMessage("claims cannot be empty");
public void buildWhenMissingClientIdThenThrowIllegalArgumentException() {
OidcClientRegistration.Builder builder = this.minimalBuilder
.clientIdIssuedAt(Instant.now());
assertThatIllegalArgumentException()
.isThrownBy(builder::build)
.withMessage("client_id cannot be null");
}
@Test
public void buildWhenNullRedirectUriThenThrowIllegalArgumentException() {
OidcClientRegistration.Builder builder = this.clientRegistrationBuilder
.redirectUris((claims) -> claims.remove(OidcClientMetadataClaimNames.REDIRECT_URIS));
public void buildWhenClientSecretAndMissingClientIdThenThrowIllegalArgumentException() {
OidcClientRegistration.Builder builder = this.minimalBuilder
.clientSecret("client-secret");
assertThatThrownBy(builder::build)
.isInstanceOf(IllegalArgumentException.class)
.hasMessage("redirect_uris must not be empty");
assertThatIllegalArgumentException()
.isThrownBy(builder::build)
.withMessage("client_id cannot be null");
}
@Test
public void buildWhenNullRedirectUriClaimThenThrowIllegalArgumentException() {
Map<String, Object> claims = new HashMap<>();
claims.put(OidcClientMetadataClaimNames.REDIRECT_URIS, null);
OidcClientRegistration.Builder builder = OidcClientRegistration.withClaims(claims);
public void buildWhenClientIdIssuedAtNotInstantThenThrowIllegalArgumentException() {
// @formatter:off
OidcClientRegistration.Builder builder = this.minimalBuilder
.clientId("client-id")
.claim(OidcClientMetadataClaimNames.CLIENT_ID_ISSUED_AT, "clientIdIssuedAt");
// @formatter:on
assertThatThrownBy(builder::build)
.isInstanceOf(IllegalArgumentException.class)
.hasMessage("redirect_uris cannot be null");
assertThatIllegalArgumentException()
.isThrownBy(builder::build)
.withMessageStartingWith("client_id_issued_at must be of type Instant");
}
@Test
public void buildWhenEmptyRedirectUriListThenThrowIllegalArgumentException() {
OidcClientRegistration.Builder builder = this.clientRegistrationBuilder
.redirectUris(List::clear);
public void buildWhenMissingClientSecretThenThrowIllegalArgumentException() {
// @formatter:off
OidcClientRegistration.Builder builder = this.minimalBuilder
.clientId("client-id")
.clientIdIssuedAt(Instant.now())
.clientSecretExpiresAt(Instant.now().plus(30, ChronoUnit.DAYS));
// @formatter:on
assertThatThrownBy(builder::build)
.isInstanceOf(IllegalArgumentException.class)
.hasMessage("redirect_uris must not be empty");
assertThatIllegalArgumentException()
.isThrownBy(builder::build)
.withMessage("client_secret cannot be null");
}
@Test
public void buildWhenRedirectUriNotOfTypeListThenThrowIllegalArgumentException() {
OidcClientRegistration.Builder builder = this.clientRegistrationBuilder
.claims(claims -> claims.put(OidcClientMetadataClaimNames.REDIRECT_URIS, "http://client.example.com"));
public void buildWhenClientSecretExpiresAtNotInstantThenThrowIllegalArgumentException() {
// @formatter:off
OidcClientRegistration.Builder builder = this.minimalBuilder
.clientId("client-id")
.clientIdIssuedAt(Instant.now())
.clientSecret("client-secret")
.claim(OidcClientMetadataClaimNames.CLIENT_SECRET_EXPIRES_AT, "clientSecretExpiresAt");
// @formatter:on
assertThatThrownBy(builder::build)
.isInstanceOf(IllegalArgumentException.class)
.hasMessageContaining("redirect_uris must be of type list");
assertThatIllegalArgumentException()
.isThrownBy(builder::build)
.withMessageStartingWith("client_secret_expires_at must be of type Instant");
}
@Test
public void buildWhenRedirectUriNotUrlThenThrowIllegalArgumentException() {
OidcClientRegistration.Builder builder = this.clientRegistrationBuilder
.redirectUri("not url");
public void buildWhenMissingRedirectUrisThenThrowIllegalArgumentException() {
OidcClientRegistration.Builder builder = OidcClientRegistration.builder()
.clientName("client-name");
assertThatThrownBy(builder::build)
.isInstanceOf(IllegalArgumentException.class)
.hasMessage("redirect_uri must be a valid URL");
assertThatIllegalArgumentException()
.isThrownBy(builder::build)
.withMessage("redirect_uris cannot be null");
}
@Test
public void buildWhenResponseTypesNotOfTypeListThenThrowIllegalArgumentException() {
OidcClientRegistration.Builder builder = this.clientRegistrationBuilder
.redirectUri("http://client.example.com")
.claims(claims -> claims.put(OidcClientMetadataClaimNames.RESPONSE_TYPES, OAuth2AuthorizationResponseType.CODE.getValue()));
public void buildWhenRedirectUrisNotListThenThrowIllegalArgumentException() {
OidcClientRegistration.Builder builder = OidcClientRegistration.builder()
.claim(OidcClientMetadataClaimNames.REDIRECT_URIS, "redirectUris");
assertThatThrownBy(builder::build)
.isInstanceOf(IllegalArgumentException.class)
.hasMessageContaining("response_types must be of type List");
assertThatIllegalArgumentException()
.isThrownBy(builder::build)
.withMessageStartingWith("redirect_uris must be of type List");
}
@Test
public void buildWhenGrantTypesNotOfTypeListThenThrowIllegalArgumentException() {
OidcClientRegistration.Builder builder = this.clientRegistrationBuilder
.redirectUri("http://client.example.com")
.claims(claims -> claims.put(OidcClientMetadataClaimNames.GRANT_TYPES, AuthorizationGrantType.AUTHORIZATION_CODE.getValue()));
public void buildWhenRedirectUrisEmptyListThenThrowIllegalArgumentException() {
OidcClientRegistration.Builder builder = OidcClientRegistration.builder()
.claim(OidcClientMetadataClaimNames.REDIRECT_URIS, Collections.emptyList());
assertThatThrownBy(builder::build)
.isInstanceOf(IllegalArgumentException.class)
.hasMessageContaining("grant_types must be of type List");
assertThatIllegalArgumentException()
.isThrownBy(builder::build)
.withMessage("redirect_uris cannot be empty");
}
private static URL url(String urlString) {
try {
return new URL(urlString);
} catch (Exception ex) {
throw new IllegalArgumentException("urlString must be a valid URL and valid URI");
}
@Test
public void buildWhenInvalidRedirectUriThenThrowIllegalArgumentException() {
OidcClientRegistration.Builder builder = OidcClientRegistration.builder()
.redirectUri("invalid-uri");
assertThatIllegalArgumentException()
.isThrownBy(builder::build)
.withMessage("redirect_uri must be a valid URL");
}
@Test
public void buildWhenRedirectUrisAddingOrRemovingThenCorrectValues() {
// @formatter:off
OidcClientRegistration clientRegistration = this.minimalBuilder
.redirectUri("https://client1.example.com")
.redirectUris(redirectUris -> {
redirectUris.clear();
redirectUris.add("https://client2.example.com");
})
.build();
// @formatter:on
assertThat(clientRegistration.getRedirectUris()).containsExactly("https://client2.example.com");
}
@Test
public void buildWhenGrantTypesNotListThenThrowIllegalArgumentException() {
OidcClientRegistration.Builder builder = this.minimalBuilder
.claim(OidcClientMetadataClaimNames.GRANT_TYPES, "grantTypes");
assertThatIllegalArgumentException()
.isThrownBy(builder::build)
.withMessageStartingWith("grant_types must be of type List");
}
@Test
public void buildWhenGrantTypesEmptyListThenThrowIllegalArgumentException() {
OidcClientRegistration.Builder builder = this.minimalBuilder
.claim(OidcClientMetadataClaimNames.GRANT_TYPES, Collections.emptyList());
assertThatIllegalArgumentException()
.isThrownBy(builder::build)
.withMessage("grant_types cannot be empty");
}
@Test
public void buildWhenGrantTypesAddingOrRemovingThenCorrectValues() {
// @formatter:off
OidcClientRegistration clientRegistration = this.minimalBuilder
.grantType("authorization_code")
.grantTypes(grantTypes -> {
grantTypes.clear();
grantTypes.add("client_credentials");
})
.build();
// @formatter:on
assertThat(clientRegistration.getGrantTypes()).containsExactly("client_credentials");
}
@Test
public void buildWhenResponseTypesNotListThenThrowIllegalArgumentException() {
OidcClientRegistration.Builder builder = this.minimalBuilder
.claim(OidcClientMetadataClaimNames.RESPONSE_TYPES, "responseTypes");
assertThatIllegalArgumentException()
.isThrownBy(builder::build)
.withMessageStartingWith("response_types must be of type List");
}
@Test
public void buildWhenResponseTypesEmptyListThenThrowIllegalArgumentException() {
OidcClientRegistration.Builder builder = this.minimalBuilder
.claim(OidcClientMetadataClaimNames.RESPONSE_TYPES, Collections.emptyList());
assertThatIllegalArgumentException()
.isThrownBy(builder::build)
.withMessage("response_types cannot be empty");
}
@Test
public void buildWhenResponseTypesAddingOrRemovingThenCorrectValues() {
// @formatter:off
OidcClientRegistration clientRegistration = this.minimalBuilder
.responseType("token")
.responseTypes(responseTypes -> {
responseTypes.clear();
responseTypes.add("code");
})
.build();
// @formatter:on
assertThat(clientRegistration.getResponseTypes()).containsExactly("code");
}
@Test
public void buildWhenScopesNotListThenThrowIllegalArgumentException() {
OidcClientRegistration.Builder builder = this.minimalBuilder
.claim(OidcClientMetadataClaimNames.SCOPE, "scopes");
assertThatIllegalArgumentException()
.isThrownBy(builder::build)
.withMessageStartingWith("scope must be of type List");
}
@Test
public void buildWhenScopesEmptyListThenThrowIllegalArgumentException() {
OidcClientRegistration.Builder builder = this.minimalBuilder
.claim(OidcClientMetadataClaimNames.SCOPE, Collections.emptyList());
assertThatIllegalArgumentException()
.isThrownBy(builder::build)
.withMessage("scope cannot be empty");
}
@Test
public void buildWhenScopesAddingOrRemovingThenCorrectValues() {
// @formatter:off
OidcClientRegistration clientRegistration = this.minimalBuilder
.scope("should-be-removed")
.scopes(scopes -> {
scopes.clear();
scopes.add("scope1");
})
.build();
// @formatter:on
assertThat(clientRegistration.getScopes()).containsExactly("scope1");
}
@Test
public void claimWhenNameNullThenThrowIllegalArgumentException() {
assertThatIllegalArgumentException()
.isThrownBy(() -> OidcClientRegistration.builder().claim(null, "claim-value"))
.withMessage("name cannot be empty");
}
@Test
public void claimWhenValueNullThenThrowIllegalArgumentException() {
assertThatIllegalArgumentException()
.isThrownBy(() -> OidcClientRegistration.builder().claim("claim-name", null))
.withMessage("value cannot be null");
}
@Test
public void claimsWhenRemovingClaimThenNotPresent() {
// @formatter:off
OidcClientRegistration clientRegistration = this.minimalBuilder
.claim("claim-name", "claim-value")
.claims((claims) -> claims.remove("claim-name"))
.build();
// @formatter:on
assertThat(clientRegistration.containsClaim("claim-name")).isFalse();
}
@Test
public void claimsWhenAddingClaimThenPresent() {
// @formatter:off
OidcClientRegistration clientRegistration = this.minimalBuilder
.claim("claim-name", "claim-value")
.build();
// @formatter:on
assertThat(clientRegistration.containsClaim("claim-name")).isTrue();
}
}

View File

@@ -1,197 +0,0 @@
/*
* Copyright 2020-2021 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* https://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.springframework.security.oauth2.core.oidc.http.converter;
import org.junit.Test;
import org.springframework.core.convert.converter.Converter;
import org.springframework.http.HttpStatus;
import org.springframework.http.converter.HttpMessageNotReadableException;
import org.springframework.http.converter.HttpMessageNotWritableException;
import org.springframework.mock.http.MockHttpOutputMessage;
import org.springframework.mock.http.client.MockClientHttpResponse;
import org.springframework.security.oauth2.core.AuthorizationGrantType;
import org.springframework.security.oauth2.core.ClientAuthenticationMethod;
import org.springframework.security.oauth2.core.endpoint.OAuth2AuthorizationResponseType;
import org.springframework.security.oauth2.core.oidc.OidcClientRegistration;
import java.util.Map;
import static org.assertj.core.api.Assertions.assertThat;
import static org.assertj.core.api.Assertions.assertThatExceptionOfType;
import static org.assertj.core.api.Assertions.assertThatIllegalArgumentException;
import static org.assertj.core.api.Assertions.assertThatThrownBy;
/**
* @author Ovidiu Popa
* @since 0.1.1
*/
public class OidcClientRegistrationHttpMessageConverterTest {
private final OidcClientRegistrationHttpMessageConverter messageConverter =
new OidcClientRegistrationHttpMessageConverter();
@Test
public void supportsWhenOidcClientRegistrationThenTrue() {
assertThat(this.messageConverter.supports(OidcClientRegistration.class)).isTrue();
}
@Test
public void setClientRegistrationReadConverterWhenNullThenThrowIllegalArgumentException() {
assertThatIllegalArgumentException()
.isThrownBy(() -> this.messageConverter.setClientRegistrationConverter(null))
.withMessageContaining("clientRegistrationConverter cannot be null");
}
@Test
public void setClientRegistrationWriteConverterWhenNullThenThrowIllegalArgumentException() {
assertThatIllegalArgumentException()
.isThrownBy(() -> this.messageConverter.setClientRegistrationParametersConverter(null))
.withMessageContaining("clientRegistrationParametersConverter cannot be null");
}
@Test
public void readInternalWhenRequiredParametersThenSuccess() {
// @formatter:off
String clientRegistrationResponse = "{\n"
+ " \"redirect_uris\": [\n"
+ " \"https://client.example.org/callback\"\n"
+ " ]\n"
+ "}\n";
// @formatter:on
MockClientHttpResponse response = new MockClientHttpResponse(clientRegistrationResponse.getBytes(), HttpStatus.OK);
OidcClientRegistration clientRegistration = this.messageConverter
.readInternal(OidcClientRegistration.class, response);
assertThat(clientRegistration.getRedirectUris())
.containsOnly("https://client.example.org/callback");
assertThat(clientRegistration.getGrantTypes())
.containsOnly(
AuthorizationGrantType.AUTHORIZATION_CODE.getValue()
);
assertThat(clientRegistration.getResponseTypes())
.contains(OAuth2AuthorizationResponseType.CODE.getValue());
assertThat(clientRegistration.getScope())
.isNull();
assertThat(clientRegistration.getTokenEndpointAuthenticationMethod())
.isEqualTo(ClientAuthenticationMethod.BASIC.getValue());
}
@Test
public void readInternalWhenValidParametersThenSuccess() {
// @formatter:off
String clientRegistrationResponse = "{\n"
+" \"redirect_uris\": [\n"
+ " \"https://client.example.org/callback\"\n"
+ " ],\n"
+" \"grant_types\": [\n"
+" \"client_credentials\",\n"
+" \"authorization_code\"\n"
+" ],\n"
+" \"response_types\":[\n"
+" \"code\"\n"
+" ],\n"
+" \"client_name\": \"My Example\",\n"
+" \"scope\": \"read write\",\n"
+" \"token_endpoint_auth_method\": \"basic\"\n"
+"}\n";
// @formatter:on
MockClientHttpResponse response = new MockClientHttpResponse(clientRegistrationResponse.getBytes(), HttpStatus.OK);
OidcClientRegistration clientRegistration = this.messageConverter
.readInternal(OidcClientRegistration.class, response);
assertThat(clientRegistration.getRedirectUris())
.containsOnly("https://client.example.org/callback");
assertThat(clientRegistration.getGrantTypes())
.contains(
AuthorizationGrantType.AUTHORIZATION_CODE.getValue(),
AuthorizationGrantType.CLIENT_CREDENTIALS.getValue()
);
assertThat(clientRegistration.getResponseTypes())
.contains(OAuth2AuthorizationResponseType.CODE.getValue());
assertThat(clientRegistration.getScope())
.isEqualTo("read write");
assertThat(clientRegistration.getTokenEndpointAuthenticationMethod())
.isEqualTo(ClientAuthenticationMethod.BASIC.getValue());
}
@Test
public void readInternalWhenFailingConverterThenThrowException() {
String errorMessage = "this is not a valid converter";
this.messageConverter.setClientRegistrationConverter(source -> {
throw new RuntimeException(errorMessage);
});
MockClientHttpResponse response = new MockClientHttpResponse("{}".getBytes(), HttpStatus.OK);
assertThatExceptionOfType(HttpMessageNotReadableException.class)
.isThrownBy(() -> this.messageConverter.readInternal(OidcClientRegistration.class, response))
.withMessageContaining("An error occurred reading the OpenID Client Registration Request")
.withMessageContaining(errorMessage);
}
@Test
public void readInternalWhenInvalidClientRegistrationThenThrowException() {
String clientRegistrationResponse = "{ \"redirect_uris\": null }";
MockClientHttpResponse response = new MockClientHttpResponse(clientRegistrationResponse.getBytes(), HttpStatus.OK);
assertThatExceptionOfType(HttpMessageNotReadableException.class)
.isThrownBy(() -> this.messageConverter.readInternal(OidcClientRegistration.class, response))
.withMessageContaining("An error occurred reading the OpenID Client Registration Request")
.withMessageContaining("redirect_uris cannot be null");
}
@Test
public void writeInternalWhenClientRegistrationThenSuccess() {
OidcClientRegistration clientRegistration = OidcClientRegistration.builder()
.redirectUri("http://client.example.com/callback")
.grantType(AuthorizationGrantType.AUTHORIZATION_CODE.getValue())
.grantType(AuthorizationGrantType.CLIENT_CREDENTIALS.getValue())
.responseType(OAuth2AuthorizationResponseType.CODE.getValue())
.scope("test read")
.tokenEndpointAuthenticationMethod(ClientAuthenticationMethod.BASIC.getValue())
.build();
MockHttpOutputMessage outputMessage = new MockHttpOutputMessage();
this.messageConverter.writeInternal(clientRegistration, outputMessage);
String clientRegistrationResponse = outputMessage.getBodyAsString();
assertThat(clientRegistrationResponse).contains("\"redirect_uris\":[\"http://client.example.com/callback\"]");
assertThat(clientRegistrationResponse).contains("\"grant_types\":[\"authorization_code\",\"client_credentials\"]");
assertThat(clientRegistrationResponse).contains("\"response_types\":[\"code\"]");
assertThat(clientRegistrationResponse).contains("\"scope\":\"test read\"");
assertThat(clientRegistrationResponse).contains("\"token_endpoint_auth_method\":\"basic\"");
}
@Test
public void writeInternalWhenWriteFailsThenThrowsException() {
String errorMessage = "this is not a valid converter";
Converter<OidcClientRegistration, Map<String, Object>> failingConverter =
source -> {
throw new RuntimeException(errorMessage);
};
this.messageConverter.setClientRegistrationParametersConverter(failingConverter);
OidcClientRegistration clientRegistration =
OidcClientRegistration.builder()
.redirectUri("http://client.example.com")
.build();
MockHttpOutputMessage outputMessage = new MockHttpOutputMessage();
assertThatThrownBy(() -> this.messageConverter.writeInternal(clientRegistration, outputMessage))
.isInstanceOf(HttpMessageNotWritableException.class)
.hasMessageContaining("An error occurred writing the OpenID Client Registration response")
.hasMessageContaining(errorMessage);
}
}

View File

@@ -0,0 +1,250 @@
/*
* Copyright 2020-2021 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* https://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.springframework.security.oauth2.core.oidc.http.converter;
import java.time.Instant;
import java.util.Map;
import org.junit.Test;
import org.springframework.core.convert.converter.Converter;
import org.springframework.http.HttpStatus;
import org.springframework.http.converter.HttpMessageNotReadableException;
import org.springframework.http.converter.HttpMessageNotWritableException;
import org.springframework.mock.http.MockHttpOutputMessage;
import org.springframework.mock.http.client.MockClientHttpResponse;
import org.springframework.security.oauth2.core.AuthorizationGrantType;
import org.springframework.security.oauth2.core.ClientAuthenticationMethod;
import org.springframework.security.oauth2.core.endpoint.OAuth2AuthorizationResponseType;
import org.springframework.security.oauth2.core.oidc.OidcClientRegistration;
import org.springframework.security.oauth2.jose.jws.SignatureAlgorithm;
import static org.assertj.core.api.Assertions.assertThat;
import static org.assertj.core.api.Assertions.assertThatExceptionOfType;
import static org.assertj.core.api.Assertions.assertThatIllegalArgumentException;
import static org.assertj.core.api.Assertions.assertThatThrownBy;
/**
* Tests for {@link OidcClientRegistrationHttpMessageConverter}
* @author Ovidiu Popa
* @author Joe Grandja
* @since 0.1.1
*/
public class OidcClientRegistrationHttpMessageConverterTests {
private final OidcClientRegistrationHttpMessageConverter messageConverter = new OidcClientRegistrationHttpMessageConverter();
@Test
public void supportsWhenOidcClientRegistrationThenTrue() {
assertThat(this.messageConverter.supports(OidcClientRegistration.class)).isTrue();
}
@Test
public void setClientRegistrationConverterWhenNullThenThrowIllegalArgumentException() {
assertThatIllegalArgumentException()
.isThrownBy(() -> this.messageConverter.setClientRegistrationConverter(null))
.withMessageContaining("clientRegistrationConverter cannot be null");
}
@Test
public void setClientRegistrationParametersConverterWhenNullThenThrowIllegalArgumentException() {
assertThatIllegalArgumentException()
.isThrownBy(() -> this.messageConverter.setClientRegistrationParametersConverter(null))
.withMessageContaining("clientRegistrationParametersConverter cannot be null");
}
@Test
public void readInternalWhenRequiredParametersThenSuccess() {
// @formatter:off
String clientRegistrationRequest = "{\n"
+ " \"redirect_uris\": [\n"
+ " \"https://client.example.com\"\n"
+ " ]\n"
+ "}\n";
// @formatter:on
MockClientHttpResponse response = new MockClientHttpResponse(
clientRegistrationRequest.getBytes(), HttpStatus.OK);
OidcClientRegistration clientRegistration = this.messageConverter
.readInternal(OidcClientRegistration.class, response);
assertThat(clientRegistration.getClaims()).hasSize(1);
assertThat(clientRegistration.getRedirectUris()).containsOnly("https://client.example.com");
}
@Test
public void readInternalWhenValidParametersThenSuccess() {
// @formatter:off
String clientRegistrationRequest = "{\n"
+" \"client_id\": \"client-id\",\n"
+" \"client_id_issued_at\": 1607633867,\n"
+" \"client_secret\": \"client-secret\",\n"
+" \"client_secret_expires_at\": 1607637467,\n"
+" \"client_name\": \"client-name\",\n"
+" \"redirect_uris\": [\n"
+ " \"https://client.example.com\"\n"
+ " ],\n"
+" \"token_endpoint_auth_method\": \"basic\",\n"
+" \"grant_types\": [\n"
+" \"authorization_code\",\n"
+" \"client_credentials\"\n"
+" ],\n"
+" \"response_types\":[\n"
+" \"code\"\n"
+" ],\n"
+" \"scope\": \"scope1 scope2\",\n"
+" \"id_token_signed_response_alg\": \"RS256\",\n"
+" \"a-claim\": \"a-value\"\n"
+"}\n";
// @formatter:on
MockClientHttpResponse response = new MockClientHttpResponse(
clientRegistrationRequest.getBytes(), HttpStatus.OK);
OidcClientRegistration clientRegistration = this.messageConverter
.readInternal(OidcClientRegistration.class, response);
assertThat(clientRegistration.getClientId()).isEqualTo("client-id");
assertThat(clientRegistration.getClientIdIssuedAt()).isEqualTo(Instant.ofEpochSecond(1607633867L));
assertThat(clientRegistration.getClientSecret()).isEqualTo("client-secret");
assertThat(clientRegistration.getClientSecretExpiresAt()).isEqualTo(Instant.ofEpochSecond(1607637467L));
assertThat(clientRegistration.getClientName()).isEqualTo("client-name");
assertThat(clientRegistration.getRedirectUris()).containsOnly("https://client.example.com");
assertThat(clientRegistration.getTokenEndpointAuthenticationMethod()).isEqualTo("basic");
assertThat(clientRegistration.getGrantTypes()).containsExactlyInAnyOrder("authorization_code", "client_credentials");
assertThat(clientRegistration.getResponseTypes()).containsOnly("code");
assertThat(clientRegistration.getScopes()).containsExactlyInAnyOrder("scope1", "scope2");
assertThat(clientRegistration.getIdTokenSignedResponseAlgorithm()).isEqualTo("RS256");
assertThat(clientRegistration.getClaimAsString("a-claim")).isEqualTo("a-value");
}
@Test
public void readInternalWhenClientSecretNoExpiryThenSuccess() {
// @formatter:off
String clientRegistrationRequest = "{\n"
+" \"client_id\": \"client-id\",\n"
+" \"client_secret\": \"client-secret\",\n"
+" \"client_secret_expires_at\": 0,\n"
+" \"redirect_uris\": [\n"
+ " \"https://client.example.com\"\n"
+ " ]\n"
+"}\n";
// @formatter:on
MockClientHttpResponse response = new MockClientHttpResponse(
clientRegistrationRequest.getBytes(), HttpStatus.OK);
OidcClientRegistration clientRegistration = this.messageConverter
.readInternal(OidcClientRegistration.class, response);
assertThat(clientRegistration.getClaims()).hasSize(3);
assertThat(clientRegistration.getClientId()).isEqualTo("client-id");
assertThat(clientRegistration.getClientSecret()).isEqualTo("client-secret");
assertThat(clientRegistration.getClientSecretExpiresAt()).isNull();
assertThat(clientRegistration.getRedirectUris()).containsOnly("https://client.example.com");
}
@Test
public void readInternalWhenFailingConverterThenThrowException() {
String errorMessage = "this is not a valid converter";
this.messageConverter.setClientRegistrationConverter(source -> {
throw new RuntimeException(errorMessage);
});
MockClientHttpResponse response = new MockClientHttpResponse("{}".getBytes(), HttpStatus.OK);
assertThatExceptionOfType(HttpMessageNotReadableException.class)
.isThrownBy(() -> this.messageConverter.readInternal(OidcClientRegistration.class, response))
.withMessageContaining("An error occurred reading the OpenID Client Registration")
.withMessageContaining(errorMessage);
}
@Test
public void writeInternalWhenClientRegistrationThenSuccess() {
// @formatter:off
OidcClientRegistration clientRegistration = OidcClientRegistration.builder()
.clientId("client-id")
.clientIdIssuedAt(Instant.ofEpochSecond(1607633867))
.clientSecret("client-secret")
.clientSecretExpiresAt(Instant.ofEpochSecond(1607637467))
.clientName("client-name")
.redirectUri("https://client.example.com")
.tokenEndpointAuthenticationMethod(ClientAuthenticationMethod.BASIC.getValue())
.grantType(AuthorizationGrantType.AUTHORIZATION_CODE.getValue())
.grantType(AuthorizationGrantType.CLIENT_CREDENTIALS.getValue())
.responseType(OAuth2AuthorizationResponseType.CODE.getValue())
.scope("scope1")
.scope("scope2")
.idTokenSignedResponseAlgorithm(SignatureAlgorithm.RS256.getName())
.claim("a-claim", "a-value")
.build();
// @formatter:on
MockHttpOutputMessage outputMessage = new MockHttpOutputMessage();
this.messageConverter.writeInternal(clientRegistration, outputMessage);
String clientRegistrationResponse = outputMessage.getBodyAsString();
assertThat(clientRegistrationResponse).contains("\"client_id\":\"client-id\"");
assertThat(clientRegistrationResponse).contains("\"client_id_issued_at\":1607633867");
assertThat(clientRegistrationResponse).contains("\"client_secret\":\"client-secret\"");
assertThat(clientRegistrationResponse).contains("\"client_secret_expires_at\":1607637467");
assertThat(clientRegistrationResponse).contains("\"client_name\":\"client-name\"");
assertThat(clientRegistrationResponse).contains("\"redirect_uris\":[\"https://client.example.com\"]");
assertThat(clientRegistrationResponse).contains("\"token_endpoint_auth_method\":\"basic\"");
assertThat(clientRegistrationResponse).contains("\"grant_types\":[\"authorization_code\",\"client_credentials\"]");
assertThat(clientRegistrationResponse).contains("\"response_types\":[\"code\"]");
assertThat(clientRegistrationResponse).contains("\"scope\":\"scope1 scope2\"");
assertThat(clientRegistrationResponse).contains("\"id_token_signed_response_alg\":\"RS256\"");
assertThat(clientRegistrationResponse).contains("\"a-claim\":\"a-value\"");
}
@Test
public void writeInternalWhenClientSecretNoExpiryThenSuccess() {
// @formatter:off
OidcClientRegistration clientRegistration = OidcClientRegistration.builder()
.clientId("client-id")
.clientSecret("client-secret")
.redirectUri("https://client.example.com")
.build();
// @formatter:on
MockHttpOutputMessage outputMessage = new MockHttpOutputMessage();
this.messageConverter.writeInternal(clientRegistration, outputMessage);
String clientRegistrationResponse = outputMessage.getBodyAsString();
assertThat(clientRegistrationResponse).contains("\"client_id\":\"client-id\"");
assertThat(clientRegistrationResponse).contains("\"client_secret\":\"client-secret\"");
assertThat(clientRegistrationResponse).contains("\"client_secret_expires_at\":0");
assertThat(clientRegistrationResponse).contains("\"redirect_uris\":[\"https://client.example.com\"]");
}
@Test
public void writeInternalWhenWriteFailsThenThrowException() {
String errorMessage = "this is not a valid converter";
Converter<OidcClientRegistration, Map<String, Object>> failingConverter = source -> {
throw new RuntimeException(errorMessage);
};
this.messageConverter.setClientRegistrationParametersConverter(failingConverter);
// @formatter:off
OidcClientRegistration clientRegistration = OidcClientRegistration.builder()
.redirectUri("https://client.example.com")
.build();
// @formatter:off
MockHttpOutputMessage outputMessage = new MockHttpOutputMessage();
assertThatThrownBy(() -> this.messageConverter.writeInternal(clientRegistration, outputMessage))
.isInstanceOf(HttpMessageNotWritableException.class)
.hasMessageContaining("An error occurred writing the OpenID Client Registration")
.hasMessageContaining(errorMessage);
}
}

View File

@@ -1,173 +0,0 @@
/*
* Copyright 2020-2021 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* https://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.springframework.security.oauth2.server.authorization.authentication;
import org.junit.Before;
import org.junit.Test;
import org.mockito.ArgumentCaptor;
import org.springframework.security.core.GrantedAuthority;
import org.springframework.security.core.authority.AuthorityUtils;
import org.springframework.security.oauth2.core.OAuth2AccessToken;
import org.springframework.security.oauth2.core.OAuth2AuthenticationException;
import org.springframework.security.oauth2.core.OAuth2ErrorCodes;
import org.springframework.security.oauth2.core.OAuth2TokenType;
import org.springframework.security.oauth2.jwt.Jwt;
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.resource.authentication.JwtAuthenticationToken;
import java.time.Instant;
import java.util.Collections;
import java.util.HashSet;
import java.util.List;
import static org.assertj.core.api.Assertions.assertThat;
import static org.assertj.core.api.Assertions.assertThatThrownBy;
import static org.mockito.ArgumentMatchers.eq;
import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.verify;
import static org.mockito.Mockito.when;
/**
* @author Ovidiu Popa
* @since 0.1.1
*/
public class OidcClientRegistrationAuthenticationProviderTests {
private OAuth2AuthorizationService authorizationService;
private OidcClientRegistrationAuthenticationProvider authenticationProvider;
@Before
public void setUp() {
this.authorizationService = mock(OAuth2AuthorizationService.class);
this.authenticationProvider = new OidcClientRegistrationAuthenticationProvider(this.authorizationService);
}
@Test
public void constructorWhenAuthorizationServiceNullThenThrowIllegalArgumentException() {
assertThatThrownBy(() -> new OidcClientRegistrationAuthenticationProvider(null))
.isInstanceOf(IllegalArgumentException.class)
.hasMessage("authorizationService cannot be null");
}
@Test
public void supportsWhenTypeJwtAuthenticationTokenThenReturnTrue() {
assertThat(this.authenticationProvider.supports(JwtAuthenticationToken.class)).isTrue();
}
@Test
public void authenticateWhenAccessTokenNotFoundThenThrowOAuth2AuthenticationException() {
JwtAuthenticationToken authentication = buildJwtAuthenticationToken("client-registration-token", "SCOPE_client.create");
when(authorizationService.findByToken(
eq("client-registration-token"), eq(OAuth2TokenType.ACCESS_TOKEN)))
.thenReturn(null);
assertThatThrownBy(() -> this.authenticationProvider.authenticate(authentication))
.isInstanceOf(OAuth2AuthenticationException.class)
.extracting(ex -> ((OAuth2AuthenticationException) ex).getError())
.extracting("errorCode")
.isEqualTo(OAuth2ErrorCodes.INVALID_GRANT);
}
@Test
public void authenticateWhenAccessTokenInvalidatedThenThrowOAuth2AuthenticationException() {
JwtAuthenticationToken authentication = buildJwtAuthenticationToken("client-registration-token", "SCOPE_client.create");
OAuth2AccessToken accessToken = new OAuth2AccessToken(OAuth2AccessToken.TokenType.BEARER,
"client-registration-token", Instant.now().minusSeconds(120), Instant.now().plusSeconds(1000));
OAuth2Authorization authorization = TestOAuth2Authorizations.authorization()
.token(accessToken, (metadata) -> metadata.put(OAuth2Authorization.Token.INVALIDATED_METADATA_NAME, true))
.build();
when(authorizationService.findByToken(
eq("client-registration-token"), eq(OAuth2TokenType.ACCESS_TOKEN)))
.thenReturn(authorization);
assertThatThrownBy(() -> this.authenticationProvider.authenticate(authentication))
.isInstanceOf(OAuth2AuthenticationException.class)
.extracting(ex -> ((OAuth2AuthenticationException) ex).getError())
.extracting("errorCode")
.isEqualTo(OAuth2ErrorCodes.INVALID_GRANT);
}
@Test
public void authenticateWhenAccessTokenWithoutClientCreateScopeThenThrowOAuth2AuthenticationException() {
JwtAuthenticationToken authentication = buildJwtAuthenticationToken("client-registration-token", "SCOPE_scope1");
OAuth2AccessToken accessToken = new OAuth2AccessToken(OAuth2AccessToken.TokenType.BEARER,
"client-registration-token", Instant.now().minusSeconds(120), Instant.now().plusSeconds(1000),
new HashSet<>(Collections.singletonList("scope1")));
OAuth2Authorization authorization = TestOAuth2Authorizations.authorization()
.token(accessToken)
.build();
when(authorizationService.findByToken(
eq("client-registration-token"), eq(OAuth2TokenType.ACCESS_TOKEN)))
.thenReturn(authorization);
assertThatThrownBy(() -> this.authenticationProvider.authenticate(authentication))
.isInstanceOf(OAuth2AuthenticationException.class)
.extracting(ex -> ((OAuth2AuthenticationException) ex).getError())
.extracting("errorCode")
.isEqualTo(OAuth2ErrorCodes.INVALID_GRANT);
}
@Test
public void authenticateWhenValidAccessTokenThenInvalidated() {
JwtAuthenticationToken authentication = buildJwtAuthenticationToken("client-registration-token", "SCOPE_client.create");
OAuth2AccessToken accessToken = new OAuth2AccessToken(OAuth2AccessToken.TokenType.BEARER,
"client-registration-token", Instant.now().minusSeconds(120), Instant.now().plusSeconds(1000),
new HashSet<>(Collections.singletonList("client.create")));
OAuth2Authorization authorization = TestOAuth2Authorizations.authorization()
.token(accessToken)
.build();
when(authorizationService.findByToken(
eq("client-registration-token"), eq(OAuth2TokenType.ACCESS_TOKEN)))
.thenReturn(authorization);
authenticationProvider.authenticate(authentication);
ArgumentCaptor<OAuth2Authorization> authorizationCaptor = ArgumentCaptor.forClass(OAuth2Authorization.class);
verify(authorizationService).save(authorizationCaptor.capture());
OAuth2Authorization capturedAuthorization = authorizationCaptor.getValue();
assertThat(capturedAuthorization.getAccessToken()).isNotNull();
assertThat(capturedAuthorization.getAccessToken().isInvalidated()).isTrue();
}
private static JwtAuthenticationToken buildJwtAuthenticationToken(String tokenValue, String... authorities) {
Jwt jwt = Jwt.withTokenValue(tokenValue)
.header("alg", "none")
.claim("sub", "client")
.build();
List<GrantedAuthority> grantedAuthorities = AuthorityUtils.createAuthorityList(authorities);
JwtAuthenticationToken jwtAuthenticationToken = new JwtAuthenticationToken(jwt, grantedAuthorities);
jwtAuthenticationToken.setAuthenticated(true);
return jwtAuthenticationToken;
}
}

View File

@@ -1,5 +1,5 @@
/*
* Copyright 2020 the original author or authors.
* Copyright 2020-2021 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -15,21 +15,24 @@
*/
package org.springframework.security.oauth2.server.authorization.client;
import org.junit.Test;
import org.springframework.security.oauth2.core.AuthorizationGrantType;
import org.springframework.security.oauth2.core.ClientAuthenticationMethod;
import java.util.Arrays;
import java.util.Collections;
import java.util.List;
import org.junit.Test;
import org.springframework.security.oauth2.core.AuthorizationGrantType;
import org.springframework.security.oauth2.core.ClientAuthenticationMethod;
import static org.assertj.core.api.Assertions.assertThat;
import static org.assertj.core.api.Assertions.assertThatThrownBy;
import static org.assertj.core.api.Assertions.assertThatIllegalArgumentException;
/**
* Tests for {@link InMemoryRegisteredClientRepository}.
*
* @author Anoop Garlapati
* @author Ovidiu Popa
* @author Joe Grandja
*/
public class InMemoryRegisteredClientRepositoryTests {
private RegisteredClient registration = TestRegisteredClients.registeredClient().build();
@@ -38,47 +41,70 @@ public class InMemoryRegisteredClientRepositoryTests {
@Test
public void constructorVarargsRegisteredClientWhenNullThenThrowIllegalArgumentException() {
assertThatThrownBy(() -> {
RegisteredClient registration = null;
new InMemoryRegisteredClientRepository(registration);
}).isInstanceOf(IllegalArgumentException.class);
assertThatIllegalArgumentException()
.isThrownBy(() -> {
RegisteredClient registration = null;
new InMemoryRegisteredClientRepository(registration);
})
.withMessageContaining("registration cannot be null");
}
@Test
public void constructorListRegisteredClientWhenNullThenThrowIllegalArgumentException() {
assertThatThrownBy(() -> {
List<RegisteredClient> registrations = null;
new InMemoryRegisteredClientRepository(registrations);
}).isInstanceOf(IllegalArgumentException.class);
assertThatIllegalArgumentException()
.isThrownBy(() -> {
List<RegisteredClient> registrations = null;
new InMemoryRegisteredClientRepository(registrations);
})
.withMessageContaining("registrations cannot be empty");
}
@Test
public void constructorListRegisteredClientWhenEmptyThenThrowIllegalArgumentException() {
assertThatThrownBy(() -> {
List<RegisteredClient> registrations = Collections.emptyList();
new InMemoryRegisteredClientRepository(registrations);
}).isInstanceOf(IllegalArgumentException.class);
assertThatIllegalArgumentException()
.isThrownBy(() -> {
List<RegisteredClient> registrations = Collections.emptyList();
new InMemoryRegisteredClientRepository(registrations);
})
.withMessageContaining("registrations cannot be empty");
}
@Test
public void constructorListRegisteredClientWhenDuplicateIdThenThrowIllegalArgumentException() {
assertThatThrownBy(() -> {
RegisteredClient anotherRegistrationWithSameId = TestRegisteredClients.registeredClient2()
.id(this.registration.getId()).build();
List<RegisteredClient> registrations = Arrays.asList(this.registration, anotherRegistrationWithSameId);
new InMemoryRegisteredClientRepository(registrations);
}).isInstanceOf(IllegalArgumentException.class);
assertThatIllegalArgumentException()
.isThrownBy(() -> {
RegisteredClient anotherRegistrationWithSameId = TestRegisteredClients.registeredClient2()
.id(this.registration.getId()).build();
List<RegisteredClient> registrations = Arrays.asList(this.registration, anotherRegistrationWithSameId);
new InMemoryRegisteredClientRepository(registrations);
})
.withMessageStartingWith("Registered client must be unique. Found duplicate identifier:");
}
@Test
public void constructorListRegisteredClientWhenDuplicateClientIdThenThrowIllegalArgumentException() {
assertThatThrownBy(() -> {
RegisteredClient anotherRegistrationWithSameClientId = TestRegisteredClients.registeredClient2()
.clientId(this.registration.getClientId()).build();
List<RegisteredClient> registrations = Arrays.asList(this.registration,
anotherRegistrationWithSameClientId);
new InMemoryRegisteredClientRepository(registrations);
}).isInstanceOf(IllegalArgumentException.class);
assertThatIllegalArgumentException()
.isThrownBy(() -> {
RegisteredClient anotherRegistrationWithSameClientId = TestRegisteredClients.registeredClient2()
.clientId(this.registration.getClientId()).build();
List<RegisteredClient> registrations = Arrays.asList(this.registration,
anotherRegistrationWithSameClientId);
new InMemoryRegisteredClientRepository(registrations);
})
.withMessageStartingWith("Registered client must be unique. Found duplicate client identifier:");
}
@Test
public void constructorListRegisteredClientWhenDuplicateClientSecretThenThrowIllegalArgumentException() {
assertThatIllegalArgumentException()
.isThrownBy(() -> {
RegisteredClient anotherRegistrationWithSameClientSecret = TestRegisteredClients.registeredClient2()
.clientSecret(this.registration.getClientSecret()).build();
List<RegisteredClient> registrations = Arrays.asList(this.registration,
anotherRegistrationWithSameClientSecret);
new InMemoryRegisteredClientRepository(registrations);
})
.withMessageStartingWith("Registered client must be unique. Found duplicate client secret for identifier:");
}
@Test
@@ -95,7 +121,9 @@ public class InMemoryRegisteredClientRepositoryTests {
@Test
public void findByIdWhenNullThenThrowIllegalArgumentException() {
assertThatThrownBy(() -> this.clients.findById(null)).isInstanceOf(IllegalArgumentException.class);
assertThatIllegalArgumentException()
.isThrownBy(() -> this.clients.findById(null))
.withMessageContaining("id cannot be empty");
}
@Test
@@ -112,79 +140,76 @@ public class InMemoryRegisteredClientRepositoryTests {
@Test
public void findByClientIdWhenNullThenThrowIllegalArgumentException() {
assertThatThrownBy(() -> this.clients.findByClientId(null)).isInstanceOf(IllegalArgumentException.class);
assertThatIllegalArgumentException()
.isThrownBy(() -> this.clients.findByClientId(null))
.withMessageContaining("clientId cannot be empty");
}
@Test
public void saveNullRegisteredClientThenThrowIllegalArgumentException() {
assertThatThrownBy(() -> this.clients.saveClient(null))
.isInstanceOf(IllegalArgumentException.class)
.hasMessageContaining("registeredClient cannot be null");
public void saveWhenNullThenThrowIllegalArgumentException() {
assertThatIllegalArgumentException()
.isThrownBy(() -> this.clients.save(null))
.withMessageContaining("registeredClient cannot be null");
}
@Test
public void saveRegisteredClientThenReturnsSavedRegisteredClientWhenSearchedById() {
RegisteredClient registeredClient = RegisteredClient.withId("new-client")
.clientId("new-client")
.clientSecret("secret")
public void saveWhenExistingIdThenThrowIllegalArgumentException() {
RegisteredClient registeredClient = createRegisteredClient(
this.registration.getId(), "client-id-2", "client-secret-2");
assertThatIllegalArgumentException()
.isThrownBy(() -> this.clients.save(registeredClient))
.withMessage("Registered client must be unique. Found duplicate identifier: " + registeredClient.getId());
}
@Test
public void saveWhenExistingClientIdThenThrowIllegalArgumentException() {
RegisteredClient registeredClient = createRegisteredClient(
"client-2", this.registration.getClientId(), "client-secret-2");
assertThatIllegalArgumentException()
.isThrownBy(() -> this.clients.save(registeredClient))
.withMessage("Registered client must be unique. Found duplicate client identifier: " + registeredClient.getClientId());
}
@Test
public void saveWhenExistingClientSecretThenThrowIllegalArgumentException() {
RegisteredClient registeredClient = createRegisteredClient(
"client-2", "client-id-2", this.registration.getClientSecret());
assertThatIllegalArgumentException()
.isThrownBy(() -> this.clients.save(registeredClient))
.withMessage("Registered client must be unique. Found duplicate client secret for identifier: " + registeredClient.getId());
}
@Test
public void saveWhenSavedAndFindByIdThenFound() {
RegisteredClient registeredClient = createRegisteredClient();
this.clients.save(registeredClient);
RegisteredClient savedClient = this.clients.findById(registeredClient.getId());
assertThat(savedClient).isEqualTo(registeredClient);
}
@Test
public void saveWhenSavedAndFindByClientIdThenFound() {
RegisteredClient registeredClient = createRegisteredClient();
this.clients.save(registeredClient);
RegisteredClient savedClient = this.clients.findByClientId(registeredClient.getClientId());
assertThat(savedClient).isEqualTo(registeredClient);
}
private static RegisteredClient createRegisteredClient() {
return createRegisteredClient("client-2", "client-id-2", "client-secret-2");
}
private static RegisteredClient createRegisteredClient(String id, String clientId, String clientSecret) {
// @formatter:off
return RegisteredClient.withId(id)
.clientId(clientId)
.clientSecret(clientSecret)
.authorizationGrantType(AuthorizationGrantType.CLIENT_CREDENTIALS)
.clientAuthenticationMethod(ClientAuthenticationMethod.BASIC)
.redirectUri("https://newclient.com")
.scope("scope1").build();
this.clients.saveClient(registeredClient);
RegisteredClient savedClient = this.clients.findById("new-client");
assertThat(savedClient).isNotNull().isEqualTo(registeredClient);
.redirectUri("https://client.example.com")
.scope("scope1")
.build();
// @formatter:on
}
@Test
public void saveRegisteredClientThenReturnsSavedRegisteredClientWhenSearchedByClientId() {
RegisteredClient registeredClient = RegisteredClient.withId("id1")
.clientId("new-client-id")
.clientSecret("secret")
.authorizationGrantType(AuthorizationGrantType.CLIENT_CREDENTIALS)
.clientAuthenticationMethod(ClientAuthenticationMethod.BASIC)
.redirectUri("https://newclient.com")
.scope("scope1").build();
this.clients.saveClient(registeredClient);
RegisteredClient savedClient = this.clients.findByClientId("new-client-id");
assertThat(savedClient).isNotNull().isEqualTo(registeredClient);
}
@Test
public void saveRegisteredClientWithExistingIdThrowIllegalArgumentException() {
assertThatThrownBy(() -> {
RegisteredClient registeredClient = RegisteredClient.withId("registration-1")
.clientId("new-client")
.clientSecret("secret")
.authorizationGrantType(AuthorizationGrantType.CLIENT_CREDENTIALS)
.clientAuthenticationMethod(ClientAuthenticationMethod.BASIC)
.redirectUri("https://newclient.com")
.scope("scope1").build();
this.clients.saveClient(registeredClient);
}).isInstanceOf(IllegalArgumentException.class)
.hasMessageContaining("Registered client must be unique. Found duplicate identifier");
}
@Test
public void saveRegisteredClientWithExistingClientIdThrowIllegalArgumentException() {
assertThatThrownBy(() -> {
RegisteredClient registeredClient = RegisteredClient.withId("new-client")
.clientId("client-1")
.clientSecret("secret")
.authorizationGrantType(AuthorizationGrantType.CLIENT_CREDENTIALS)
.clientAuthenticationMethod(ClientAuthenticationMethod.BASIC)
.redirectUri("https://newclient.com")
.scope("scope1").build();
this.clients.saveClient(registeredClient);
}).isInstanceOf(IllegalArgumentException.class)
.hasMessageContaining("Registered client must be unique. Found duplicate client identifier");
}
}

View File

@@ -15,6 +15,8 @@
*/
package org.springframework.security.oauth2.server.authorization.client;
import java.time.Instant;
import java.time.temporal.ChronoUnit;
import java.util.Collections;
import java.util.Set;
import java.util.stream.Collectors;
@@ -58,9 +60,14 @@ public class RegisteredClientTests {
@Test
public void buildWhenAllAttributesProvidedThenAllAttributesAreSet() {
Instant clientIdIssuedAt = Instant.now();
Instant clientSecretExpiresAt = clientIdIssuedAt.plus(30, ChronoUnit.DAYS);
RegisteredClient registration = RegisteredClient.withId(ID)
.clientId(CLIENT_ID)
.clientIdIssuedAt(clientIdIssuedAt)
.clientSecret(CLIENT_SECRET)
.clientSecretExpiresAt(clientSecretExpiresAt)
.clientName("client-name")
.authorizationGrantType(AuthorizationGrantType.AUTHORIZATION_CODE)
.clientAuthenticationMethod(ClientAuthenticationMethod.BASIC)
.redirectUris(redirectUris -> redirectUris.addAll(REDIRECT_URIS))
@@ -69,7 +76,10 @@ public class RegisteredClientTests {
assertThat(registration.getId()).isEqualTo(ID);
assertThat(registration.getClientId()).isEqualTo(CLIENT_ID);
assertThat(registration.getClientIdIssuedAt()).isEqualTo(clientIdIssuedAt);
assertThat(registration.getClientSecret()).isEqualTo(CLIENT_SECRET);
assertThat(registration.getClientSecretExpiresAt()).isEqualTo(clientSecretExpiresAt);
assertThat(registration.getClientName()).isEqualTo("client-name");
assertThat(registration.getAuthorizationGrantTypes())
.isEqualTo(Collections.singleton(AuthorizationGrantType.AUTHORIZATION_CODE));
assertThat(registration.getClientAuthenticationMethods()).isEqualTo(CLIENT_AUTHENTICATION_METHODS);
@@ -325,7 +335,10 @@ public class RegisteredClientTests {
assertThat(registration.getId()).isEqualTo(updated.getId());
assertThat(registration.getClientId()).isEqualTo(updated.getClientId());
assertThat(registration.getClientIdIssuedAt()).isEqualTo(updated.getClientIdIssuedAt());
assertThat(registration.getClientSecret()).isEqualTo(updated.getClientSecret());
assertThat(registration.getClientSecretExpiresAt()).isEqualTo(updated.getClientSecretExpiresAt());
assertThat(registration.getClientName()).isEqualTo(updated.getClientName());
assertThat(registration.getClientAuthenticationMethods()).isEqualTo(updated.getClientAuthenticationMethods());
assertThat(registration.getClientAuthenticationMethods()).isNotSameAs(updated.getClientAuthenticationMethods());
assertThat(registration.getAuthorizationGrantTypes()).isEqualTo(updated.getAuthorizationGrantTypes());
@@ -343,10 +356,12 @@ public class RegisteredClientTests {
@Test
public void buildWhenRegisteredClientValuesOverriddenThenPropagated() {
RegisteredClient registration = TestRegisteredClients.registeredClient().build();
String newName = "client-name";
String newSecret = "new-secret";
String newScope = "new-scope";
String newRedirectUri = "https://another-redirect-uri.com";
RegisteredClient updated = RegisteredClient.from(registration)
.clientName(newName)
.clientSecret(newSecret)
.scopes(scopes -> {
scopes.clear();
@@ -358,6 +373,8 @@ public class RegisteredClientTests {
})
.build();
assertThat(registration.getClientName()).isNotEqualTo(newName);
assertThat(updated.getClientName()).isEqualTo(newName);
assertThat(registration.getClientSecret()).isNotEqualTo(newSecret);
assertThat(updated.getClientSecret()).isEqualTo(newSecret);
assertThat(registration.getScopes()).doesNotContain(newScope);

View File

@@ -38,7 +38,6 @@ public class ProviderSettingsTests {
assertThat(providerSettings.tokenRevocationEndpoint()).isEqualTo("/oauth2/revoke");
assertThat(providerSettings.tokenIntrospectionEndpoint()).isEqualTo("/oauth2/introspect");
assertThat(providerSettings.oidcClientRegistrationEndpoint()).isEqualTo("/connect/register");
assertThat(providerSettings.isOidClientRegistrationEndpointEnabled()).isFalse();
}
@Test
@@ -48,8 +47,8 @@ public class ProviderSettingsTests {
String jwkSetEndpoint = "/oauth2/v1/jwks";
String tokenRevocationEndpoint = "/oauth2/v1/revoke";
String tokenIntrospectionEndpoint = "/oauth2/v1/introspect";
String issuer = "https://example.com:9000";
String oidcClientRegistrationEndpoint = "/connect/v1/register";
String issuer = "https://example.com:9000";
ProviderSettings providerSettings = new ProviderSettings()
.issuer(issuer)
@@ -59,7 +58,6 @@ public class ProviderSettingsTests {
.tokenRevocationEndpoint(tokenRevocationEndpoint)
.tokenIntrospectionEndpoint(tokenIntrospectionEndpoint)
.tokenRevocationEndpoint(tokenRevocationEndpoint)
.isOidClientRegistrationEndpointEnabled(true)
.oidcClientRegistrationEndpoint(oidcClientRegistrationEndpoint);
assertThat(providerSettings.issuer()).isEqualTo(issuer);
@@ -69,7 +67,6 @@ public class ProviderSettingsTests {
assertThat(providerSettings.tokenRevocationEndpoint()).isEqualTo(tokenRevocationEndpoint);
assertThat(providerSettings.tokenIntrospectionEndpoint()).isEqualTo(tokenIntrospectionEndpoint);
assertThat(providerSettings.oidcClientRegistrationEndpoint()).isEqualTo(oidcClientRegistrationEndpoint);
assertThat(providerSettings.isOidClientRegistrationEndpointEnabled()).isTrue();
}
@Test
@@ -78,7 +75,7 @@ public class ProviderSettingsTests {
.setting("name1", "value1")
.settings(settings -> settings.put("name2", "value2"));
assertThat(providerSettings.settings()).hasSize(9);
assertThat(providerSettings.settings()).hasSize(8);
assertThat(providerSettings.<String>setting("name1")).isEqualTo("value1");
assertThat(providerSettings.<String>setting("name2")).isEqualTo("value2");
}
@@ -126,12 +123,11 @@ public class ProviderSettingsTests {
@Test
public void oidcClientRegistrationEndpointWhenNullThenThrowIllegalArgumentException() {
ProviderSettings settings = new ProviderSettings();
assertThatThrownBy(() -> settings.oidcClientRegistrationEndpoint(null))
.isInstanceOf(IllegalArgumentException.class)
.hasMessage("value cannot be null");
assertThatIllegalArgumentException()
.isThrownBy(() -> settings.oidcClientRegistrationEndpoint(null))
.withMessage("value cannot be null");
}
@Test
public void jwksEndpointWhenNullThenThrowIllegalArgumentException() {
ProviderSettings settings = new ProviderSettings();
@@ -139,4 +135,5 @@ public class ProviderSettingsTests {
.isThrownBy(() -> settings.jwkSetEndpoint(null))
.withMessage("value cannot be null");
}
}

View File

@@ -15,9 +15,11 @@
*/
package org.springframework.security.oauth2.server.authorization.config;
import java.time.Duration;
import org.junit.Test;
import java.time.Duration;
import org.springframework.security.oauth2.jose.jws.SignatureAlgorithm;
import static org.assertj.core.api.Assertions.assertThat;
import static org.assertj.core.api.Assertions.assertThatThrownBy;
@@ -32,10 +34,11 @@ public class TokenSettingsTests {
@Test
public void constructorWhenDefaultThenDefaultsAreSet() {
TokenSettings tokenSettings = new TokenSettings();
assertThat(tokenSettings.settings()).hasSize(3);
assertThat(tokenSettings.settings()).hasSize(4);
assertThat(tokenSettings.accessTokenTimeToLive()).isEqualTo(Duration.ofMinutes(5));
assertThat(tokenSettings.reuseRefreshTokens()).isTrue();
assertThat(tokenSettings.refreshTokenTimeToLive()).isEqualTo(Duration.ofMinutes(60));
assertThat(tokenSettings.idTokenSignatureAlgorithm()).isEqualTo(SignatureAlgorithm.RS256);
}
@Test
@@ -101,17 +104,25 @@ public class TokenSettingsTests {
.isEqualTo("refreshTokenTimeToLive must be greater than Duration.ZERO");
}
@Test
public void idTokenSignatureAlgorithmWhenProvidedThenSet() {
SignatureAlgorithm idTokenSignatureAlgorithm = SignatureAlgorithm.RS512;
TokenSettings tokenSettings = new TokenSettings().idTokenSignatureAlgorithm(idTokenSignatureAlgorithm);
assertThat(tokenSettings.idTokenSignatureAlgorithm()).isEqualTo(idTokenSignatureAlgorithm);
}
@Test
public void settingWhenCalledThenReturnTokenSettings() {
Duration accessTokenTimeToLive = Duration.ofMinutes(10);
TokenSettings tokenSettings = new TokenSettings()
.<TokenSettings>setting("name1", "value1")
.accessTokenTimeToLive(accessTokenTimeToLive)
.<TokenSettings>settings(settings -> settings.put("name2", "value2"));
assertThat(tokenSettings.settings()).hasSize(5);
.settings(settings -> settings.put("name2", "value2"));
assertThat(tokenSettings.settings()).hasSize(6);
assertThat(tokenSettings.accessTokenTimeToLive()).isEqualTo(accessTokenTimeToLive);
assertThat(tokenSettings.reuseRefreshTokens()).isTrue();
assertThat(tokenSettings.refreshTokenTimeToLive()).isEqualTo(Duration.ofMinutes(60));
assertThat(tokenSettings.idTokenSignatureAlgorithm()).isEqualTo(SignatureAlgorithm.RS256);
assertThat(tokenSettings.<String>setting("name1")).isEqualTo("value1");
assertThat(tokenSettings.<String>setting("name2")).isEqualTo("value2");
}

View File

@@ -0,0 +1,314 @@
/*
* Copyright 2020-2021 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* https://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.springframework.security.oauth2.server.authorization.oidc.authentication;
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
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;
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;
import org.springframework.security.oauth2.core.OAuth2TokenType;
import org.springframework.security.oauth2.core.endpoint.OAuth2AuthorizationResponseType;
import org.springframework.security.oauth2.core.endpoint.OAuth2ParameterNames;
import org.springframework.security.oauth2.core.oidc.OidcClientRegistration;
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.TestJoseHeaders;
import org.springframework.security.oauth2.jwt.TestJwtClaimsSets;
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.resource.authentication.JwtAuthenticationToken;
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.eq;
import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.verify;
import static org.mockito.Mockito.when;
/**
* Tests for {@link OidcClientRegistrationAuthenticationProvider}.
*
* @author Ovidiu Popa
* @author Joe Grandja
*/
public class OidcClientRegistrationAuthenticationProviderTests {
private RegisteredClientRepository registeredClientRepository;
private OAuth2AuthorizationService authorizationService;
private OidcClientRegistrationAuthenticationProvider authenticationProvider;
@Before
public void setUp() {
this.registeredClientRepository = mock(RegisteredClientRepository.class);
this.authorizationService = mock(OAuth2AuthorizationService.class);
this.authenticationProvider = new OidcClientRegistrationAuthenticationProvider(
this.registeredClientRepository, this.authorizationService);
}
@Test
public void constructorWhenRegisteredClientRepositoryNullThenThrowIllegalArgumentException() {
assertThatIllegalArgumentException()
.isThrownBy(() -> new OidcClientRegistrationAuthenticationProvider(null, this.authorizationService))
.withMessage("registeredClientRepository cannot be null");
}
@Test
public void constructorWhenAuthorizationServiceNullThenThrowIllegalArgumentException() {
assertThatIllegalArgumentException()
.isThrownBy(() -> new OidcClientRegistrationAuthenticationProvider(this.registeredClientRepository, null))
.withMessage("authorizationService cannot be null");
}
@Test
public void supportsWhenTypeOidcClientRegistrationAuthenticationTokenThenReturnTrue() {
assertThat(this.authenticationProvider.supports(OidcClientRegistrationAuthenticationToken.class)).isTrue();
}
@Test
public void authenticateWhenPrincipalNotOAuth2TokenAuthenticationTokenThenThrowOAuth2AuthenticationException() {
TestingAuthenticationToken principal = new TestingAuthenticationToken("principal", "credentials");
OidcClientRegistration clientRegistration = OidcClientRegistration.builder()
.redirectUri("https://client.example.com")
.build();
OidcClientRegistrationAuthenticationToken authentication = new OidcClientRegistrationAuthenticationToken(
principal, clientRegistration);
assertThatThrownBy(() -> this.authenticationProvider.authenticate(authentication))
.isInstanceOf(OAuth2AuthenticationException.class)
.extracting(ex -> ((OAuth2AuthenticationException) ex).getError()).extracting("errorCode")
.isEqualTo(OAuth2ErrorCodes.INVALID_TOKEN);
}
@Test
public void authenticateWhenPrincipalNotAuthenticatedThenThrowOAuth2AuthenticationException() {
JwtAuthenticationToken principal = new JwtAuthenticationToken(createJwt());
OidcClientRegistration clientRegistration = OidcClientRegistration.builder()
.redirectUri("https://client.example.com")
.build();
OidcClientRegistrationAuthenticationToken authentication = new OidcClientRegistrationAuthenticationToken(
principal, clientRegistration);
assertThatThrownBy(() -> this.authenticationProvider.authenticate(authentication))
.isInstanceOf(OAuth2AuthenticationException.class)
.extracting(ex -> ((OAuth2AuthenticationException) ex).getError()).extracting("errorCode")
.isEqualTo(OAuth2ErrorCodes.INVALID_TOKEN);
}
@Test
public void authenticateWhenAccessTokenNotFoundThenThrowOAuth2AuthenticationException() {
Jwt jwt = createJwt();
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);
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 authenticateWhenAccessTokenNotActiveThenThrowOAuth2AuthenticationException() {
Jwt jwt = createJwt();
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.create"));
OidcClientRegistration clientRegistration = OidcClientRegistration.builder()
.redirectUri("https://client.example.com")
.build();
OidcClientRegistrationAuthenticationToken authentication = new OidcClientRegistrationAuthenticationToken(
principal, clientRegistration);
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 authenticateWhenAccessTokenNotAuthorizedThenThrowOAuth2AuthenticationException() {
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"));
OidcClientRegistration clientRegistration = OidcClientRegistration.builder()
.redirectUri("https://client.example.com")
.build();
OidcClientRegistrationAuthenticationToken authentication = new OidcClientRegistrationAuthenticationToken(
principal, clientRegistration);
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 authenticateWhenValidAccessTokenThenReturnClientRegistration() {
Jwt jwt = createJwt();
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_client.create"));
// @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
OidcClientRegistrationAuthenticationToken authentication = new OidcClientRegistrationAuthenticationToken(
principal, clientRegistration);
OidcClientRegistrationAuthenticationToken authenticationResult =
(OidcClientRegistrationAuthenticationToken) this.authenticationProvider.authenticate(authentication);
ArgumentCaptor<RegisteredClient> registeredClientCaptor = ArgumentCaptor.forClass(RegisteredClient.class);
ArgumentCaptor<OAuth2Authorization> authorizationCaptor = ArgumentCaptor.forClass(OAuth2Authorization.class);
verify(this.authorizationService).findByToken(
eq(jwtAccessToken.getTokenValue()), eq(OAuth2TokenType.ACCESS_TOKEN));
verify(this.registeredClientRepository).save(registeredClientCaptor.capture());
verify(this.authorizationService).save(authorizationCaptor.capture());
OAuth2Authorization authorizationResult = authorizationCaptor.getValue();
assertThat(authorizationResult.getAccessToken().isInvalidated()).isTrue();
if (authorizationResult.getRefreshToken() != null) {
assertThat(authorizationResult.getRefreshToken().isInvalidated()).isTrue();
}
RegisteredClient registeredClientResult = registeredClientCaptor.getValue();
assertThat(registeredClientResult.getId()).isNotNull();
assertThat(registeredClientResult.getClientId()).isNotNull();
assertThat(registeredClientResult.getClientIdIssuedAt()).isNotNull();
assertThat(registeredClientResult.getClientSecret()).isNotNull();
assertThat(registeredClientResult.getClientName()).isEqualTo(clientRegistration.getClientName());
assertThat(registeredClientResult.getClientAuthenticationMethods()).containsExactly(ClientAuthenticationMethod.BASIC);
assertThat(registeredClientResult.getRedirectUris()).containsExactly("https://client.example.com");
assertThat(registeredClientResult.getAuthorizationGrantTypes())
.containsExactlyInAnyOrder(AuthorizationGrantType.AUTHORIZATION_CODE, AuthorizationGrantType.CLIENT_CREDENTIALS);
assertThat(registeredClientResult.getScopes()).containsExactlyInAnyOrder("scope1", "scope2");
assertThat(registeredClientResult.getClientSettings().requireProofKey()).isTrue();
assertThat(registeredClientResult.getClientSettings().requireUserConsent()).isTrue();
assertThat(registeredClientResult.getTokenSettings().idTokenSignatureAlgorithm()).isEqualTo(SignatureAlgorithm.RS256);
OidcClientRegistration clientRegistrationResult = authenticationResult.getClientRegistration();
assertThat(clientRegistrationResult.getClientId()).isEqualTo(registeredClientResult.getClientId());
assertThat(clientRegistrationResult.getClientIdIssuedAt()).isEqualTo(registeredClientResult.getClientIdIssuedAt());
assertThat(clientRegistrationResult.getClientSecret()).isEqualTo(registeredClientResult.getClientSecret());
assertThat(clientRegistrationResult.getClientSecretExpiresAt()).isEqualTo(registeredClientResult.getClientSecretExpiresAt());
assertThat(clientRegistrationResult.getClientName()).isEqualTo(registeredClientResult.getClientName());
assertThat(clientRegistrationResult.getRedirectUris())
.containsExactlyInAnyOrderElementsOf(registeredClientResult.getRedirectUris());
List<String> grantTypes = new ArrayList<>();
registeredClientResult.getAuthorizationGrantTypes().forEach(authorizationGrantType ->
grantTypes.add(authorizationGrantType.getValue()));
assertThat(clientRegistrationResult.getGrantTypes()).containsExactlyInAnyOrderElementsOf(grantTypes);
assertThat(clientRegistrationResult.getResponseTypes())
.containsExactly(OAuth2AuthorizationResponseType.CODE.getValue());
assertThat(clientRegistrationResult.getScopes())
.containsExactlyInAnyOrderElementsOf(registeredClientResult.getScopes());
assertThat(clientRegistrationResult.getTokenEndpointAuthenticationMethod())
.isEqualTo(registeredClientResult.getClientAuthenticationMethods().iterator().next().getValue());
assertThat(clientRegistrationResult.getIdTokenSignedResponseAlgorithm())
.isEqualTo(registeredClientResult.getTokenSettings().idTokenSignatureAlgorithm().getName());
}
private static Jwt createJwt() {
return createJwt(Collections.singleton("client.create"));
}
private static Jwt createJwt(Set<String> scopes) {
// @formatter:off
JoseHeader joseHeader = TestJoseHeaders.joseHeader()
.build();
JwtClaimsSet jwtClaimsSet = TestJwtClaimsSets.jwtClaimsSet()
.claim(OAuth2ParameterNames.SCOPE, scopes)
.build();
Jwt jwt = Jwt.withTokenValue("jwt-access-token")
.headers(headers -> headers.putAll(joseHeader.getHeaders()))
.claims(claims -> claims.putAll(jwtClaimsSet.getClaims()))
.build();
// @formatter:on
return jwt;
}
}

View File

@@ -0,0 +1,61 @@
/*
* Copyright 2020-2021 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* https://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.springframework.security.oauth2.server.authorization.oidc.authentication;
import org.junit.Test;
import org.springframework.security.authentication.TestingAuthenticationToken;
import org.springframework.security.oauth2.core.oidc.OidcClientRegistration;
import static org.assertj.core.api.Assertions.assertThat;
import static org.assertj.core.api.Assertions.assertThatIllegalArgumentException;
/**
* Tests for {@link OidcClientRegistrationAuthenticationToken}.
*
* @author Joe Grandja
*/
public class OidcClientRegistrationAuthenticationTokenTests {
private TestingAuthenticationToken principal = new TestingAuthenticationToken("principal", "credentials");
private OidcClientRegistration clientRegistration = OidcClientRegistration.builder()
.redirectUri("https://client.example.com").build();
@Test
public void constructorWhenPrincipalNullThenThrowIllegalArgumentException() {
assertThatIllegalArgumentException()
.isThrownBy(() -> new OidcClientRegistrationAuthenticationToken(null, this.clientRegistration))
.withMessage("principal cannot be null");
}
@Test
public void constructorWhenClientRegistrationNullThenThrowIllegalArgumentException() {
assertThatIllegalArgumentException()
.isThrownBy(() -> new OidcClientRegistrationAuthenticationToken(this.principal, null))
.withMessage("clientRegistration cannot be null");
}
@Test
public void constructorWhenAllValuesProvidedThenCreated() {
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.isAuthenticated()).isEqualTo(this.principal.isAuthenticated());
}
}

View File

@@ -15,267 +15,259 @@
*/
package org.springframework.security.oauth2.server.authorization.oidc.web;
import com.fasterxml.jackson.core.JsonProcessingException;
import com.fasterxml.jackson.databind.ObjectMapper;
import org.junit.After;
import org.junit.Before;
import org.junit.BeforeClass;
import org.junit.Test;
import org.mockito.AdditionalAnswers;
import org.mockito.ArgumentCaptor;
import org.springframework.http.HttpStatus;
import org.springframework.http.MediaType;
import org.springframework.http.converter.HttpMessageConverter;
import org.springframework.mock.http.client.MockClientHttpResponse;
import org.springframework.mock.web.MockHttpServletRequest;
import org.springframework.mock.web.MockHttpServletResponse;
import org.springframework.security.authentication.AuthenticationManager;
import org.springframework.security.core.GrantedAuthority;
import org.springframework.security.core.authority.AuthorityUtils;
import org.springframework.security.core.context.SecurityContext;
import org.springframework.security.core.context.SecurityContextHolder;
import org.springframework.security.oauth2.core.AuthorizationGrantType;
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.OAuth2AuthorizationResponseType;
import org.springframework.security.oauth2.core.http.converter.OAuth2ErrorHttpMessageConverter;
import org.springframework.security.oauth2.core.oidc.OidcClientMetadataClaimNames;
import org.springframework.security.oauth2.core.oidc.OidcClientRegistration;
import org.springframework.security.oauth2.jwt.Jwt;
import org.springframework.security.oauth2.server.authorization.client.RegisteredClient;
import org.springframework.security.oauth2.server.authorization.client.RegisteredClientRepository;
import org.springframework.security.oauth2.server.resource.authentication.JwtAuthenticationToken;
import java.time.Instant;
import java.util.Collections;
import javax.servlet.FilterChain;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.util.List;
import java.util.Map;
import java.util.stream.Collectors;
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;
import org.springframework.mock.http.client.MockClientHttpResponse;
import org.springframework.mock.web.MockHttpServletRequest;
import org.springframework.mock.web.MockHttpServletResponse;
import org.springframework.security.authentication.AuthenticationManager;
import org.springframework.security.core.authority.AuthorityUtils;
import org.springframework.security.core.context.SecurityContext;
import org.springframework.security.core.context.SecurityContextHolder;
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.OAuth2Error;
import org.springframework.security.oauth2.core.OAuth2ErrorCodes;
import org.springframework.security.oauth2.core.endpoint.OAuth2AuthorizationResponseType;
import org.springframework.security.oauth2.core.endpoint.OAuth2ParameterNames;
import org.springframework.security.oauth2.core.http.converter.OAuth2ErrorHttpMessageConverter;
import org.springframework.security.oauth2.core.oidc.OidcClientRegistration;
import org.springframework.security.oauth2.core.oidc.http.converter.OidcClientRegistrationHttpMessageConverter;
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.TestJoseHeaders;
import org.springframework.security.oauth2.jwt.TestJwtClaimsSets;
import org.springframework.security.oauth2.server.authorization.oidc.authentication.OidcClientRegistrationAuthenticationToken;
import org.springframework.security.oauth2.server.resource.authentication.JwtAuthenticationToken;
import static org.assertj.core.api.Assertions.assertThat;
import static org.assertj.core.api.Assertions.assertThatThrownBy;
import static org.assertj.core.api.Assertions.assertThatIllegalArgumentException;
import static org.mockito.ArgumentMatchers.any;
import static org.mockito.Mockito.doNothing;
import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.reset;
import static org.mockito.Mockito.verify;
import static org.mockito.Mockito.verifyNoInteractions;
import static org.mockito.Mockito.when;
/**
* Tests for {@link OidcClientRegistrationEndpointFilter}
* Tests for {@link OidcClientRegistrationEndpointFilter}.
*
* @author Ovidiu Popa
* @since 0.1.1
* @author Joe Grandja
*/
public class OidcClientRegistrationEndpointFilterTests {
private static final OidcClientRegistration.Builder OIDC_CLIENT_REGISTRATION = OidcClientRegistration.builder()
.redirectUri("https://localhost:8080/client")
.responseType("code")
.grantType("authorization_code")
.tokenEndpointAuthenticationMethod("basic")
.scope("test");
private AuthenticationManager authenticationManager;
private OidcClientRegistrationEndpointFilter filter;
private final HttpMessageConverter<OidcClientRegistration> clientRegistrationHttpMessageConverter =
new OidcClientRegistrationHttpMessageConverter();
private final HttpMessageConverter<OAuth2Error> errorHttpResponseConverter =
new OAuth2ErrorHttpMessageConverter();
private static RegisteredClientRepository registeredClientRepository;
private static AuthenticationManager authenticationManager;
@BeforeClass
public static void init() {
registeredClientRepository = mock(RegisteredClientRepository.class);
authenticationManager = mock(AuthenticationManager.class);
}
@Before
public void setup() {
reset(registeredClientRepository);
reset(authenticationManager);
this.authenticationManager = mock(AuthenticationManager.class);
this.filter = new OidcClientRegistrationEndpointFilter(this.authenticationManager);
}
@After
public void tearDown() {
public void cleanup() {
SecurityContextHolder.clearContext();
}
@Test
public void constructorWhenRegisteredClientRepositoryNullThenThrowIllegalArgumentException() {
assertThatThrownBy(() -> new OidcClientRegistrationEndpointFilter(null,
authenticationManager))
.isInstanceOf(IllegalArgumentException.class)
.hasMessage("registeredClientRepository cannot be null");
}
@Test
public void constructorWhenAuthenticationManagerNullThenThrowIllegalArgumentException() {
assertThatThrownBy(() -> new OidcClientRegistrationEndpointFilter(registeredClientRepository, null))
.isInstanceOf(IllegalArgumentException.class)
.hasMessage("authenticationManager cannot be null");
assertThatIllegalArgumentException()
.isThrownBy(() -> new OidcClientRegistrationEndpointFilter(null))
.withMessage("authenticationManager cannot be null");
}
@Test
public void constructorWhenOidcClientRegistrationUriNullThenThrowIllegalArgumentException() {
assertThatThrownBy(() -> new OidcClientRegistrationEndpointFilter(registeredClientRepository, authenticationManager, null))
.isInstanceOf(IllegalArgumentException.class)
.hasMessage("oidcClientRegistrationUri cannot be empty");
}
@Test
public void constructorWhenOidcClientRegistrationUriEmptyThenThrowIllegalArgumentException() {
assertThatThrownBy(() -> new OidcClientRegistrationEndpointFilter(registeredClientRepository, authenticationManager, ""))
.isInstanceOf(IllegalArgumentException.class)
.hasMessage("oidcClientRegistrationUri cannot be empty");
public void constructorWhenClientRegistrationEndpointUriNullThenThrowIllegalArgumentException() {
assertThatIllegalArgumentException()
.isThrownBy(() -> new OidcClientRegistrationEndpointFilter(this.authenticationManager, null))
.withMessage("clientRegistrationEndpointUri cannot be empty");
}
@Test
public void doFilterWhenNotClientRegistrationRequestThenNotProcessed() throws Exception {
OidcClientRegistrationEndpointFilter filter =
new OidcClientRegistrationEndpointFilter(registeredClientRepository, authenticationManager);
String requestUri = "/path";
MockHttpServletRequest request = new MockHttpServletRequest("POST", requestUri);
request.setServletPath(requestUri);
MockHttpServletResponse response = new MockHttpServletResponse();
FilterChain filterChain = mock(FilterChain.class);
filter.doFilter(request, response, filterChain);
this.filter.doFilter(request, response, filterChain);
verify(filterChain).doFilter(any(HttpServletRequest.class), any(HttpServletResponse.class));
}
@Test
public void doFilterWhenClientRegistrationRequestGetThenNotProcessed() throws Exception {
OidcClientRegistrationEndpointFilter filter =
new OidcClientRegistrationEndpointFilter(registeredClientRepository, authenticationManager);
String requestUri = OidcClientRegistrationEndpointFilter.DEFAULT_OIDC_CLIENT_REGISTRATION_ENDPOINT_URI;
MockHttpServletRequest request = new MockHttpServletRequest("GET", requestUri);
request.setServletPath(requestUri);
MockHttpServletResponse response = new MockHttpServletResponse();
FilterChain filterChain = mock(FilterChain.class);
filter.doFilter(request, response, filterChain);
this.filter.doFilter(request, response, filterChain);
verify(filterChain).doFilter(any(HttpServletRequest.class), any(HttpServletResponse.class));
}
@Test
public void doFilterWhenAuthenticationManagerThrowsOAuth2AuthenticationExceptionThenBadRequest() throws Exception {
setSecurityContext("client-registration-token", true, "SCOPE_client.create");
when(authenticationManager.authenticate(any(JwtAuthenticationToken.class)))
.thenThrow(new OAuth2AuthenticationException(new OAuth2Error(OAuth2ErrorCodes.INVALID_GRANT)));
OidcClientRegistrationEndpointFilter filter =
new OidcClientRegistrationEndpointFilter(registeredClientRepository, authenticationManager);
public void doFilterWhenClientRegistrationRequestInvalidThenInvalidRequestError() throws Exception {
String requestUri = OidcClientRegistrationEndpointFilter.DEFAULT_OIDC_CLIENT_REGISTRATION_ENDPOINT_URI;
MockHttpServletRequest request = new MockHttpServletRequest("POST", requestUri);
request.setServletPath(requestUri);
request.setContent(convertToByteArray(OIDC_CLIENT_REGISTRATION.build()));
request.setContent("invalid content".getBytes());
MockHttpServletResponse response = new MockHttpServletResponse();
FilterChain filterChain = mock(FilterChain.class);
filter.doFilter(request, response, filterChain);
this.filter.doFilter(request, response, filterChain);
verifyNoInteractions(filterChain);
assertThat(response.getStatus()).isEqualTo(HttpStatus.BAD_REQUEST.value());
OAuth2Error error = readError(response);
assertThat(error.getErrorCode()).isEqualTo(OAuth2ErrorCodes.INVALID_GRANT);
assertThat(error.getErrorCode()).isEqualTo(OAuth2ErrorCodes.INVALID_REQUEST);
assertThat(error.getDescription()).startsWith("OpenID Client Registration Error: ");
}
@Test
@SuppressWarnings("unchecked")
public void doFilterWhenClientRegistrationRequestThenClientRegistrationResponse() throws Exception {
public void doFilterWhenClientRegistrationRequestInvalidTokenThenUnauthorizedError() throws Exception {
doFilterWhenClientRegistrationRequestInvalidThenError(
OAuth2ErrorCodes.INVALID_TOKEN, HttpStatus.UNAUTHORIZED);
}
doNothing().when(registeredClientRepository).saveClient(any(RegisteredClient.class));
when(authenticationManager.authenticate(any(JwtAuthenticationToken.class))).then(AdditionalAnswers.returnsFirstArg());
setSecurityContext("client-registration-token", true, "SCOPE_client.create");
@Test
public void doFilterWhenClientRegistrationRequestInsufficientTokenScopeThenForbiddenError() throws Exception {
doFilterWhenClientRegistrationRequestInvalidThenError(
OAuth2ErrorCodes.INSUFFICIENT_SCOPE, HttpStatus.FORBIDDEN);
}
OidcClientRegistrationEndpointFilter filter =
new OidcClientRegistrationEndpointFilter(registeredClientRepository, authenticationManager);
private void doFilterWhenClientRegistrationRequestInvalidThenError(
String errorCode, HttpStatus status) throws Exception {
Jwt jwt = createJwt();
JwtAuthenticationToken principal = new JwtAuthenticationToken(
jwt, AuthorityUtils.createAuthorityList("SCOPE_client.create"));
SecurityContext securityContext = SecurityContextHolder.createEmptyContext();
securityContext.setAuthentication(principal);
SecurityContextHolder.setContext(securityContext);
when(this.authenticationManager.authenticate(any()))
.thenThrow(new OAuth2AuthenticationException(new OAuth2Error(errorCode)));
// @formatter:off
OidcClientRegistration clientRegistrationRequest = 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
String requestUri = OidcClientRegistrationEndpointFilter.DEFAULT_OIDC_CLIENT_REGISTRATION_ENDPOINT_URI;
MockHttpServletRequest request = new MockHttpServletRequest("POST", requestUri);
request.setServletPath(requestUri);
writeClientRegistrationRequest(request, clientRegistrationRequest);
MockHttpServletResponse response = new MockHttpServletResponse();
FilterChain filterChain = mock(FilterChain.class);
request.setContent(convertToByteArray(OIDC_CLIENT_REGISTRATION.build()));
this.filter.doFilter(request, response, filterChain);
verifyNoInteractions(filterChain);
assertThat(response.getStatus()).isEqualTo(status.value());
OAuth2Error error = readError(response);
assertThat(error.getErrorCode()).isEqualTo(errorCode);
}
@Test
public void doFilterWhenClientRegistrationRequestValidThenSuccessResponse() throws Exception {
// @formatter:off
OidcClientRegistration.Builder clientRegistrationBuilder = OidcClientRegistration.builder()
.clientName("client-name")
.redirectUri("https://client.example.com")
.grantType(AuthorizationGrantType.AUTHORIZATION_CODE.getValue())
.grantType(AuthorizationGrantType.CLIENT_CREDENTIALS.getValue())
.scope("scope1")
.scope("scope2");
OidcClientRegistration clientRegistrationRequest = clientRegistrationBuilder.build();
OidcClientRegistration expectedClientRegistrationResponse = clientRegistrationBuilder
.clientId("client-id")
.clientIdIssuedAt(Instant.now())
.clientSecret("client-secret")
.tokenEndpointAuthenticationMethod(ClientAuthenticationMethod.BASIC.getValue())
.responseType(OAuth2AuthorizationResponseType.CODE.getValue())
.idTokenSignedResponseAlgorithm(SignatureAlgorithm.RS256.getName())
.build();
// @formatter:on
Jwt jwt = createJwt();
JwtAuthenticationToken principal = new JwtAuthenticationToken(
jwt, AuthorityUtils.createAuthorityList("SCOPE_client.create"));
OidcClientRegistrationAuthenticationToken clientRegistrationAuthenticationResult =
new OidcClientRegistrationAuthenticationToken(principal, expectedClientRegistrationResponse);
when(this.authenticationManager.authenticate(any())).thenReturn(clientRegistrationAuthenticationResult);
SecurityContext securityContext = SecurityContextHolder.createEmptyContext();
securityContext.setAuthentication(principal);
SecurityContextHolder.setContext(securityContext);
String requestUri = OidcClientRegistrationEndpointFilter.DEFAULT_OIDC_CLIENT_REGISTRATION_ENDPOINT_URI;
MockHttpServletRequest request = new MockHttpServletRequest("POST", requestUri);
request.setServletPath(requestUri);
writeClientRegistrationRequest(request, clientRegistrationRequest);
MockHttpServletResponse response = new MockHttpServletResponse();
FilterChain filterChain = mock(FilterChain.class);
filter.doFilter(request, response, filterChain);
this.filter.doFilter(request, response, filterChain);
verifyNoInteractions(filterChain);
verify(authenticationManager).authenticate(any());
ArgumentCaptor<RegisteredClient> registeredClientCaptor = ArgumentCaptor.forClass(RegisteredClient.class);
verify(registeredClientRepository).saveClient(registeredClientCaptor.capture());
RegisteredClient registeredClient = registeredClientCaptor.getValue();
assertThat(response.getStatus()).isEqualTo(HttpStatus.CREATED.value());
assertThat(response.getContentType()).isEqualTo(MediaType.APPLICATION_JSON_VALUE);
ObjectMapper objectMapper = new ObjectMapper();
Map<String, Object> clientRegistrationResponse = objectMapper.readerFor(Map.class)
.readValue(response.getContentAsString());
assertThat(clientRegistrationResponse.get(OidcClientMetadataClaimNames.CLIENT_ID))
.isEqualTo(registeredClient.getClientId());
assertThat((String) clientRegistrationResponse.get(OidcClientMetadataClaimNames.CLIENT_SECRET))
.isEqualTo(registeredClient.getClientSecret());
assertThat((List<String>) clientRegistrationResponse.get(OidcClientMetadataClaimNames.REDIRECT_URIS))
.containsAll(registeredClient.getRedirectUris());
assertThat(clientRegistrationResponse.get(OidcClientMetadataClaimNames.CLIENT_ID_ISSUED_AT))
.isNotNull();
assertThat(clientRegistrationResponse.get(OidcClientMetadataClaimNames.CLIENT_SECRET_EXPIRES_AT))
.isEqualTo(0.0);
assertThat((List<String>) clientRegistrationResponse.get(OidcClientMetadataClaimNames.RESPONSE_TYPES))
.contains(OAuth2AuthorizationResponseType.CODE.getValue());
assertThat((List<String>) clientRegistrationResponse.get(OidcClientMetadataClaimNames.GRANT_TYPES))
.containsAll(grantTypes(registeredClient));
assertThat(clientRegistrationResponse.get(OidcClientMetadataClaimNames.SCOPE))
.isEqualTo(String.join(" ", registeredClient.getScopes()));
assertThat(clientRegistrationResponse.get(OidcClientMetadataClaimNames.TOKEN_ENDPOINT_AUTH_METHOD))
.isEqualTo(registeredClient.getClientAuthenticationMethods().iterator().next().getValue());
}
private List<String> grantTypes(RegisteredClient registeredClient) {
return registeredClient.getAuthorizationGrantTypes().stream()
.map(AuthorizationGrantType::getValue)
.collect(Collectors.toList());
}
private static void setSecurityContext(String tokenValue, boolean authenticated, String... authorities) {
Jwt jwt = Jwt.withTokenValue(tokenValue)
.header("alg", "none")
.claim("sub", "client")
.build();
List<GrantedAuthority> grantedAuthorities = AuthorityUtils.createAuthorityList(authorities);
JwtAuthenticationToken jwtAuthenticationToken = new JwtAuthenticationToken(jwt, grantedAuthorities);
jwtAuthenticationToken.setAuthenticated(authenticated);
SecurityContext securityContext = SecurityContextHolder.createEmptyContext();
securityContext.setAuthentication(jwtAuthenticationToken);
SecurityContextHolder.setContext(securityContext);
}
private static byte[] convertToByteArray(OidcClientRegistration clientRegistration) throws JsonProcessingException {
ObjectMapper objectMapper = new ObjectMapper();
return objectMapper
.writerFor(Map.class)
.writeValueAsBytes(clientRegistration.getClaims());
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());
}
private OAuth2Error readError(MockHttpServletResponse response) throws Exception {
@@ -283,4 +275,33 @@ public class OidcClientRegistrationEndpointFilterTests {
response.getContentAsByteArray(), HttpStatus.valueOf(response.getStatus()));
return this.errorHttpResponseConverter.read(OAuth2Error.class, httpResponse);
}
private void writeClientRegistrationRequest(MockHttpServletRequest request,
OidcClientRegistration clientRegistration) throws Exception {
MockClientHttpRequest httpRequest = new MockClientHttpRequest();
this.clientRegistrationHttpMessageConverter.write(clientRegistration, null, httpRequest);
request.setContent(httpRequest.getBodyAsBytes());
}
private OidcClientRegistration readClientRegistrationResponse(MockHttpServletResponse response) throws Exception {
MockClientHttpResponse httpResponse = new MockClientHttpResponse(
response.getContentAsByteArray(), HttpStatus.valueOf(response.getStatus()));
return this.clientRegistrationHttpMessageConverter.read(OidcClientRegistration.class, httpResponse);
}
private static Jwt createJwt() {
// @formatter:off
JoseHeader joseHeader = TestJoseHeaders.joseHeader()
.build();
JwtClaimsSet jwtClaimsSet = TestJwtClaimsSets.jwtClaimsSet()
.claim(OAuth2ParameterNames.SCOPE, Collections.singleton("client.create"))
.build();
Jwt jwt = Jwt.withTokenValue("jwt-access-token")
.headers(headers -> headers.putAll(joseHeader.getHeaders()))
.claims(claims -> claims.putAll(jwtClaimsSet.getClaims()))
.build();
// @formatter:on
return jwt;
}
}