diff --git a/docs/src/docs/asciidoc/protocol-endpoints.adoc b/docs/src/docs/asciidoc/protocol-endpoints.adoc index 06841159..45b472e8 100644 --- a/docs/src/docs/asciidoc/protocol-endpoints.adoc +++ b/docs/src/docs/asciidoc/protocol-endpoints.adoc @@ -179,10 +179,31 @@ public SecurityFilterChain authorizationServerSecurityFilterChain(HttpSecurity h [[oauth2-authorization-server-metadata-endpoint]] == OAuth2 Authorization Server Metadata Endpoint -`OAuth2AuthorizationServerConfigurer` provides support for the https://datatracker.ietf.org/doc/html/rfc8414#section-3[OAuth2 Authorization Server Metadata endpoint]. +`OAuth2AuthorizationServerMetadataEndpointConfigurer` provides the ability to customize the https://datatracker.ietf.org/doc/html/rfc8414#section-3[OAuth2 Authorization Server Metadata endpoint]. +It defines an extension point that lets you customize the https://datatracker.ietf.org/doc/html/rfc8414#section-3.2[OAuth2 Authorization Server Metadata response]. -`OAuth2AuthorizationServerConfigurer` configures the `OAuth2AuthorizationServerMetadataEndpointFilter` and registers it with the OAuth2 authorization server `SecurityFilterChain` `@Bean`. -`OAuth2AuthorizationServerMetadataEndpointFilter` is the `Filter` that processes https://datatracker.ietf.org/doc/html/rfc8414#section-3.1[OAuth2 authorization server metadata requests] and returns the https://datatracker.ietf.org/doc/html/rfc8414#section-3.2[OAuth2AuthorizationServerMetadata response]. +`OAuth2AuthorizationServerMetadataEndpointConfigurer` provides the following configuration option: + +[source,java] +---- +@Bean +public SecurityFilterChain authorizationServerSecurityFilterChain(HttpSecurity http) throws Exception { + OAuth2AuthorizationServerConfigurer authorizationServerConfigurer = + new OAuth2AuthorizationServerConfigurer(); + http.apply(authorizationServerConfigurer); + + authorizationServerConfigurer + .authorizationServerMetadataEndpoint(authorizationServerMetadataEndpoint -> + authorizationServerMetadataEndpoint + .authorizationServerMetadataCustomizer(authorizationServerMetadataCustomizer)); <1> + + return http.build(); +} +---- +<1> `authorizationServerMetadataCustomizer()`: The `Consumer` providing access to the `OAuth2AuthorizationServerMetadata.Builder` allowing the ability to customize the claims of the Authorization Server's configuration. + +`OAuth2AuthorizationServerMetadataEndpointConfigurer` configures the `OAuth2AuthorizationServerMetadataEndpointFilter` and registers it with the OAuth2 authorization server `SecurityFilterChain` `@Bean`. +`OAuth2AuthorizationServerMetadataEndpointFilter` is the `Filter` that returns the https://datatracker.ietf.org/doc/html/rfc8414#section-3.2[OAuth2AuthorizationServerMetadata response]. [[jwk-set-endpoint]] == JWK Set Endpoint diff --git a/oauth2-authorization-server/src/main/java/org/springframework/security/oauth2/server/authorization/config/annotation/web/configurers/OAuth2AuthorizationServerConfigurer.java b/oauth2-authorization-server/src/main/java/org/springframework/security/oauth2/server/authorization/config/annotation/web/configurers/OAuth2AuthorizationServerConfigurer.java index 28919e88..8e9f6332 100644 --- a/oauth2-authorization-server/src/main/java/org/springframework/security/oauth2/server/authorization/config/annotation/web/configurers/OAuth2AuthorizationServerConfigurer.java +++ b/oauth2-authorization-server/src/main/java/org/springframework/security/oauth2/server/authorization/config/annotation/web/configurers/OAuth2AuthorizationServerConfigurer.java @@ -34,7 +34,6 @@ import org.springframework.security.oauth2.server.authorization.client.Registere import org.springframework.security.oauth2.server.authorization.settings.AuthorizationServerSettings; import org.springframework.security.oauth2.server.authorization.token.OAuth2TokenGenerator; import org.springframework.security.oauth2.server.authorization.web.NimbusJwkSetEndpointFilter; -import org.springframework.security.oauth2.server.authorization.web.OAuth2AuthorizationServerMetadataEndpointFilter; import org.springframework.security.web.authentication.HttpStatusEntryPoint; import org.springframework.security.web.authentication.preauth.AbstractPreAuthenticatedProcessingFilter; import org.springframework.security.web.context.SecurityContextHolderFilter; @@ -54,6 +53,7 @@ import org.springframework.util.Assert; * @since 0.0.1 * @see AbstractHttpConfigurer * @see OAuth2ClientAuthenticationConfigurer + * @see OAuth2AuthorizationServerMetadataEndpointConfigurer * @see OAuth2AuthorizationEndpointConfigurer * @see OAuth2TokenEndpointConfigurer * @see OAuth2TokenIntrospectionEndpointConfigurer @@ -63,22 +63,20 @@ import org.springframework.util.Assert; * @see OAuth2AuthorizationService * @see OAuth2AuthorizationConsentService * @see NimbusJwkSetEndpointFilter - * @see OAuth2AuthorizationServerMetadataEndpointFilter */ public final class OAuth2AuthorizationServerConfigurer extends AbstractHttpConfigurer { private final Map, AbstractOAuth2Configurer> configurers = createConfigurers(); - private RequestMatcher jwkSetEndpointMatcher; - private RequestMatcher authorizationServerMetadataEndpointMatcher; private final RequestMatcher endpointsMatcher = (request) -> - getRequestMatcher(OAuth2AuthorizationEndpointConfigurer.class).matches(request) || - getRequestMatcher(OAuth2TokenEndpointConfigurer.class).matches(request) || - getRequestMatcher(OAuth2TokenIntrospectionEndpointConfigurer.class).matches(request) || - getRequestMatcher(OAuth2TokenRevocationEndpointConfigurer.class).matches(request) || - getRequestMatcher(OidcConfigurer.class).matches(request) || - this.jwkSetEndpointMatcher.matches(request) || - this.authorizationServerMetadataEndpointMatcher.matches(request); + getRequestMatcher(OAuth2AuthorizationServerMetadataEndpointConfigurer.class).matches(request) || + getRequestMatcher(OAuth2AuthorizationEndpointConfigurer.class).matches(request) || + getRequestMatcher(OAuth2TokenEndpointConfigurer.class).matches(request) || + getRequestMatcher(OAuth2TokenIntrospectionEndpointConfigurer.class).matches(request) || + getRequestMatcher(OAuth2TokenRevocationEndpointConfigurer.class).matches(request) || + getRequestMatcher(OidcConfigurer.class).matches(request) || + this.jwkSetEndpointMatcher.matches(request); + private RequestMatcher jwkSetEndpointMatcher; /** * Sets the repository of registered clients. @@ -152,6 +150,18 @@ public final class OAuth2AuthorizationServerConfigurer return this; } + /** + * Configures the OAuth 2.0 Authorization Server Metadata Endpoint. + * + * @param authorizationServerMetadataEndpointCustomizer the {@link Customizer} providing access to the {@link OAuth2AuthorizationServerMetadataEndpointConfigurer} + * @return the {@link OAuth2AuthorizationServerConfigurer} for further configuration + * @since 0.4.0 + */ + public OAuth2AuthorizationServerConfigurer authorizationServerMetadataEndpoint(Customizer authorizationServerMetadataEndpointCustomizer) { + authorizationServerMetadataEndpointCustomizer.customize(getConfigurer(OAuth2AuthorizationServerMetadataEndpointConfigurer.class)); + return this; + } + /** * Configures the OAuth 2.0 Authorization Endpoint. * @@ -222,7 +232,9 @@ public final class OAuth2AuthorizationServerConfigurer public void init(HttpSecurity httpSecurity) { AuthorizationServerSettings authorizationServerSettings = OAuth2ConfigurerUtils.getAuthorizationServerSettings(httpSecurity); validateAuthorizationServerSettings(authorizationServerSettings); - initEndpointMatchers(authorizationServerSettings); + + this.jwkSetEndpointMatcher = new AntPathRequestMatcher( + authorizationServerSettings.getJwkSetEndpoint(), HttpMethod.GET.name()); this.configurers.values().forEach(configurer -> configurer.init(httpSecurity)); @@ -253,15 +265,12 @@ public final class OAuth2AuthorizationServerConfigurer jwkSource, authorizationServerSettings.getJwkSetEndpoint()); httpSecurity.addFilterBefore(postProcess(jwkSetEndpointFilter), AbstractPreAuthenticatedProcessingFilter.class); } - - OAuth2AuthorizationServerMetadataEndpointFilter authorizationServerMetadataEndpointFilter = - new OAuth2AuthorizationServerMetadataEndpointFilter(); - httpSecurity.addFilterBefore(postProcess(authorizationServerMetadataEndpointFilter), AbstractPreAuthenticatedProcessingFilter.class); } private Map, AbstractOAuth2Configurer> createConfigurers() { Map, AbstractOAuth2Configurer> configurers = new LinkedHashMap<>(); configurers.put(OAuth2ClientAuthenticationConfigurer.class, new OAuth2ClientAuthenticationConfigurer(this::postProcess)); + configurers.put(OAuth2AuthorizationServerMetadataEndpointConfigurer.class, new OAuth2AuthorizationServerMetadataEndpointConfigurer(this::postProcess)); configurers.put(OAuth2AuthorizationEndpointConfigurer.class, new OAuth2AuthorizationEndpointConfigurer(this::postProcess)); configurers.put(OAuth2TokenEndpointConfigurer.class, new OAuth2TokenEndpointConfigurer(this::postProcess)); configurers.put(OAuth2TokenIntrospectionEndpointConfigurer.class, new OAuth2TokenIntrospectionEndpointConfigurer(this::postProcess)); @@ -279,13 +288,6 @@ public final class OAuth2AuthorizationServerConfigurer return getConfigurer(configurerType).getRequestMatcher(); } - private void initEndpointMatchers(AuthorizationServerSettings authorizationServerSettings) { - this.jwkSetEndpointMatcher = new AntPathRequestMatcher( - authorizationServerSettings.getJwkSetEndpoint(), HttpMethod.GET.name()); - this.authorizationServerMetadataEndpointMatcher = new AntPathRequestMatcher( - "/.well-known/oauth-authorization-server", HttpMethod.GET.name()); - } - private static void validateAuthorizationServerSettings(AuthorizationServerSettings authorizationServerSettings) { if (authorizationServerSettings.getIssuer() != null) { URI issuerUri; diff --git a/oauth2-authorization-server/src/main/java/org/springframework/security/oauth2/server/authorization/config/annotation/web/configurers/OAuth2AuthorizationServerMetadataEndpointConfigurer.java b/oauth2-authorization-server/src/main/java/org/springframework/security/oauth2/server/authorization/config/annotation/web/configurers/OAuth2AuthorizationServerMetadataEndpointConfigurer.java new file mode 100644 index 00000000..25c4fb39 --- /dev/null +++ b/oauth2-authorization-server/src/main/java/org/springframework/security/oauth2/server/authorization/config/annotation/web/configurers/OAuth2AuthorizationServerMetadataEndpointConfigurer.java @@ -0,0 +1,108 @@ +/* + * Copyright 2020-2022 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.config.annotation.web.configurers; + +import java.util.function.Consumer; + +import org.springframework.http.HttpMethod; +import org.springframework.security.config.annotation.ObjectPostProcessor; +import org.springframework.security.config.annotation.web.builders.HttpSecurity; +import org.springframework.security.oauth2.server.authorization.OAuth2AuthorizationServerMetadata; +import org.springframework.security.oauth2.server.authorization.web.OAuth2AuthorizationServerMetadataEndpointFilter; +import org.springframework.security.web.authentication.preauth.AbstractPreAuthenticatedProcessingFilter; +import org.springframework.security.web.util.matcher.AntPathRequestMatcher; +import org.springframework.security.web.util.matcher.RequestMatcher; + +/** + * Configurer for the OAuth 2.0 Authorization Server Metadata Endpoint. + * + * @author Joe Grandja + * @since 0.4.0 + * @see OAuth2AuthorizationServerConfigurer#authorizationServerMetadataEndpoint + * @see OAuth2AuthorizationServerMetadataEndpointFilter + */ +public final class OAuth2AuthorizationServerMetadataEndpointConfigurer extends AbstractOAuth2Configurer { + private RequestMatcher requestMatcher; + private Consumer authorizationServerMetadataCustomizer; + private Consumer defaultAuthorizationServerMetadataCustomizer; + + /** + * Restrict for internal use only. + */ + OAuth2AuthorizationServerMetadataEndpointConfigurer(ObjectPostProcessor objectPostProcessor) { + super(objectPostProcessor); + } + + /** + * Sets the {@code Consumer} providing access to the {@link OAuth2AuthorizationServerMetadata.Builder} + * allowing the ability to customize the claims of the Authorization Server's configuration. + * + * @param authorizationServerMetadataCustomizer the {@code Consumer} providing access to the {@link OAuth2AuthorizationServerMetadata.Builder} + * @return the {@link OAuth2AuthorizationServerMetadataEndpointConfigurer} for further configuration + */ + public OAuth2AuthorizationServerMetadataEndpointConfigurer authorizationServerMetadataCustomizer( + Consumer authorizationServerMetadataCustomizer) { + this.authorizationServerMetadataCustomizer = authorizationServerMetadataCustomizer; + return this; + } + + void addDefaultAuthorizationServerMetadataCustomizer( + Consumer defaultAuthorizationServerMetadataCustomizer) { + this.defaultAuthorizationServerMetadataCustomizer = + this.defaultAuthorizationServerMetadataCustomizer == null ? + defaultAuthorizationServerMetadataCustomizer : + this.defaultAuthorizationServerMetadataCustomizer.andThen(defaultAuthorizationServerMetadataCustomizer); + } + + @Override + void init(HttpSecurity httpSecurity) { + this.requestMatcher = new AntPathRequestMatcher( + "/.well-known/oauth-authorization-server", HttpMethod.GET.name()); + } + + @Override + void configure(HttpSecurity httpSecurity) { + OAuth2AuthorizationServerMetadataEndpointFilter authorizationServerMetadataEndpointFilter = + new OAuth2AuthorizationServerMetadataEndpointFilter(); + Consumer authorizationServerMetadataCustomizer = getAuthorizationServerMetadataCustomizer(); + if (authorizationServerMetadataCustomizer != null) { + authorizationServerMetadataEndpointFilter.setAuthorizationServerMetadataCustomizer(authorizationServerMetadataCustomizer); + } + httpSecurity.addFilterBefore(postProcess(authorizationServerMetadataEndpointFilter), AbstractPreAuthenticatedProcessingFilter.class); + } + + private Consumer getAuthorizationServerMetadataCustomizer() { + Consumer authorizationServerMetadataCustomizer = null; + if (this.defaultAuthorizationServerMetadataCustomizer != null || this.authorizationServerMetadataCustomizer != null) { + if (this.defaultAuthorizationServerMetadataCustomizer != null) { + authorizationServerMetadataCustomizer = this.defaultAuthorizationServerMetadataCustomizer; + } + if (this.authorizationServerMetadataCustomizer != null) { + authorizationServerMetadataCustomizer = + authorizationServerMetadataCustomizer == null ? + this.authorizationServerMetadataCustomizer : + authorizationServerMetadataCustomizer.andThen(this.authorizationServerMetadataCustomizer); + } + } + return authorizationServerMetadataCustomizer; + } + + @Override + RequestMatcher getRequestMatcher() { + return this.requestMatcher; + } + +} diff --git a/oauth2-authorization-server/src/main/java/org/springframework/security/oauth2/server/authorization/web/OAuth2AuthorizationServerMetadataEndpointFilter.java b/oauth2-authorization-server/src/main/java/org/springframework/security/oauth2/server/authorization/web/OAuth2AuthorizationServerMetadataEndpointFilter.java index 6823b026..c988d56e 100644 --- a/oauth2-authorization-server/src/main/java/org/springframework/security/oauth2/server/authorization/web/OAuth2AuthorizationServerMetadataEndpointFilter.java +++ b/oauth2-authorization-server/src/main/java/org/springframework/security/oauth2/server/authorization/web/OAuth2AuthorizationServerMetadataEndpointFilter.java @@ -37,6 +37,7 @@ import org.springframework.security.oauth2.server.authorization.http.converter.O import org.springframework.security.oauth2.server.authorization.settings.AuthorizationServerSettings; import org.springframework.security.web.util.matcher.AntPathRequestMatcher; import org.springframework.security.web.util.matcher.RequestMatcher; +import org.springframework.util.Assert; import org.springframework.web.filter.OncePerRequestFilter; import org.springframework.web.util.UriComponentsBuilder; @@ -44,6 +45,7 @@ import org.springframework.web.util.UriComponentsBuilder; * A {@code Filter} that processes OAuth 2.0 Authorization Server Metadata Requests. * * @author Daniel Garnier-Moiroux + * @author Joe Grandja * @since 0.1.1 * @see OAuth2AuthorizationServerMetadata * @see AuthorizationServerSettings @@ -60,6 +62,19 @@ public final class OAuth2AuthorizationServerMetadataEndpointFilter extends OnceP HttpMethod.GET.name()); private final OAuth2AuthorizationServerMetadataHttpMessageConverter authorizationServerMetadataHttpMessageConverter = new OAuth2AuthorizationServerMetadataHttpMessageConverter(); + private Consumer authorizationServerMetadataCustomizer = (authorizationServerMetadata) -> {}; + + /** + * Sets the {@code Consumer} providing access to the {@link OAuth2AuthorizationServerMetadata.Builder} + * allowing the ability to customize the claims of the Authorization Server's configuration. + * + * @param authorizationServerMetadataCustomizer the {@code Consumer} providing access to the {@link OAuth2AuthorizationServerMetadata.Builder} + * @since 0.4.0 + */ + public void setAuthorizationServerMetadataCustomizer(Consumer authorizationServerMetadataCustomizer) { + Assert.notNull(authorizationServerMetadataCustomizer, "authorizationServerMetadataCustomizer cannot be null"); + this.authorizationServerMetadataCustomizer = authorizationServerMetadataCustomizer; + } @Override protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain filterChain) @@ -74,7 +89,7 @@ public final class OAuth2AuthorizationServerMetadataEndpointFilter extends OnceP String issuer = authorizationServerContext.getIssuer(); AuthorizationServerSettings authorizationServerSettings = authorizationServerContext.getAuthorizationServerSettings(); - OAuth2AuthorizationServerMetadata authorizationServerMetadata = OAuth2AuthorizationServerMetadata.builder() + OAuth2AuthorizationServerMetadata.Builder authorizationServerMetadata = OAuth2AuthorizationServerMetadata.builder() .issuer(issuer) .authorizationEndpoint(asUrl(issuer, authorizationServerSettings.getAuthorizationEndpoint())) .tokenEndpoint(asUrl(issuer, authorizationServerSettings.getTokenEndpoint())) @@ -88,12 +103,13 @@ public final class OAuth2AuthorizationServerMetadataEndpointFilter extends OnceP .tokenRevocationEndpointAuthenticationMethods(clientAuthenticationMethods()) .tokenIntrospectionEndpoint(asUrl(issuer, authorizationServerSettings.getTokenIntrospectionEndpoint())) .tokenIntrospectionEndpointAuthenticationMethods(clientAuthenticationMethods()) - .codeChallengeMethod("S256") - .build(); + .codeChallengeMethod("S256"); + + this.authorizationServerMetadataCustomizer.accept(authorizationServerMetadata); ServletServerHttpResponse httpResponse = new ServletServerHttpResponse(response); this.authorizationServerMetadataHttpMessageConverter.write( - authorizationServerMetadata, MediaType.APPLICATION_JSON, httpResponse); + authorizationServerMetadata.build(), MediaType.APPLICATION_JSON, httpResponse); } private static Consumer> clientAuthenticationMethods() { diff --git a/oauth2-authorization-server/src/test/java/org/springframework/security/oauth2/server/authorization/config/annotation/web/configurers/OAuth2AuthorizationServerMetadataTests.java b/oauth2-authorization-server/src/test/java/org/springframework/security/oauth2/server/authorization/config/annotation/web/configurers/OAuth2AuthorizationServerMetadataTests.java index 7750a77d..5f2449e4 100644 --- a/oauth2-authorization-server/src/test/java/org/springframework/security/oauth2/server/authorization/config/annotation/web/configurers/OAuth2AuthorizationServerMetadataTests.java +++ b/oauth2-authorization-server/src/test/java/org/springframework/security/oauth2/server/authorization/config/annotation/web/configurers/OAuth2AuthorizationServerMetadataTests.java @@ -15,6 +15,8 @@ */ package org.springframework.security.oauth2.server.authorization.config.annotation.web.configurers; +import java.util.function.Consumer; + import com.nimbusds.jose.jwk.JWKSet; import com.nimbusds.jose.jwk.source.JWKSource; import com.nimbusds.jose.proc.SecurityContext; @@ -26,14 +28,18 @@ 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.jdbc.core.JdbcOperations; import org.springframework.jdbc.core.JdbcTemplate; import org.springframework.jdbc.datasource.embedded.EmbeddedDatabase; import org.springframework.jdbc.datasource.embedded.EmbeddedDatabaseBuilder; import org.springframework.jdbc.datasource.embedded.EmbeddedDatabaseType; +import org.springframework.security.config.annotation.web.builders.HttpSecurity; import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity; import org.springframework.security.oauth2.jose.TestJwks; +import org.springframework.security.oauth2.server.authorization.OAuth2AuthorizationServerMetadata; +import org.springframework.security.oauth2.server.authorization.OAuth2AuthorizationServerMetadataClaimNames; import org.springframework.security.oauth2.server.authorization.client.JdbcRegisteredClientRepository; import org.springframework.security.oauth2.server.authorization.client.RegisteredClient; import org.springframework.security.oauth2.server.authorization.client.RegisteredClientRepository; @@ -41,8 +47,11 @@ import org.springframework.security.oauth2.server.authorization.client.TestRegis import org.springframework.security.oauth2.server.authorization.config.annotation.web.configuration.OAuth2AuthorizationServerConfiguration; 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 static org.hamcrest.CoreMatchers.hasItems; 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; @@ -111,6 +120,17 @@ public class OAuth2AuthorizationServerMetadataTests { .andReturn(); } + // gh-616 + @Test + public void requestWhenAuthorizationServerMetadataRequestAndMetadataCustomizerSetThenReturnCustomMetadataResponse() throws Exception { + this.spring.register(AuthorizationServerConfigurationWithMetadataCustomizer.class).autowire(); + + this.mvc.perform(get(DEFAULT_OAUTH2_AUTHORIZATION_SERVER_METADATA_ENDPOINT_URI)) + .andExpect(status().is2xxSuccessful()) + .andExpect(jsonPath(OAuth2AuthorizationServerMetadataClaimNames.SCOPES_SUPPORTED, + hasItems("scope1", "scope2"))); + } + @EnableWebSecurity @Import(OAuth2AuthorizationServerConfiguration.class) static class AuthorizationServerConfiguration { @@ -139,6 +159,42 @@ public class OAuth2AuthorizationServerMetadataTests { } } + @Configuration + @EnableWebSecurity + static class AuthorizationServerConfigurationWithMetadataCustomizer extends AuthorizationServerConfiguration { + + // @formatter:off + @Bean + public SecurityFilterChain authorizationServerSecurityFilterChain(HttpSecurity http) throws Exception { + OAuth2AuthorizationServerConfigurer authorizationServerConfigurer = + new OAuth2AuthorizationServerConfigurer(); + http.apply(authorizationServerConfigurer); + + authorizationServerConfigurer + .authorizationServerMetadataEndpoint(authorizationServerMetadataEndpoint -> + authorizationServerMetadataEndpoint + .authorizationServerMetadataCustomizer(authorizationServerMetadataCustomizer())); + + RequestMatcher endpointsMatcher = authorizationServerConfigurer.getEndpointsMatcher(); + + http + .requestMatcher(endpointsMatcher) + .authorizeRequests(authorizeRequests -> + authorizeRequests.anyRequest().authenticated() + ) + .csrf(csrf -> csrf.ignoringRequestMatchers(endpointsMatcher)); + + return http.build(); + } + // @formatter:on + + private Consumer authorizationServerMetadataCustomizer() { + return (authorizationServerMetadata) -> + authorizationServerMetadata.scope("scope1").scope("scope2"); + } + + } + @EnableWebSecurity @Import(OAuth2AuthorizationServerConfiguration.class) static class AuthorizationServerConfigurationWithIssuerNotSet extends AuthorizationServerConfiguration { diff --git a/oauth2-authorization-server/src/test/java/org/springframework/security/oauth2/server/authorization/web/OAuth2AuthorizationServerMetadataEndpointFilterTests.java b/oauth2-authorization-server/src/test/java/org/springframework/security/oauth2/server/authorization/web/OAuth2AuthorizationServerMetadataEndpointFilterTests.java index 43bb4edd..ff73c5aa 100644 --- a/oauth2-authorization-server/src/test/java/org/springframework/security/oauth2/server/authorization/web/OAuth2AuthorizationServerMetadataEndpointFilterTests.java +++ b/oauth2-authorization-server/src/test/java/org/springframework/security/oauth2/server/authorization/web/OAuth2AuthorizationServerMetadataEndpointFilterTests.java @@ -31,6 +31,7 @@ import org.springframework.security.oauth2.server.authorization.settings.Authori import static org.assertj.core.api.Assertions.assertThat; import static org.assertj.core.api.Assertions.assertThatIllegalArgumentException; +import static org.assertj.core.api.Assertions.assertThatThrownBy; import static org.mockito.ArgumentMatchers.any; import static org.mockito.Mockito.mock; import static org.mockito.Mockito.verify; @@ -40,9 +41,11 @@ import static org.mockito.Mockito.verifyNoInteractions; * Tests for {@link OAuth2AuthorizationServerMetadataEndpointFilter}. * * @author Daniel Garnier-Moiroux + * @author Joe Grandja */ public class OAuth2AuthorizationServerMetadataEndpointFilterTests { private static final String DEFAULT_OAUTH2_AUTHORIZATION_SERVER_METADATA_ENDPOINT_URI = "/.well-known/oauth-authorization-server"; + private final OAuth2AuthorizationServerMetadataEndpointFilter filter = new OAuth2AuthorizationServerMetadataEndpointFilter(); @After public void cleanup() { @@ -50,39 +53,34 @@ public class OAuth2AuthorizationServerMetadataEndpointFilterTests { } @Test - public void doFilterWhenNotAuthorizationServerMetadataRequestThenNotProcessed() throws Exception { - AuthorizationServerSettings authorizationServerSettings = AuthorizationServerSettings.builder() - .issuer("https://example.com") - .build(); - AuthorizationServerContextHolder.setContext(new TestAuthorizationServerContext(authorizationServerSettings, null)); - OAuth2AuthorizationServerMetadataEndpointFilter filter = new OAuth2AuthorizationServerMetadataEndpointFilter(); + public void setAuthorizationServerMetadataCustomizerWhenNullThenThrowIllegalArgumentException() { + assertThatThrownBy(() -> this.filter.setAuthorizationServerMetadataCustomizer(null)) + .isInstanceOf(IllegalArgumentException.class) + .hasMessage("authorizationServerMetadataCustomizer cannot be null"); + } + @Test + public void doFilterWhenNotAuthorizationServerMetadataRequestThenNotProcessed() throws Exception { String requestUri = "/path"; 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 doFilterWhenAuthorizationServerMetadataRequestPostThenNotProcessed() throws Exception { - AuthorizationServerSettings authorizationServerSettings = AuthorizationServerSettings.builder() - .issuer("https://example.com") - .build(); - AuthorizationServerContextHolder.setContext(new TestAuthorizationServerContext(authorizationServerSettings, null)); - OAuth2AuthorizationServerMetadataEndpointFilter filter = new OAuth2AuthorizationServerMetadataEndpointFilter(); - String requestUri = DEFAULT_OAUTH2_AUTHORIZATION_SERVER_METADATA_ENDPOINT_URI; 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)); } @@ -105,7 +103,6 @@ public class OAuth2AuthorizationServerMetadataEndpointFilterTests { .tokenIntrospectionEndpoint(tokenIntrospectionEndpoint) .build(); AuthorizationServerContextHolder.setContext(new TestAuthorizationServerContext(authorizationServerSettings, null)); - OAuth2AuthorizationServerMetadataEndpointFilter filter = new OAuth2AuthorizationServerMetadataEndpointFilter(); String requestUri = DEFAULT_OAUTH2_AUTHORIZATION_SERVER_METADATA_ENDPOINT_URI; MockHttpServletRequest request = new MockHttpServletRequest("GET", requestUri); @@ -113,7 +110,7 @@ public class OAuth2AuthorizationServerMetadataEndpointFilterTests { MockHttpServletResponse response = new MockHttpServletResponse(); FilterChain filterChain = mock(FilterChain.class); - filter.doFilter(request, response, filterChain); + this.filter.doFilter(request, response, filterChain); verifyNoInteractions(filterChain); @@ -139,7 +136,6 @@ public class OAuth2AuthorizationServerMetadataEndpointFilterTests { .issuer("https://this is an invalid URL") .build(); AuthorizationServerContextHolder.setContext(new TestAuthorizationServerContext(authorizationServerSettings, null)); - OAuth2AuthorizationServerMetadataEndpointFilter filter = new OAuth2AuthorizationServerMetadataEndpointFilter(); String requestUri = DEFAULT_OAUTH2_AUTHORIZATION_SERVER_METADATA_ENDPOINT_URI; MockHttpServletRequest request = new MockHttpServletRequest("GET", requestUri); @@ -149,7 +145,7 @@ public class OAuth2AuthorizationServerMetadataEndpointFilterTests { assertThatIllegalArgumentException() - .isThrownBy(() -> filter.doFilter(request, response, filterChain)) + .isThrownBy(() -> this.filter.doFilter(request, response, filterChain)) .withMessage("issuer must be a valid URL"); }