diff --git a/oauth2-authorization-server/src/test/java/org/springframework/security/oauth2/server/authorization/config/annotation/web/configurers/OidcProviderConfigurationTests.java b/oauth2-authorization-server/src/test/java/org/springframework/security/oauth2/server/authorization/config/annotation/web/configurers/OidcProviderConfigurationTests.java index 30f25fcc..035f5933 100644 --- a/oauth2-authorization-server/src/test/java/org/springframework/security/oauth2/server/authorization/config/annotation/web/configurers/OidcProviderConfigurationTests.java +++ b/oauth2-authorization-server/src/test/java/org/springframework/security/oauth2/server/authorization/config/annotation/web/configurers/OidcProviderConfigurationTests.java @@ -15,6 +15,8 @@ */ package org.springframework.security.oauth2.server.authorization.config.annotation.web.configurers; +import java.util.function.Consumer; + import org.junit.Rule; import org.junit.Test; @@ -30,17 +32,23 @@ import org.springframework.security.oauth2.core.ClientAuthenticationMethod; import org.springframework.security.oauth2.core.endpoint.OAuth2AuthorizationResponseType; import org.springframework.security.oauth2.core.oidc.OidcScopes; import org.springframework.security.oauth2.jose.jws.SignatureAlgorithm; +import org.springframework.security.oauth2.server.authorization.OAuth2AuthorizationServerMetadataClaimNames; import org.springframework.security.oauth2.server.authorization.client.InMemoryRegisteredClientRepository; 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.annotation.web.configuration.OAuth2AuthorizationServerConfiguration; +import org.springframework.security.oauth2.server.authorization.oidc.OidcProviderConfiguration; import org.springframework.security.oauth2.server.authorization.settings.AuthorizationServerSettings; import org.springframework.security.oauth2.server.authorization.test.SpringTestRule; import org.springframework.security.web.SecurityFilterChain; +import org.springframework.security.web.util.matcher.RequestMatcher; import org.springframework.test.web.servlet.MockMvc; import org.springframework.test.web.servlet.ResultMatcher; +import static org.assertj.core.api.Assertions.assertThatThrownBy; +import static org.hamcrest.CoreMatchers.hasItems; +import static org.springframework.security.test.web.servlet.request.SecurityMockMvcRequestPostProcessors.user; import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.get; import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.jsonPath; import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status; @@ -50,6 +58,7 @@ import static org.springframework.test.web.servlet.result.MockMvcResultMatchers. * * @author Sahariar Alam Khandoker * @author Joe Grandja + * @author Daniel Garnier-Moiroux */ public class OidcProviderConfigurationTests { private static final String DEFAULT_OIDC_PROVIDER_CONFIGURATION_ENDPOINT_URI = "/.well-known/openid-configuration"; @@ -65,13 +74,34 @@ public class OidcProviderConfigurationTests { private MockMvc mvc; @Test - public void requestWhenConfigurationRequestThenDefaultConfigurationResponse() throws Exception { + public void requestWhenConfigurationRequestAndIssuerSetThenReturnDefaultConfigurationResponse() throws Exception { this.spring.register(AuthorizationServerConfiguration.class).autowire(); this.mvc.perform(get(DEFAULT_OIDC_PROVIDER_CONFIGURATION_ENDPOINT_URI)) .andExpect(status().is2xxSuccessful()) - .andExpectAll(defaultConfigurationMatchers()) - .andExpect(jsonPath("$.registration_endpoint").doesNotExist()); + .andExpectAll(defaultConfigurationMatchers()); + } + + // gh-632 + @Test + public void requestWhenConfigurationRequestAndUserAuthenticatedThenReturnConfigurationResponse() throws Exception { + this.spring.register(AuthorizationServerConfiguration.class).autowire(); + + this.mvc.perform(get(DEFAULT_OIDC_PROVIDER_CONFIGURATION_ENDPOINT_URI) + .with(user("user"))) + .andExpect(status().is2xxSuccessful()) + .andExpectAll(defaultConfigurationMatchers()); + } + + // gh-616 + @Test + public void requestWhenConfigurationRequestAndConfigurationCustomizerSetThenReturnCustomConfigurationResponse() throws Exception { + this.spring.register(AuthorizationServerConfigurationWithProviderConfigurationCustomizer.class).autowire(); + + this.mvc.perform(get(DEFAULT_OIDC_PROVIDER_CONFIGURATION_ENDPOINT_URI)) + .andExpect(status().is2xxSuccessful()) + .andExpect(jsonPath(OAuth2AuthorizationServerMetadataClaimNames.SCOPES_SUPPORTED, + hasItems(OidcScopes.OPENID, OidcScopes.PROFILE, OidcScopes.EMAIL))); } @Test @@ -117,6 +147,55 @@ public class OidcProviderConfigurationTests { // @formatter:on } + @Test + public void loadContextWhenIssuerNotValidUrlThenThrowException() { + assertThatThrownBy( + () -> this.spring.register(AuthorizationServerConfigurationWithInvalidIssuerUrl.class).autowire() + ); + } + + @Test + public void loadContextWhenIssuerNotValidUriThenThrowException() { + assertThatThrownBy( + () -> this.spring.register(AuthorizationServerConfigurationWithInvalidIssuerUri.class).autowire() + ); + } + + @Test + public void loadContextWhenIssuerWithQueryThenThrowException() { + assertThatThrownBy( + () -> this.spring.register(AuthorizationServerConfigurationWithIssuerQuery.class).autowire() + ); + } + + @Test + public void loadContextWhenIssuerWithFragmentThenThrowException() { + assertThatThrownBy( + () -> this.spring.register(AuthorizationServerConfigurationWithIssuerFragment.class).autowire() + ); + } + + @Test + public void loadContextWhenIssuerWithQueryAndFragmentThenThrowException() { + assertThatThrownBy( + () -> this.spring.register(AuthorizationServerConfigurationWithIssuerQueryAndFragment.class).autowire() + ); + } + + @Test + public void loadContextWhenIssuerWithEmptyQueryThenThrowException() { + assertThatThrownBy( + () -> this.spring.register(AuthorizationServerConfigurationWithIssuerEmptyQuery.class).autowire() + ); + } + + @Test + public void loadContextWhenIssuerWithEmptyFragmentThenThrowException() { + assertThatThrownBy( + () -> this.spring.register(AuthorizationServerConfigurationWithIssuerEmptyFragment.class).autowire() + ); + } + @EnableWebSecurity @Import(OAuth2AuthorizationServerConfiguration.class) static class AuthorizationServerConfiguration { @@ -136,6 +215,43 @@ public class OidcProviderConfigurationTests { } + @Configuration + @EnableWebSecurity + static class AuthorizationServerConfigurationWithProviderConfigurationCustomizer extends AuthorizationServerConfiguration { + + // @formatter:off + @Bean + public SecurityFilterChain authorizationServerSecurityFilterChain(HttpSecurity http) throws Exception { + OAuth2AuthorizationServerConfigurer authorizationServerConfigurer = + new OAuth2AuthorizationServerConfigurer(); + http.apply(authorizationServerConfigurer); + + authorizationServerConfigurer + .oidc(oidc -> + oidc.providerConfigurationEndpoint(providerConfigurationEndpoint -> + providerConfigurationEndpoint + .providerConfigurationCustomizer(providerConfigurationCustomizer()))); + + RequestMatcher endpointsMatcher = authorizationServerConfigurer.getEndpointsMatcher(); + + http + .requestMatcher(endpointsMatcher) + .authorizeRequests(authorizeRequests -> + authorizeRequests.anyRequest().authenticated() + ) + .csrf(csrf -> csrf.ignoringRequestMatchers(endpointsMatcher)); + + return http.build(); + } + // @formatter:on + + private Consumer providerConfigurationCustomizer() { + return (providerConfiguration) -> + providerConfiguration.scope(OidcScopes.PROFILE).scope(OidcScopes.EMAIL); + } + + } + @Configuration @EnableWebSecurity static class AuthorizationServerConfigurationWithClientRegistrationEnabled extends AuthorizationServerConfiguration { @@ -158,4 +274,74 @@ public class OidcProviderConfigurationTests { } + @EnableWebSecurity + @Import(OAuth2AuthorizationServerConfiguration.class) + static class AuthorizationServerConfigurationWithInvalidIssuerUrl extends AuthorizationServerConfiguration { + + @Bean + AuthorizationServerSettings authorizationServerSettings() { + return AuthorizationServerSettings.builder().issuer("urn:example").build(); + } + } + + @EnableWebSecurity + @Import(OAuth2AuthorizationServerConfiguration.class) + static class AuthorizationServerConfigurationWithInvalidIssuerUri extends AuthorizationServerConfiguration { + + @Bean + AuthorizationServerSettings authorizationServerSettings() { + return AuthorizationServerSettings.builder().issuer("https://not a valid uri").build(); + } + } + + @EnableWebSecurity + @Import(OAuth2AuthorizationServerConfiguration.class) + static class AuthorizationServerConfigurationWithIssuerQuery extends AuthorizationServerConfiguration { + + @Bean + AuthorizationServerSettings authorizationServerSettings() { + return AuthorizationServerSettings.builder().issuer(ISSUER_URL + "?param=value").build(); + } + } + + @EnableWebSecurity + @Import(OAuth2AuthorizationServerConfiguration.class) + static class AuthorizationServerConfigurationWithIssuerFragment extends AuthorizationServerConfiguration { + + @Bean + AuthorizationServerSettings authorizationServerSettings() { + return AuthorizationServerSettings.builder().issuer(ISSUER_URL + "#fragment").build(); + } + } + + @EnableWebSecurity + @Import(OAuth2AuthorizationServerConfiguration.class) + static class AuthorizationServerConfigurationWithIssuerQueryAndFragment extends AuthorizationServerConfiguration { + + @Bean + AuthorizationServerSettings authorizationServerSettings() { + return AuthorizationServerSettings.builder().issuer(ISSUER_URL + "?param=value#fragment").build(); + } + } + + @EnableWebSecurity + @Import(OAuth2AuthorizationServerConfiguration.class) + static class AuthorizationServerConfigurationWithIssuerEmptyQuery extends AuthorizationServerConfiguration { + + @Bean + AuthorizationServerSettings authorizationServerSettings() { + return AuthorizationServerSettings.builder().issuer(ISSUER_URL + "?").build(); + } + } + + @EnableWebSecurity + @Import(OAuth2AuthorizationServerConfiguration.class) + static class AuthorizationServerConfigurationWithIssuerEmptyFragment extends AuthorizationServerConfiguration { + + @Bean + AuthorizationServerSettings authorizationServerSettings() { + return AuthorizationServerSettings.builder().issuer(ISSUER_URL + "#").build(); + } + } + } diff --git a/oauth2-authorization-server/src/test/java/org/springframework/security/oauth2/server/authorization/config/annotation/web/configurers/OidcTests.java b/oauth2-authorization-server/src/test/java/org/springframework/security/oauth2/server/authorization/config/annotation/web/configurers/OidcTests.java index cf530093..e74b9c8a 100644 --- a/oauth2-authorization-server/src/test/java/org/springframework/security/oauth2/server/authorization/config/annotation/web/configurers/OidcTests.java +++ b/oauth2-authorization-server/src/test/java/org/springframework/security/oauth2/server/authorization/config/annotation/web/configurers/OidcTests.java @@ -24,7 +24,6 @@ import java.util.Base64; import java.util.HashSet; import java.util.List; import java.util.Set; -import java.util.function.Consumer; import com.nimbusds.jose.jwk.JWKSet; import com.nimbusds.jose.jwk.source.JWKSource; @@ -37,7 +36,6 @@ import org.junit.Test; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.context.annotation.Bean; -import org.springframework.context.annotation.Configuration; import org.springframework.context.annotation.Import; import org.springframework.http.HttpHeaders; import org.springframework.http.HttpStatus; @@ -71,7 +69,6 @@ import org.springframework.security.oauth2.jwt.NimbusJwtEncoder; import org.springframework.security.oauth2.server.authorization.JdbcOAuth2AuthorizationService; import org.springframework.security.oauth2.server.authorization.OAuth2Authorization; import org.springframework.security.oauth2.server.authorization.OAuth2AuthorizationCode; -import org.springframework.security.oauth2.server.authorization.OAuth2AuthorizationServerMetadataClaimNames; import org.springframework.security.oauth2.server.authorization.OAuth2AuthorizationService; import org.springframework.security.oauth2.server.authorization.OAuth2TokenType; import org.springframework.security.oauth2.server.authorization.TestOAuth2Authorizations; @@ -82,8 +79,6 @@ import org.springframework.security.oauth2.server.authorization.client.Registere import org.springframework.security.oauth2.server.authorization.client.TestRegisteredClients; import org.springframework.security.oauth2.server.authorization.config.annotation.web.configuration.OAuth2AuthorizationServerConfiguration; import org.springframework.security.oauth2.server.authorization.jackson2.TestingAuthenticationTokenMixin; -import org.springframework.security.oauth2.server.authorization.oidc.OidcProviderConfiguration; -import org.springframework.security.oauth2.server.authorization.settings.AuthorizationServerSettings; import org.springframework.security.oauth2.server.authorization.test.SpringTestRule; import org.springframework.security.oauth2.server.authorization.token.DelegatingOAuth2TokenGenerator; import org.springframework.security.oauth2.server.authorization.token.JwtEncodingContext; @@ -103,9 +98,7 @@ import org.springframework.web.util.UriComponents; import org.springframework.web.util.UriComponentsBuilder; import static org.assertj.core.api.Assertions.assertThat; -import static org.assertj.core.api.Assertions.assertThatThrownBy; import static org.hamcrest.CoreMatchers.containsString; -import static org.hamcrest.CoreMatchers.hasItems; import static org.mockito.ArgumentMatchers.any; import static org.mockito.Mockito.spy; import static org.mockito.Mockito.times; @@ -126,8 +119,6 @@ import static org.springframework.test.web.servlet.result.MockMvcResultMatchers. public class OidcTests { private static final String DEFAULT_AUTHORIZATION_ENDPOINT_URI = "/oauth2/authorize"; private static final String DEFAULT_TOKEN_ENDPOINT_URI = "/oauth2/token"; - private static final String DEFAULT_OIDC_PROVIDER_CONFIGURATION_ENDPOINT_URI = "/.well-known/openid-configuration"; - private static final String ISSUER_URL = "https://example.com/issuer1"; private static final String AUTHORITIES_CLAIM = "authorities"; private static final OAuth2TokenType AUTHORIZATION_CODE_TOKEN_TYPE = new OAuth2TokenType(OAuth2ParameterNames.CODE); private static EmbeddedDatabase db; @@ -182,85 +173,6 @@ public class OidcTests { db.shutdown(); } - @Test - public void requestWhenConfigurationRequestAndIssuerSetThenReturnConfigurationResponse() throws Exception { - this.spring.register(AuthorizationServerConfigurationWithIssuer.class).autowire(); - - this.mvc.perform(get(DEFAULT_OIDC_PROVIDER_CONFIGURATION_ENDPOINT_URI)) - .andExpect(status().is2xxSuccessful()) - .andExpect(jsonPath("issuer").value(ISSUER_URL)); - } - - // gh-632 - @Test - public void requestWhenConfigurationRequestAndUserAuthenticatedThenReturnConfigurationResponse() throws Exception { - this.spring.register(AuthorizationServerConfiguration.class).autowire(); - - this.mvc.perform(get(DEFAULT_OIDC_PROVIDER_CONFIGURATION_ENDPOINT_URI) - .with(user("user"))) - .andExpect(status().is2xxSuccessful()); - } - - // gh-616 - @Test - public void requestWhenConfigurationRequestAndConfigurationCustomizerSetThenReturnCustomConfigurationResponse() throws Exception { - this.spring.register(AuthorizationServerConfigurationWithProviderConfigurationCustomizer.class).autowire(); - - this.mvc.perform(get(DEFAULT_OIDC_PROVIDER_CONFIGURATION_ENDPOINT_URI)) - .andExpect(status().is2xxSuccessful()) - .andExpect(jsonPath(OAuth2AuthorizationServerMetadataClaimNames.SCOPES_SUPPORTED, - hasItems(OidcScopes.OPENID, OidcScopes.PROFILE, OidcScopes.EMAIL))); - } - - @Test - public void loadContextWhenIssuerNotValidUrlThenThrowException() { - assertThatThrownBy( - () -> this.spring.register(AuthorizationServerConfigurationWithInvalidIssuerUrl.class).autowire() - ); - } - - @Test - public void loadContextWhenIssuerNotValidUriThenThrowException() { - assertThatThrownBy( - () -> this.spring.register(AuthorizationServerConfigurationWithInvalidIssuerUri.class).autowire() - ); - } - - @Test - public void loadContextWhenIssuerWithQueryThenThrowException() { - assertThatThrownBy( - () -> this.spring.register(AuthorizationServerConfigurationWithIssuerQuery.class).autowire() - ); - } - - @Test - public void loadContextWhenIssuerWithFragmentThenThrowException() { - assertThatThrownBy( - () -> this.spring.register(AuthorizationServerConfigurationWithIssuerFragment.class).autowire() - ); - } - - @Test - public void loadContextWhenIssuerWithQueryAndFragmentThenThrowException() { - assertThatThrownBy( - () -> this.spring.register(AuthorizationServerConfigurationWithIssuerQueryAndFragment.class).autowire() - ); - } - - @Test - public void loadContextWhenIssuerWithEmptyQueryThenThrowException() { - assertThatThrownBy( - () -> this.spring.register(AuthorizationServerConfigurationWithIssuerEmptyQuery.class).autowire() - ); - } - - @Test - public void loadContextWhenIssuerWithEmptyFragmentThenThrowException() { - assertThatThrownBy( - () -> this.spring.register(AuthorizationServerConfigurationWithIssuerEmptyFragment.class).autowire() - ); - } - @Test public void requestWhenAuthenticationRequestThenTokenResponseIncludesIdToken() throws Exception { this.spring.register(AuthorizationServerConfiguration.class).autowire(); @@ -480,121 +392,4 @@ public class OidcTests { } - @Configuration - @EnableWebSecurity - static class AuthorizationServerConfigurationWithProviderConfigurationCustomizer extends AuthorizationServerConfiguration { - - // @formatter:off - @Bean - public SecurityFilterChain authorizationServerSecurityFilterChain(HttpSecurity http) throws Exception { - OAuth2AuthorizationServerConfigurer authorizationServerConfigurer = - new OAuth2AuthorizationServerConfigurer(); - http.apply(authorizationServerConfigurer); - - authorizationServerConfigurer - .oidc(oidc -> - oidc.providerConfigurationEndpoint(providerConfigurationEndpoint -> - providerConfigurationEndpoint - .providerConfigurationCustomizer(providerConfigurationCustomizer()))); - - RequestMatcher endpointsMatcher = authorizationServerConfigurer.getEndpointsMatcher(); - - http - .requestMatcher(endpointsMatcher) - .authorizeRequests(authorizeRequests -> - authorizeRequests.anyRequest().authenticated() - ) - .csrf(csrf -> csrf.ignoringRequestMatchers(endpointsMatcher)); - - return http.build(); - } - // @formatter:on - - private Consumer providerConfigurationCustomizer() { - return (providerConfiguration) -> - providerConfiguration.scope(OidcScopes.PROFILE).scope(OidcScopes.EMAIL); - } - - } - - @EnableWebSecurity - @Import(OAuth2AuthorizationServerConfiguration.class) - static class AuthorizationServerConfigurationWithIssuer extends AuthorizationServerConfiguration { - - @Bean - AuthorizationServerSettings authorizationServerSettings() { - return AuthorizationServerSettings.builder().issuer(ISSUER_URL).build(); - } - } - - @EnableWebSecurity - @Import(OAuth2AuthorizationServerConfiguration.class) - static class AuthorizationServerConfigurationWithInvalidIssuerUrl extends AuthorizationServerConfiguration { - - @Bean - AuthorizationServerSettings authorizationServerSettings() { - return AuthorizationServerSettings.builder().issuer("urn:example").build(); - } - } - - @EnableWebSecurity - @Import(OAuth2AuthorizationServerConfiguration.class) - static class AuthorizationServerConfigurationWithInvalidIssuerUri extends AuthorizationServerConfiguration { - - @Bean - AuthorizationServerSettings authorizationServerSettings() { - return AuthorizationServerSettings.builder().issuer("https://not a valid uri").build(); - } - } - - @EnableWebSecurity - @Import(OAuth2AuthorizationServerConfiguration.class) - static class AuthorizationServerConfigurationWithIssuerQuery extends AuthorizationServerConfiguration { - - @Bean - AuthorizationServerSettings authorizationServerSettings() { - return AuthorizationServerSettings.builder().issuer(ISSUER_URL + "?param=value").build(); - } - } - - @EnableWebSecurity - @Import(OAuth2AuthorizationServerConfiguration.class) - static class AuthorizationServerConfigurationWithIssuerFragment extends AuthorizationServerConfiguration { - - @Bean - AuthorizationServerSettings authorizationServerSettings() { - return AuthorizationServerSettings.builder().issuer(ISSUER_URL + "#fragment").build(); - } - } - - @EnableWebSecurity - @Import(OAuth2AuthorizationServerConfiguration.class) - static class AuthorizationServerConfigurationWithIssuerQueryAndFragment extends AuthorizationServerConfiguration { - - @Bean - AuthorizationServerSettings authorizationServerSettings() { - return AuthorizationServerSettings.builder().issuer(ISSUER_URL + "?param=value#fragment").build(); - } - } - - @EnableWebSecurity - @Import(OAuth2AuthorizationServerConfiguration.class) - static class AuthorizationServerConfigurationWithIssuerEmptyQuery extends AuthorizationServerConfiguration { - - @Bean - AuthorizationServerSettings authorizationServerSettings() { - return AuthorizationServerSettings.builder().issuer(ISSUER_URL + "?").build(); - } - } - - @EnableWebSecurity - @Import(OAuth2AuthorizationServerConfiguration.class) - static class AuthorizationServerConfigurationWithIssuerEmptyFragment extends AuthorizationServerConfiguration { - - @Bean - AuthorizationServerSettings authorizationServerSettings() { - return AuthorizationServerSettings.builder().issuer(ISSUER_URL + "#").build(); - } - } - }