diff --git a/oauth2-authorization-server/src/main/java/org/springframework/security/config/annotation/web/configuration/OAuth2AuthorizationServerConfiguration.java b/oauth2-authorization-server/src/main/java/org/springframework/security/config/annotation/web/configuration/OAuth2AuthorizationServerConfiguration.java index 6591dcbb..288dec25 100644 --- a/oauth2-authorization-server/src/main/java/org/springframework/security/config/annotation/web/configuration/OAuth2AuthorizationServerConfiguration.java +++ b/oauth2-authorization-server/src/main/java/org/springframework/security/config/annotation/web/configuration/OAuth2AuthorizationServerConfiguration.java @@ -32,7 +32,6 @@ import org.springframework.core.Ordered; import org.springframework.core.annotation.Order; import org.springframework.security.config.annotation.web.builders.HttpSecurity; import org.springframework.security.config.annotation.web.configurers.oauth2.server.authorization.OAuth2AuthorizationServerConfigurer; -import org.springframework.security.config.annotation.web.configurers.oauth2.server.resource.OAuth2ResourceServerConfigurer; import org.springframework.security.oauth2.jwt.JwtDecoder; import org.springframework.security.oauth2.jwt.NimbusJwtDecoder; import org.springframework.security.oauth2.server.authorization.config.ProviderSettings; @@ -69,12 +68,10 @@ public class OAuth2AuthorizationServerConfiguration { authorizeRequests.anyRequest().authenticated() ) .csrf(csrf -> csrf.ignoringRequestMatchers(endpointsMatcher)) - .oauth2ResourceServer(OAuth2ResourceServerConfigurer::jwt) .apply(authorizationServerConfigurer); } // @formatter:on - @Bean public static JwtDecoder jwtDecoder(JWKSource jwkSource) { Set jwsAlgs = new HashSet<>(); jwsAlgs.addAll(JWSAlgorithm.Family.RSA); diff --git a/oauth2-authorization-server/src/main/java/org/springframework/security/config/annotation/web/configurers/oauth2/server/authorization/AbstractOAuth2Configurer.java b/oauth2-authorization-server/src/main/java/org/springframework/security/config/annotation/web/configurers/oauth2/server/authorization/AbstractOAuth2Configurer.java index 325ecc20..fe2239c0 100644 --- a/oauth2-authorization-server/src/main/java/org/springframework/security/config/annotation/web/configurers/oauth2/server/authorization/AbstractOAuth2Configurer.java +++ b/oauth2-authorization-server/src/main/java/org/springframework/security/config/annotation/web/configurers/oauth2/server/authorization/AbstractOAuth2Configurer.java @@ -38,8 +38,12 @@ abstract class AbstractOAuth2Configurer { abstract RequestMatcher getRequestMatcher(); - protected T postProcess(T object) { + protected final T postProcess(T object) { return (T) this.objectPostProcessor.postProcess(object); } + protected final ObjectPostProcessor getObjectPostProcessor() { + return this.objectPostProcessor; + } + } diff --git a/oauth2-authorization-server/src/main/java/org/springframework/security/config/annotation/web/configurers/oauth2/server/authorization/OidcClientRegistrationEndpointConfigurer.java b/oauth2-authorization-server/src/main/java/org/springframework/security/config/annotation/web/configurers/oauth2/server/authorization/OidcClientRegistrationEndpointConfigurer.java new file mode 100644 index 00000000..dda93881 --- /dev/null +++ b/oauth2-authorization-server/src/main/java/org/springframework/security/config/annotation/web/configurers/oauth2/server/authorization/OidcClientRegistrationEndpointConfigurer.java @@ -0,0 +1,77 @@ +/* + * 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.config.annotation.web.configurers.oauth2.server.authorization; + +import org.springframework.http.HttpMethod; +import org.springframework.security.authentication.AuthenticationManager; +import org.springframework.security.config.annotation.ObjectPostProcessor; +import org.springframework.security.config.annotation.web.HttpSecurityBuilder; +import org.springframework.security.oauth2.server.authorization.config.ProviderSettings; +import org.springframework.security.oauth2.server.authorization.oidc.authentication.OidcClientRegistrationAuthenticationProvider; +import org.springframework.security.oauth2.server.authorization.oidc.web.OidcClientRegistrationEndpointFilter; +import org.springframework.security.web.access.intercept.FilterSecurityInterceptor; +import org.springframework.security.web.util.matcher.AntPathRequestMatcher; +import org.springframework.security.web.util.matcher.RequestMatcher; + +/** + * Configurer for OpenID Connect Dynamic Client Registration 1.0 Endpoint. + * + * @author Joe Grandja + * @since 0.2.0 + * @see OidcConfigurer#clientRegistrationEndpoint + * @see OidcClientRegistrationEndpointFilter + */ +public final class OidcClientRegistrationEndpointConfigurer extends AbstractOAuth2Configurer { + private RequestMatcher requestMatcher; + + /** + * Restrict for internal use only. + */ + OidcClientRegistrationEndpointConfigurer(ObjectPostProcessor objectPostProcessor) { + super(objectPostProcessor); + } + + @Override + > void init(B builder) { + ProviderSettings providerSettings = OAuth2ConfigurerUtils.getProviderSettings(builder); + this.requestMatcher = new AntPathRequestMatcher( + providerSettings.getOidcClientRegistrationEndpoint(), HttpMethod.POST.name()); + + OidcClientRegistrationAuthenticationProvider oidcClientRegistrationAuthenticationProvider = + new OidcClientRegistrationAuthenticationProvider( + OAuth2ConfigurerUtils.getRegisteredClientRepository(builder), + OAuth2ConfigurerUtils.getAuthorizationService(builder)); + builder.authenticationProvider(postProcess(oidcClientRegistrationAuthenticationProvider)); + } + + @Override + > void configure(B builder) { + AuthenticationManager authenticationManager = builder.getSharedObject(AuthenticationManager.class); + ProviderSettings providerSettings = OAuth2ConfigurerUtils.getProviderSettings(builder); + + OidcClientRegistrationEndpointFilter oidcClientRegistrationEndpointFilter = + new OidcClientRegistrationEndpointFilter( + authenticationManager, + providerSettings.getOidcClientRegistrationEndpoint()); + builder.addFilterAfter(postProcess(oidcClientRegistrationEndpointFilter), FilterSecurityInterceptor.class); + } + + @Override + RequestMatcher getRequestMatcher() { + return this.requestMatcher; + } + +} diff --git a/oauth2-authorization-server/src/main/java/org/springframework/security/config/annotation/web/configurers/oauth2/server/authorization/OidcConfigurer.java b/oauth2-authorization-server/src/main/java/org/springframework/security/config/annotation/web/configurers/oauth2/server/authorization/OidcConfigurer.java index 95359c43..71bbd323 100644 --- a/oauth2-authorization-server/src/main/java/org/springframework/security/config/annotation/web/configurers/oauth2/server/authorization/OidcConfigurer.java +++ b/oauth2-authorization-server/src/main/java/org/springframework/security/config/annotation/web/configurers/oauth2/server/authorization/OidcConfigurer.java @@ -19,14 +19,11 @@ import java.util.ArrayList; import java.util.List; import org.springframework.http.HttpMethod; -import org.springframework.security.authentication.AuthenticationManager; +import org.springframework.security.config.Customizer; import org.springframework.security.config.annotation.ObjectPostProcessor; import org.springframework.security.config.annotation.web.HttpSecurityBuilder; import org.springframework.security.oauth2.server.authorization.config.ProviderSettings; -import org.springframework.security.oauth2.server.authorization.oidc.authentication.OidcClientRegistrationAuthenticationProvider; -import org.springframework.security.oauth2.server.authorization.oidc.web.OidcClientRegistrationEndpointFilter; import org.springframework.security.oauth2.server.authorization.oidc.web.OidcProviderConfigurationEndpointFilter; -import org.springframework.security.web.access.intercept.FilterSecurityInterceptor; import org.springframework.security.web.authentication.preauth.AbstractPreAuthenticatedProcessingFilter; import org.springframework.security.web.util.matcher.AntPathRequestMatcher; import org.springframework.security.web.util.matcher.OrRequestMatcher; @@ -38,10 +35,11 @@ import org.springframework.security.web.util.matcher.RequestMatcher; * @author Joe Grandja * @since 0.2.0 * @see OAuth2AuthorizationServerConfigurer#oidc + * @see OidcClientRegistrationEndpointConfigurer * @see OidcProviderConfigurationEndpointFilter - * @see OidcClientRegistrationEndpointFilter */ public final class OidcConfigurer extends AbstractOAuth2Configurer { + private OidcClientRegistrationEndpointConfigurer clientRegistrationEndpointConfigurer; private RequestMatcher requestMatcher; /** @@ -51,47 +49,50 @@ public final class OidcConfigurer extends AbstractOAuth2Configurer { super(objectPostProcessor); } + /** + * Configures the OpenID Connect Dynamic Client Registration 1.0 Endpoint. + * + * @param clientRegistrationEndpointCustomizer the {@link Customizer} providing access to the {@link OidcClientRegistrationEndpointConfigurer} + * @return the {@link OidcConfigurer} for further configuration + */ + public OidcConfigurer clientRegistrationEndpoint(Customizer clientRegistrationEndpointCustomizer) { + if (this.clientRegistrationEndpointConfigurer == null) { + this.clientRegistrationEndpointConfigurer = new OidcClientRegistrationEndpointConfigurer(getObjectPostProcessor()); + } + clientRegistrationEndpointCustomizer.customize(this.clientRegistrationEndpointConfigurer); + return this; + } + @Override > void init(B builder) { + if (this.clientRegistrationEndpointConfigurer != null) { + this.clientRegistrationEndpointConfigurer.init(builder); + } + List requestMatchers = new ArrayList<>(); ProviderSettings providerSettings = OAuth2ConfigurerUtils.getProviderSettings(builder); if (providerSettings.getIssuer() != null) { - requestMatchers.add( - new AntPathRequestMatcher( - "/.well-known/openid-configuration", - HttpMethod.GET.name())); + requestMatchers.add(new AntPathRequestMatcher( + "/.well-known/openid-configuration", HttpMethod.GET.name())); } - requestMatchers.add( - new AntPathRequestMatcher( - providerSettings.getOidcClientRegistrationEndpoint(), - HttpMethod.POST.name())); - this.requestMatcher = new OrRequestMatcher(requestMatchers); - - // TODO Make OpenID Client Registration an "opt-in" feature - OidcClientRegistrationAuthenticationProvider oidcClientRegistrationAuthenticationProvider = - new OidcClientRegistrationAuthenticationProvider( - OAuth2ConfigurerUtils.getRegisteredClientRepository(builder), - OAuth2ConfigurerUtils.getAuthorizationService(builder)); - builder.authenticationProvider(postProcess(oidcClientRegistrationAuthenticationProvider)); + if (this.clientRegistrationEndpointConfigurer != null) { + requestMatchers.add(this.clientRegistrationEndpointConfigurer.getRequestMatcher()); + } + this.requestMatcher = !requestMatchers.isEmpty() ? new OrRequestMatcher(requestMatchers) : request -> false; } @Override > void configure(B builder) { + if (this.clientRegistrationEndpointConfigurer != null) { + this.clientRegistrationEndpointConfigurer.configure(builder); + } + ProviderSettings providerSettings = OAuth2ConfigurerUtils.getProviderSettings(builder); if (providerSettings.getIssuer() != null) { OidcProviderConfigurationEndpointFilter oidcProviderConfigurationEndpointFilter = new OidcProviderConfigurationEndpointFilter(providerSettings); builder.addFilterBefore(postProcess(oidcProviderConfigurationEndpointFilter), AbstractPreAuthenticatedProcessingFilter.class); } - - AuthenticationManager authenticationManager = builder.getSharedObject(AuthenticationManager.class); - - // TODO Make OpenID Client Registration an "opt-in" feature - OidcClientRegistrationEndpointFilter oidcClientRegistrationEndpointFilter = - new OidcClientRegistrationEndpointFilter( - authenticationManager, - providerSettings.getOidcClientRegistrationEndpoint()); - builder.addFilterAfter(postProcess(oidcClientRegistrationEndpointFilter), FilterSecurityInterceptor.class); } @Override diff --git a/oauth2-authorization-server/src/test/java/org/springframework/security/config/annotation/web/configurers/oauth2/server/authorization/OAuth2AuthorizationCodeGrantTests.java b/oauth2-authorization-server/src/test/java/org/springframework/security/config/annotation/web/configurers/oauth2/server/authorization/OAuth2AuthorizationCodeGrantTests.java index d582dc20..0c1fecb2 100644 --- a/oauth2-authorization-server/src/test/java/org/springframework/security/config/annotation/web/configurers/oauth2/server/authorization/OAuth2AuthorizationCodeGrantTests.java +++ b/oauth2-authorization-server/src/test/java/org/springframework/security/config/annotation/web/configurers/oauth2/server/authorization/OAuth2AuthorizationCodeGrantTests.java @@ -61,6 +61,7 @@ import org.springframework.security.core.GrantedAuthority; import org.springframework.security.crypto.password.NoOpPasswordEncoder; import org.springframework.security.crypto.password.PasswordEncoder; import org.springframework.security.oauth2.core.AuthorizationGrantType; +import org.springframework.security.oauth2.core.OAuth2AuthorizationCode; import org.springframework.security.oauth2.core.OAuth2TokenType; import org.springframework.security.oauth2.core.endpoint.OAuth2AccessTokenResponse; import org.springframework.security.oauth2.core.endpoint.OAuth2AuthorizationResponseType; @@ -76,7 +77,6 @@ import org.springframework.security.oauth2.server.authorization.JdbcOAuth2Author import org.springframework.security.oauth2.server.authorization.JdbcOAuth2AuthorizationService; import org.springframework.security.oauth2.server.authorization.JwtEncodingContext; import org.springframework.security.oauth2.server.authorization.OAuth2Authorization; -import org.springframework.security.oauth2.core.OAuth2AuthorizationCode; import org.springframework.security.oauth2.server.authorization.OAuth2AuthorizationConsentService; import org.springframework.security.oauth2.server.authorization.OAuth2AuthorizationService; import org.springframework.security.oauth2.server.authorization.OAuth2TokenCustomizer; @@ -601,6 +601,11 @@ public class OAuth2AuthorizationCodeGrantTests { return jwkSource; } + @Bean + JwtDecoder jwtDecoder(JWKSource jwkSource) { + return OAuth2AuthorizationServerConfiguration.jwtDecoder(jwkSource); + } + @Bean OAuth2TokenCustomizer jwtCustomizer() { return context -> { diff --git a/oauth2-authorization-server/src/test/java/org/springframework/security/config/annotation/web/configurers/oauth2/server/authorization/OidcClientRegistrationTests.java b/oauth2-authorization-server/src/test/java/org/springframework/security/config/annotation/web/configurers/oauth2/server/authorization/OidcClientRegistrationTests.java index 1035fc4b..865703d1 100644 --- a/oauth2-authorization-server/src/test/java/org/springframework/security/config/annotation/web/configurers/oauth2/server/authorization/OidcClientRegistrationTests.java +++ b/oauth2-authorization-server/src/test/java/org/springframework/security/config/annotation/web/configurers/oauth2/server/authorization/OidcClientRegistrationTests.java @@ -30,7 +30,6 @@ import org.junit.Test; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.context.annotation.Bean; -import org.springframework.context.annotation.Import; import org.springframework.http.HttpHeaders; import org.springframework.http.HttpStatus; import org.springframework.http.MediaType; @@ -43,8 +42,11 @@ import org.springframework.jdbc.datasource.embedded.EmbeddedDatabaseType; import org.springframework.mock.http.MockHttpOutputMessage; import org.springframework.mock.http.client.MockClientHttpResponse; import org.springframework.mock.web.MockHttpServletResponse; +import org.springframework.security.config.Customizer; +import org.springframework.security.config.annotation.web.builders.HttpSecurity; import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity; import org.springframework.security.config.annotation.web.configuration.OAuth2AuthorizationServerConfiguration; +import org.springframework.security.config.annotation.web.configurers.oauth2.server.resource.OAuth2ResourceServerConfigurer; import org.springframework.security.config.test.SpringTestRule; import org.springframework.security.crypto.password.NoOpPasswordEncoder; import org.springframework.security.crypto.password.PasswordEncoder; @@ -59,11 +61,14 @@ 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.jws.SignatureAlgorithm; +import org.springframework.security.oauth2.jwt.JwtDecoder; import org.springframework.security.oauth2.server.authorization.client.JdbcRegisteredClientRepository; import org.springframework.security.oauth2.server.authorization.client.JdbcRegisteredClientRepository.RegisteredClientParametersMapper; 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.web.SecurityFilterChain; +import org.springframework.security.web.util.matcher.RequestMatcher; import org.springframework.test.web.servlet.MockMvc; import org.springframework.test.web.servlet.MvcResult; @@ -223,9 +228,30 @@ public class OidcClientRegistrationTests { } @EnableWebSecurity - @Import(OAuth2AuthorizationServerConfiguration.class) static class AuthorizationServerConfiguration { + // @formatter:off + @Bean + public SecurityFilterChain authorizationServerSecurityFilterChain(HttpSecurity http) throws Exception { + OAuth2AuthorizationServerConfigurer authorizationServerConfigurer = + new OAuth2AuthorizationServerConfigurer<>(); + authorizationServerConfigurer + .oidc(oidc -> + oidc.clientRegistrationEndpoint(Customizer.withDefaults())); + RequestMatcher endpointsMatcher = authorizationServerConfigurer.getEndpointsMatcher(); + + http + .requestMatcher(endpointsMatcher) + .authorizeRequests(authorizeRequests -> + authorizeRequests.anyRequest().authenticated() + ) + .csrf(csrf -> csrf.ignoringRequestMatchers(endpointsMatcher)) + .oauth2ResourceServer(OAuth2ResourceServerConfigurer::jwt) + .apply(authorizationServerConfigurer); + return http.build(); + } + // @formatter:on + @Bean RegisteredClientRepository registeredClientRepository(JdbcOperations jdbcOperations, PasswordEncoder passwordEncoder) { RegisteredClient registeredClient = TestRegisteredClients.registeredClient().build(); @@ -247,10 +273,16 @@ public class OidcClientRegistrationTests { return jwkSource; } + @Bean + JwtDecoder jwtDecoder(JWKSource jwkSource) { + return OAuth2AuthorizationServerConfiguration.jwtDecoder(jwkSource); + } + @Bean PasswordEncoder passwordEncoder() { return NoOpPasswordEncoder.getInstance(); } } + } diff --git a/oauth2-authorization-server/src/test/java/org/springframework/security/config/annotation/web/configurers/oauth2/server/authorization/OidcTests.java b/oauth2-authorization-server/src/test/java/org/springframework/security/config/annotation/web/configurers/oauth2/server/authorization/OidcTests.java index 733d5417..60d91d20 100644 --- a/oauth2-authorization-server/src/test/java/org/springframework/security/config/annotation/web/configurers/oauth2/server/authorization/OidcTests.java +++ b/oauth2-authorization-server/src/test/java/org/springframework/security/config/annotation/web/configurers/oauth2/server/authorization/OidcTests.java @@ -56,6 +56,7 @@ import org.springframework.security.core.GrantedAuthority; import org.springframework.security.crypto.password.NoOpPasswordEncoder; import org.springframework.security.crypto.password.PasswordEncoder; import org.springframework.security.oauth2.core.AuthorizationGrantType; +import org.springframework.security.oauth2.core.OAuth2AuthorizationCode; import org.springframework.security.oauth2.core.OAuth2TokenType; import org.springframework.security.oauth2.core.endpoint.OAuth2AccessTokenResponse; import org.springframework.security.oauth2.core.endpoint.OAuth2AuthorizationResponseType; @@ -69,7 +70,6 @@ import org.springframework.security.oauth2.jwt.JwtDecoder; import org.springframework.security.oauth2.server.authorization.JdbcOAuth2AuthorizationService; import org.springframework.security.oauth2.server.authorization.JwtEncodingContext; import org.springframework.security.oauth2.server.authorization.OAuth2Authorization; -import org.springframework.security.oauth2.core.OAuth2AuthorizationCode; import org.springframework.security.oauth2.server.authorization.OAuth2AuthorizationService; import org.springframework.security.oauth2.server.authorization.OAuth2TokenCustomizer; import org.springframework.security.oauth2.server.authorization.client.JdbcRegisteredClientRepository; @@ -294,6 +294,11 @@ public class OidcTests { return jwkSource; } + @Bean + JwtDecoder jwtDecoder(JWKSource jwkSource) { + return OAuth2AuthorizationServerConfiguration.jwtDecoder(jwkSource); + } + @Bean OAuth2TokenCustomizer jwtCustomizer() { return context -> { diff --git a/samples/boot/oauth2-integration/authorizationserver/src/main/java/sample/config/AuthorizationServerConfig.java b/samples/boot/oauth2-integration/authorizationserver/src/main/java/sample/config/AuthorizationServerConfig.java index 39c75607..70e706d1 100644 --- a/samples/boot/oauth2-integration/authorizationserver/src/main/java/sample/config/AuthorizationServerConfig.java +++ b/samples/boot/oauth2-integration/authorizationserver/src/main/java/sample/config/AuthorizationServerConfig.java @@ -37,7 +37,6 @@ import org.springframework.security.config.annotation.web.configuration.OAuth2Au import org.springframework.security.oauth2.core.AuthorizationGrantType; import org.springframework.security.oauth2.core.ClientAuthenticationMethod; import org.springframework.security.oauth2.core.oidc.OidcScopes; -import org.springframework.security.oauth2.jwt.JwtDecoder; import org.springframework.security.oauth2.server.authorization.JdbcOAuth2AuthorizationConsentService; import org.springframework.security.oauth2.server.authorization.JdbcOAuth2AuthorizationService; import org.springframework.security.oauth2.server.authorization.OAuth2AuthorizationConsentService; @@ -106,11 +105,6 @@ public class AuthorizationServerConfig { return (jwkSelector, securityContext) -> jwkSelector.select(jwkSet); } - @Bean - public JwtDecoder jwtDecoder(JWKSource jwkSource) { - return OAuth2AuthorizationServerConfiguration.jwtDecoder(jwkSource); - } - @Bean public ProviderSettings providerSettings() { return ProviderSettings.builder().issuer("http://auth-server:9000").build();