Allow customizing Authorization Server Metadata Response

Closes gh-878
This commit is contained in:
Joe Grandja
2022-08-29 14:17:48 -04:00
parent 0994a1e1e1
commit 8043b8c949
6 changed files with 247 additions and 48 deletions

View File

@@ -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<OAuth2AuthorizationServerMetadata.Builder> authorizationServerMetadataCustomizer() {
return (authorizationServerMetadata) ->
authorizationServerMetadata.scope("scope1").scope("scope2");
}
}
@EnableWebSecurity
@Import(OAuth2AuthorizationServerConfiguration.class)
static class AuthorizationServerConfigurationWithIssuerNotSet extends AuthorizationServerConfiguration {

View File

@@ -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");
}