From 231396e6ba00c95e9792b134ea4fc456d68fdab0 Mon Sep 17 00:00:00 2001 From: Andy Wilkinson Date: Thu, 17 Apr 2025 12:17:31 +0100 Subject: [PATCH] Restructure OAuth2 client auto-configuration Previously, OAuth2 client auto-configuration was managed by a single class: - OAuth2ClientAutoConfiguration for servlet apps - ReactiveOAuth2ClientAutoConfiguration for reactive apps OAuth2ClientAutoConfiguration being for servlet apps meant that a blocking OAuth2 client was not availabile in a non-web application. The auto-configuration classes did two things: - Auto-configured beans that are specific to server-side web security that uses an OAuth2 client - Auto-configured OAuth2 client beans that may be used client- or server-side Combining these two things into a single auto-configuration class meant that you could not choose to use one or the other. For example, you may want to make use of an OAuth2 client in a web application without also using OAuth2 client-based web security. This commit restructures the auto-configuration to address these problems. There are now two auto-configurations for non-reactive apps: - OAuth2ClientAutoConfiguration - OAuth2ClientWebSecurityAutoConfiguration and two auto-configurations for reactive apps: - ReactiveOAuth2ClientAutoConfiguration - ReactiveOAuth2ClientWebSecurityAutoConfiguration This separation allows one to be used without the other. Furthermore, the conditions have been updated so that, for example, the blocking OAuth2 client is available in a non-web application. Closes gh-40997 Closes gh-44906 Co-authored-by: Moritz Halbritter --- ...anagementWebSecurityAutoConfiguration.java | 4 +- ...anagementWebSecurityAutoConfiguration.java | 6 +- .../client/OAuth2ClientAutoConfiguration.java | 56 +++++ ...n.java => OAuth2ClientConfigurations.java} | 47 ++-- ...ReactiveOAuth2ClientAutoConfiguration.java | 13 +- .../ReactiveOAuth2ClientConfigurations.java | 38 +--- ...th2ClientWebSecurityAutoConfiguration.java | 68 ++++++ .../OAuth2ClientAutoConfiguration.java | 16 +- ...h2ClientWebSecurityAutoConfiguration.java} | 28 +-- ...ot.autoconfigure.AutoConfiguration.imports | 4 +- ...toconfigure.AutoConfiguration.replacements | 1 + ...iveOAuth2ClientAutoConfigurationTests.java | 200 +++--------------- ...ientWebSecurityAutoConfigurationTests.java | 197 +++++++++++++++++ .../OAuth2ClientAutoConfigurationTests.java | 103 +++++++++ ...istrationRepositoryConfigurationTests.java | 57 ----- ...entWebSecurityAutoConfigurationTests.java} | 183 +++++++--------- ...eactive.AutoConfigureWebTestClient.imports | 1 + ...e.web.servlet.AutoConfigureMockMvc.imports | 3 +- ...TestAutoConfigurationIntegrationTests.java | 4 +- 19 files changed, 611 insertions(+), 418 deletions(-) create mode 100644 spring-boot-project/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/security/oauth2/client/OAuth2ClientAutoConfiguration.java rename spring-boot-project/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/security/oauth2/client/{servlet/OAuth2ClientRegistrationRepositoryConfiguration.java => OAuth2ClientConfigurations.java} (51%) create mode 100644 spring-boot-project/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/security/oauth2/client/reactive/ReactiveOAuth2ClientWebSecurityAutoConfiguration.java rename spring-boot-project/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/security/oauth2/client/servlet/{OAuth2WebSecurityConfiguration.java => OAuth2ClientWebSecurityAutoConfiguration.java} (67%) create mode 100644 spring-boot-project/spring-boot-autoconfigure/src/main/resources/META-INF/spring/org.springframework.boot.autoconfigure.AutoConfiguration.replacements create mode 100644 spring-boot-project/spring-boot-autoconfigure/src/test/java/org/springframework/boot/autoconfigure/security/oauth2/client/reactive/ReactiveOAuth2ClientWebSecurityAutoConfigurationTests.java create mode 100644 spring-boot-project/spring-boot-autoconfigure/src/test/java/org/springframework/boot/autoconfigure/security/oauth2/client/servlet/OAuth2ClientAutoConfigurationTests.java delete mode 100644 spring-boot-project/spring-boot-autoconfigure/src/test/java/org/springframework/boot/autoconfigure/security/oauth2/client/servlet/OAuth2ClientRegistrationRepositoryConfigurationTests.java rename spring-boot-project/spring-boot-autoconfigure/src/test/java/org/springframework/boot/autoconfigure/security/oauth2/client/servlet/{OAuth2WebSecurityConfigurationTests.java => OAuth2ClientWebSecurityAutoConfigurationTests.java} (72%) diff --git a/spring-boot-project/spring-boot-actuator-autoconfigure/src/main/java/org/springframework/boot/actuate/autoconfigure/security/reactive/ReactiveManagementWebSecurityAutoConfiguration.java b/spring-boot-project/spring-boot-actuator-autoconfigure/src/main/java/org/springframework/boot/actuate/autoconfigure/security/reactive/ReactiveManagementWebSecurityAutoConfiguration.java index 3567604974..2e3a3ee6ff 100644 --- a/spring-boot-project/spring-boot-actuator-autoconfigure/src/main/java/org/springframework/boot/actuate/autoconfigure/security/reactive/ReactiveManagementWebSecurityAutoConfiguration.java +++ b/spring-boot-project/spring-boot-actuator-autoconfigure/src/main/java/org/springframework/boot/actuate/autoconfigure/security/reactive/ReactiveManagementWebSecurityAutoConfiguration.java @@ -28,7 +28,7 @@ import org.springframework.boot.autoconfigure.EnableAutoConfiguration; import org.springframework.boot.autoconfigure.condition.ConditionalOnClass; import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean; import org.springframework.boot.autoconfigure.condition.ConditionalOnWebApplication; -import org.springframework.boot.autoconfigure.security.oauth2.client.reactive.ReactiveOAuth2ClientAutoConfiguration; +import org.springframework.boot.autoconfigure.security.oauth2.client.reactive.ReactiveOAuth2ClientWebSecurityAutoConfiguration; import org.springframework.boot.autoconfigure.security.oauth2.resource.reactive.ReactiveOAuth2ResourceServerAutoConfiguration; import org.springframework.boot.autoconfigure.security.reactive.ReactiveSecurityAutoConfiguration; import org.springframework.boot.autoconfigure.security.reactive.ReactiveUserDetailsServiceAutoConfiguration; @@ -57,7 +57,7 @@ import static org.springframework.security.config.Customizer.withDefaults; */ @AutoConfiguration(before = ReactiveSecurityAutoConfiguration.class, after = { HealthEndpointAutoConfiguration.class, InfoEndpointAutoConfiguration.class, - WebEndpointAutoConfiguration.class, ReactiveOAuth2ClientAutoConfiguration.class, + WebEndpointAutoConfiguration.class, ReactiveOAuth2ClientWebSecurityAutoConfiguration.class, ReactiveOAuth2ResourceServerAutoConfiguration.class, ReactiveUserDetailsServiceAutoConfiguration.class }) @ConditionalOnClass({ EnableWebFluxSecurity.class, WebFilterChainProxy.class }) diff --git a/spring-boot-project/spring-boot-actuator-autoconfigure/src/main/java/org/springframework/boot/actuate/autoconfigure/security/servlet/ManagementWebSecurityAutoConfiguration.java b/spring-boot-project/spring-boot-actuator-autoconfigure/src/main/java/org/springframework/boot/actuate/autoconfigure/security/servlet/ManagementWebSecurityAutoConfiguration.java index 704100462f..1dcdfe6121 100644 --- a/spring-boot-project/spring-boot-actuator-autoconfigure/src/main/java/org/springframework/boot/actuate/autoconfigure/security/servlet/ManagementWebSecurityAutoConfiguration.java +++ b/spring-boot-project/spring-boot-actuator-autoconfigure/src/main/java/org/springframework/boot/actuate/autoconfigure/security/servlet/ManagementWebSecurityAutoConfiguration.java @@ -1,5 +1,5 @@ /* - * Copyright 2012-2024 the original author or authors. + * Copyright 2012-2025 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. @@ -26,7 +26,7 @@ import org.springframework.boot.autoconfigure.EnableAutoConfiguration; import org.springframework.boot.autoconfigure.condition.ConditionalOnWebApplication; import org.springframework.boot.autoconfigure.security.ConditionalOnDefaultWebSecurity; import org.springframework.boot.autoconfigure.security.SecurityProperties; -import org.springframework.boot.autoconfigure.security.oauth2.client.servlet.OAuth2ClientAutoConfiguration; +import org.springframework.boot.autoconfigure.security.oauth2.client.servlet.OAuth2ClientWebSecurityAutoConfiguration; import org.springframework.boot.autoconfigure.security.oauth2.resource.servlet.OAuth2ResourceServerAutoConfiguration; import org.springframework.boot.autoconfigure.security.saml2.Saml2RelyingPartyAutoConfiguration; import org.springframework.boot.autoconfigure.security.servlet.SecurityAutoConfiguration; @@ -53,7 +53,7 @@ import static org.springframework.security.config.Customizer.withDefaults; */ @AutoConfiguration(before = SecurityAutoConfiguration.class, after = { HealthEndpointAutoConfiguration.class, InfoEndpointAutoConfiguration.class, - WebEndpointAutoConfiguration.class, OAuth2ClientAutoConfiguration.class, + WebEndpointAutoConfiguration.class, OAuth2ClientWebSecurityAutoConfiguration.class, OAuth2ResourceServerAutoConfiguration.class, Saml2RelyingPartyAutoConfiguration.class }) @ConditionalOnWebApplication(type = ConditionalOnWebApplication.Type.SERVLET) @ConditionalOnDefaultWebSecurity diff --git a/spring-boot-project/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/security/oauth2/client/OAuth2ClientAutoConfiguration.java b/spring-boot-project/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/security/oauth2/client/OAuth2ClientAutoConfiguration.java new file mode 100644 index 0000000000..11b53239b8 --- /dev/null +++ b/spring-boot-project/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/security/oauth2/client/OAuth2ClientAutoConfiguration.java @@ -0,0 +1,56 @@ +/* + * Copyright 2012-2025 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.boot.autoconfigure.security.oauth2.client; + +import org.springframework.boot.autoconfigure.AutoConfiguration; +import org.springframework.boot.autoconfigure.EnableAutoConfiguration; +import org.springframework.boot.autoconfigure.condition.ConditionalOnClass; +import org.springframework.boot.autoconfigure.condition.ConditionalOnWebApplication; +import org.springframework.boot.autoconfigure.condition.NoneNestedConditions; +import org.springframework.boot.autoconfigure.security.oauth2.client.OAuth2ClientAutoConfiguration.NonReactiveWebApplicationCondition; +import org.springframework.context.annotation.Conditional; +import org.springframework.context.annotation.Import; +import org.springframework.security.oauth2.client.registration.ClientRegistration; + +/** + * {@link EnableAutoConfiguration Auto-configuration} for OAuth client support. + * + * @author Madhura Bhave + * @author Phillip Webb + * @since 3.5.0 + */ +@AutoConfiguration +@Conditional(NonReactiveWebApplicationCondition.class) +@ConditionalOnClass(ClientRegistration.class) +@Import({ OAuth2ClientConfigurations.ClientRegistrationRepositoryConfiguration.class, + OAuth2ClientConfigurations.OAuth2AuthorizedClientServiceConfiguration.class }) +public class OAuth2ClientAutoConfiguration { + + static class NonReactiveWebApplicationCondition extends NoneNestedConditions { + + NonReactiveWebApplicationCondition() { + super(ConfigurationPhase.PARSE_CONFIGURATION); + } + + @ConditionalOnWebApplication(type = ConditionalOnWebApplication.Type.REACTIVE) + static class ReactiveWebApplicationCondition { + + } + + } + +} diff --git a/spring-boot-project/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/security/oauth2/client/servlet/OAuth2ClientRegistrationRepositoryConfiguration.java b/spring-boot-project/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/security/oauth2/client/OAuth2ClientConfigurations.java similarity index 51% rename from spring-boot-project/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/security/oauth2/client/servlet/OAuth2ClientRegistrationRepositoryConfiguration.java rename to spring-boot-project/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/security/oauth2/client/OAuth2ClientConfigurations.java index edcb424d7f..a4a7e4d04f 100644 --- a/spring-boot-project/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/security/oauth2/client/servlet/OAuth2ClientRegistrationRepositoryConfiguration.java +++ b/spring-boot-project/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/security/oauth2/client/OAuth2ClientConfigurations.java @@ -14,39 +14,56 @@ * limitations under the License. */ -package org.springframework.boot.autoconfigure.security.oauth2.client.servlet; +package org.springframework.boot.autoconfigure.security.oauth2.client; import java.util.ArrayList; import java.util.List; +import org.springframework.boot.autoconfigure.condition.ConditionalOnBean; import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean; -import org.springframework.boot.autoconfigure.security.oauth2.client.ConditionalOnOAuth2ClientRegistrationProperties; -import org.springframework.boot.autoconfigure.security.oauth2.client.OAuth2ClientProperties; -import org.springframework.boot.autoconfigure.security.oauth2.client.OAuth2ClientPropertiesMapper; import org.springframework.boot.context.properties.EnableConfigurationProperties; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; +import org.springframework.security.oauth2.client.InMemoryOAuth2AuthorizedClientService; +import org.springframework.security.oauth2.client.OAuth2AuthorizedClientService; import org.springframework.security.oauth2.client.registration.ClientRegistration; import org.springframework.security.oauth2.client.registration.ClientRegistrationRepository; import org.springframework.security.oauth2.client.registration.InMemoryClientRegistrationRepository; /** - * {@link Configuration @Configuration} used to map {@link OAuth2ClientProperties} to - * client registrations. + * Configurations related to auto-configuration of OAuth2 client support. * * @author Madhura Bhave + * @author Andy Wilkinson */ -@Configuration(proxyBeanMethods = false) -@EnableConfigurationProperties(OAuth2ClientProperties.class) -@ConditionalOnOAuth2ClientRegistrationProperties -class OAuth2ClientRegistrationRepositoryConfiguration { +class OAuth2ClientConfigurations { - @Bean + @Configuration(proxyBeanMethods = false) + @ConditionalOnOAuth2ClientRegistrationProperties + @EnableConfigurationProperties(OAuth2ClientProperties.class) @ConditionalOnMissingBean(ClientRegistrationRepository.class) - InMemoryClientRegistrationRepository clientRegistrationRepository(OAuth2ClientProperties properties) { - List registrations = new ArrayList<>( - new OAuth2ClientPropertiesMapper(properties).asClientRegistrations().values()); - return new InMemoryClientRegistrationRepository(registrations); + static class ClientRegistrationRepositoryConfiguration { + + @Bean + InMemoryClientRegistrationRepository clientRegistrationRepository(OAuth2ClientProperties properties) { + List registrations = new ArrayList<>( + new OAuth2ClientPropertiesMapper(properties).asClientRegistrations().values()); + return new InMemoryClientRegistrationRepository(registrations); + } + + } + + @Configuration(proxyBeanMethods = false) + @ConditionalOnBean(ClientRegistrationRepository.class) + static class OAuth2AuthorizedClientServiceConfiguration { + + @Bean + @ConditionalOnMissingBean + OAuth2AuthorizedClientService authorizedClientService( + ClientRegistrationRepository clientRegistrationRepository) { + return new InMemoryOAuth2AuthorizedClientService(clientRegistrationRepository); + } + } } diff --git a/spring-boot-project/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/security/oauth2/client/reactive/ReactiveOAuth2ClientAutoConfiguration.java b/spring-boot-project/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/security/oauth2/client/reactive/ReactiveOAuth2ClientAutoConfiguration.java index f5d645e008..a7a40f27d5 100644 --- a/spring-boot-project/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/security/oauth2/client/reactive/ReactiveOAuth2ClientAutoConfiguration.java +++ b/spring-boot-project/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/security/oauth2/client/reactive/ReactiveOAuth2ClientAutoConfiguration.java @@ -1,5 +1,5 @@ /* - * Copyright 2012-2022 the original author or authors. + * Copyright 2012-2025 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. @@ -23,12 +23,8 @@ import org.springframework.boot.autoconfigure.EnableAutoConfiguration; import org.springframework.boot.autoconfigure.condition.ConditionalOnClass; import org.springframework.boot.autoconfigure.condition.ConditionalOnWebApplication; import org.springframework.boot.autoconfigure.condition.NoneNestedConditions; -import org.springframework.boot.autoconfigure.security.oauth2.client.OAuth2ClientProperties; -import org.springframework.boot.autoconfigure.security.reactive.ReactiveSecurityAutoConfiguration; -import org.springframework.boot.context.properties.EnableConfigurationProperties; import org.springframework.context.annotation.Conditional; import org.springframework.context.annotation.Import; -import org.springframework.security.config.annotation.web.reactive.EnableWebFluxSecurity; import org.springframework.security.oauth2.client.registration.ClientRegistration; /** @@ -38,12 +34,11 @@ import org.springframework.security.oauth2.client.registration.ClientRegistratio * @author Madhura Bhave * @since 2.1.0 */ -@AutoConfiguration(before = ReactiveSecurityAutoConfiguration.class) -@EnableConfigurationProperties(OAuth2ClientProperties.class) +@AutoConfiguration @Conditional(ReactiveOAuth2ClientAutoConfiguration.NonServletApplicationCondition.class) -@ConditionalOnClass({ Flux.class, EnableWebFluxSecurity.class, ClientRegistration.class }) +@ConditionalOnClass({ Flux.class, ClientRegistration.class }) @Import({ ReactiveOAuth2ClientConfigurations.ReactiveClientRegistrationRepositoryConfiguration.class, - ReactiveOAuth2ClientConfigurations.ReactiveOAuth2ClientConfiguration.class }) + ReactiveOAuth2ClientConfigurations.ReactiveOAuth2AuthorizedClientServiceConfiguration.class }) public class ReactiveOAuth2ClientAutoConfiguration { static class NonServletApplicationCondition extends NoneNestedConditions { diff --git a/spring-boot-project/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/security/oauth2/client/reactive/ReactiveOAuth2ClientConfigurations.java b/spring-boot-project/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/security/oauth2/client/reactive/ReactiveOAuth2ClientConfigurations.java index 8ab3b87a18..d8e65561c1 100644 --- a/spring-boot-project/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/security/oauth2/client/reactive/ReactiveOAuth2ClientConfigurations.java +++ b/spring-boot-project/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/security/oauth2/client/reactive/ReactiveOAuth2ClientConfigurations.java @@ -21,23 +21,17 @@ import java.util.List; import org.springframework.boot.autoconfigure.condition.ConditionalOnBean; import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean; -import org.springframework.boot.autoconfigure.condition.ConditionalOnWebApplication; import org.springframework.boot.autoconfigure.security.oauth2.client.ConditionalOnOAuth2ClientRegistrationProperties; import org.springframework.boot.autoconfigure.security.oauth2.client.OAuth2ClientProperties; import org.springframework.boot.autoconfigure.security.oauth2.client.OAuth2ClientPropertiesMapper; +import org.springframework.boot.context.properties.EnableConfigurationProperties; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; -import org.springframework.security.config.web.server.ServerHttpSecurity; import org.springframework.security.oauth2.client.InMemoryReactiveOAuth2AuthorizedClientService; import org.springframework.security.oauth2.client.ReactiveOAuth2AuthorizedClientService; import org.springframework.security.oauth2.client.registration.ClientRegistration; import org.springframework.security.oauth2.client.registration.InMemoryReactiveClientRegistrationRepository; import org.springframework.security.oauth2.client.registration.ReactiveClientRegistrationRepository; -import org.springframework.security.oauth2.client.web.server.AuthenticatedPrincipalServerOAuth2AuthorizedClientRepository; -import org.springframework.security.oauth2.client.web.server.ServerOAuth2AuthorizedClientRepository; -import org.springframework.security.web.server.SecurityWebFilterChain; - -import static org.springframework.security.config.Customizer.withDefaults; /** * Reactive OAuth2 Client configurations. @@ -47,12 +41,14 @@ import static org.springframework.security.config.Customizer.withDefaults; class ReactiveOAuth2ClientConfigurations { @Configuration(proxyBeanMethods = false) + @EnableConfigurationProperties(OAuth2ClientProperties.class) @ConditionalOnOAuth2ClientRegistrationProperties @ConditionalOnMissingBean(ReactiveClientRegistrationRepository.class) static class ReactiveClientRegistrationRepositoryConfiguration { @Bean - InMemoryReactiveClientRegistrationRepository clientRegistrationRepository(OAuth2ClientProperties properties) { + InMemoryReactiveClientRegistrationRepository reactiveClientRegistrationRepository( + OAuth2ClientProperties properties) { List registrations = new ArrayList<>( new OAuth2ClientPropertiesMapper(properties).asClientRegistrations().values()); return new InMemoryReactiveClientRegistrationRepository(registrations); @@ -62,37 +58,15 @@ class ReactiveOAuth2ClientConfigurations { @Configuration(proxyBeanMethods = false) @ConditionalOnBean(ReactiveClientRegistrationRepository.class) - static class ReactiveOAuth2ClientConfiguration { + static class ReactiveOAuth2AuthorizedClientServiceConfiguration { @Bean @ConditionalOnMissingBean - ReactiveOAuth2AuthorizedClientService authorizedClientService( + ReactiveOAuth2AuthorizedClientService reactiveAuthorizedClientService( ReactiveClientRegistrationRepository clientRegistrationRepository) { return new InMemoryReactiveOAuth2AuthorizedClientService(clientRegistrationRepository); } - @Bean - @ConditionalOnMissingBean - ServerOAuth2AuthorizedClientRepository authorizedClientRepository( - ReactiveOAuth2AuthorizedClientService authorizedClientService) { - return new AuthenticatedPrincipalServerOAuth2AuthorizedClientRepository(authorizedClientService); - } - - @Configuration(proxyBeanMethods = false) - @ConditionalOnWebApplication(type = ConditionalOnWebApplication.Type.REACTIVE) - static class SecurityWebFilterChainConfiguration { - - @Bean - @ConditionalOnMissingBean - SecurityWebFilterChain springSecurityFilterChain(ServerHttpSecurity http) { - http.authorizeExchange((exchange) -> exchange.anyExchange().authenticated()); - http.oauth2Login(withDefaults()); - http.oauth2Client(withDefaults()); - return http.build(); - } - - } - } } diff --git a/spring-boot-project/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/security/oauth2/client/reactive/ReactiveOAuth2ClientWebSecurityAutoConfiguration.java b/spring-boot-project/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/security/oauth2/client/reactive/ReactiveOAuth2ClientWebSecurityAutoConfiguration.java new file mode 100644 index 0000000000..2e835ee425 --- /dev/null +++ b/spring-boot-project/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/security/oauth2/client/reactive/ReactiveOAuth2ClientWebSecurityAutoConfiguration.java @@ -0,0 +1,68 @@ +/* + * Copyright 2012-2025 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.boot.autoconfigure.security.oauth2.client.reactive; + +import reactor.core.publisher.Flux; + +import org.springframework.boot.autoconfigure.AutoConfiguration; +import org.springframework.boot.autoconfigure.condition.ConditionalOnBean; +import org.springframework.boot.autoconfigure.condition.ConditionalOnClass; +import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean; +import org.springframework.boot.autoconfigure.condition.ConditionalOnWebApplication; +import org.springframework.boot.autoconfigure.security.reactive.ReactiveSecurityAutoConfiguration; +import org.springframework.context.annotation.Bean; +import org.springframework.security.config.annotation.web.reactive.EnableWebFluxSecurity; +import org.springframework.security.config.web.server.ServerHttpSecurity; +import org.springframework.security.oauth2.client.ReactiveOAuth2AuthorizedClientService; +import org.springframework.security.oauth2.client.web.server.AuthenticatedPrincipalServerOAuth2AuthorizedClientRepository; +import org.springframework.security.oauth2.client.web.server.ServerOAuth2AuthorizedClientRepository; +import org.springframework.security.web.server.SecurityWebFilterChain; + +import static org.springframework.security.config.Customizer.withDefaults; + +/** + * Auto-configuration for reactive web security that uses an OAuth 2 client. + * + * @author Madhura Bhave + * @author Phillip Webb + * @author Andy Wilkinson + * @since 3.5.0 + */ +@AutoConfiguration(before = ReactiveSecurityAutoConfiguration.class, + after = ReactiveOAuth2ClientAutoConfiguration.class) +@ConditionalOnClass({ Flux.class, EnableWebFluxSecurity.class, ServerOAuth2AuthorizedClientRepository.class }) +@ConditionalOnBean(ReactiveOAuth2AuthorizedClientService.class) +@ConditionalOnWebApplication(type = ConditionalOnWebApplication.Type.REACTIVE) +public class ReactiveOAuth2ClientWebSecurityAutoConfiguration { + + @Bean + @ConditionalOnMissingBean + ServerOAuth2AuthorizedClientRepository authorizedClientRepository( + ReactiveOAuth2AuthorizedClientService authorizedClientService) { + return new AuthenticatedPrincipalServerOAuth2AuthorizedClientRepository(authorizedClientService); + } + + @Bean + @ConditionalOnMissingBean + SecurityWebFilterChain springSecurityFilterChain(ServerHttpSecurity http) { + http.authorizeExchange((exchange) -> exchange.anyExchange().authenticated()); + http.oauth2Login(withDefaults()); + http.oauth2Client(withDefaults()); + return http.build(); + } + +} diff --git a/spring-boot-project/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/security/oauth2/client/servlet/OAuth2ClientAutoConfiguration.java b/spring-boot-project/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/security/oauth2/client/servlet/OAuth2ClientAutoConfiguration.java index 438f156133..d7c4033ee2 100644 --- a/spring-boot-project/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/security/oauth2/client/servlet/OAuth2ClientAutoConfiguration.java +++ b/spring-boot-project/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/security/oauth2/client/servlet/OAuth2ClientAutoConfiguration.java @@ -1,5 +1,5 @@ /* - * Copyright 2012-2022 the original author or authors. + * Copyright 2012-2025 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. @@ -16,14 +16,7 @@ package org.springframework.boot.autoconfigure.security.oauth2.client.servlet; -import org.springframework.boot.autoconfigure.AutoConfiguration; import org.springframework.boot.autoconfigure.EnableAutoConfiguration; -import org.springframework.boot.autoconfigure.condition.ConditionalOnClass; -import org.springframework.boot.autoconfigure.condition.ConditionalOnWebApplication; -import org.springframework.boot.autoconfigure.security.servlet.SecurityAutoConfiguration; -import org.springframework.context.annotation.Import; -import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity; -import org.springframework.security.oauth2.client.registration.ClientRegistration; /** * {@link EnableAutoConfiguration Auto-configuration} for OAuth client support. @@ -31,11 +24,10 @@ import org.springframework.security.oauth2.client.registration.ClientRegistratio * @author Madhura Bhave * @author Phillip Webb * @since 2.0.0 + * @deprecated since 3.5.0 for removal in 4.0.0 in favor of + * {@link org.springframework.boot.autoconfigure.security.oauth2.client.OAuth2ClientAutoConfiguration} */ -@AutoConfiguration(before = SecurityAutoConfiguration.class) -@ConditionalOnClass({ EnableWebSecurity.class, ClientRegistration.class }) -@ConditionalOnWebApplication(type = ConditionalOnWebApplication.Type.SERVLET) -@Import({ OAuth2ClientRegistrationRepositoryConfiguration.class, OAuth2WebSecurityConfiguration.class }) +@Deprecated(since = "3.5.0", forRemoval = true) public class OAuth2ClientAutoConfiguration { } diff --git a/spring-boot-project/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/security/oauth2/client/servlet/OAuth2WebSecurityConfiguration.java b/spring-boot-project/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/security/oauth2/client/servlet/OAuth2ClientWebSecurityAutoConfiguration.java similarity index 67% rename from spring-boot-project/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/security/oauth2/client/servlet/OAuth2WebSecurityConfiguration.java rename to spring-boot-project/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/security/oauth2/client/servlet/OAuth2ClientWebSecurityAutoConfiguration.java index 3163f7309e..c9b6011c4c 100644 --- a/spring-boot-project/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/security/oauth2/client/servlet/OAuth2WebSecurityConfiguration.java +++ b/spring-boot-project/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/security/oauth2/client/servlet/OAuth2ClientWebSecurityAutoConfiguration.java @@ -1,5 +1,5 @@ /* - * Copyright 2012-2023 the original author or authors. + * Copyright 2012-2025 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. @@ -16,15 +16,19 @@ package org.springframework.boot.autoconfigure.security.oauth2.client.servlet; +import org.springframework.boot.autoconfigure.AutoConfiguration; import org.springframework.boot.autoconfigure.condition.ConditionalOnBean; +import org.springframework.boot.autoconfigure.condition.ConditionalOnClass; import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean; +import org.springframework.boot.autoconfigure.condition.ConditionalOnWebApplication; import org.springframework.boot.autoconfigure.security.ConditionalOnDefaultWebSecurity; +import org.springframework.boot.autoconfigure.security.oauth2.client.OAuth2ClientAutoConfiguration; +import org.springframework.boot.autoconfigure.security.servlet.SecurityAutoConfiguration; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; import org.springframework.security.config.annotation.web.builders.HttpSecurity; -import org.springframework.security.oauth2.client.InMemoryOAuth2AuthorizedClientService; +import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity; import org.springframework.security.oauth2.client.OAuth2AuthorizedClientService; -import org.springframework.security.oauth2.client.registration.ClientRegistrationRepository; import org.springframework.security.oauth2.client.web.AuthenticatedPrincipalOAuth2AuthorizedClientRepository; import org.springframework.security.oauth2.client.web.OAuth2AuthorizedClientRepository; import org.springframework.security.web.SecurityFilterChain; @@ -32,20 +36,18 @@ import org.springframework.security.web.SecurityFilterChain; import static org.springframework.security.config.Customizer.withDefaults; /** - * {@link SecurityFilterChain} to add OAuth client support. + * Auto-configuration for web security that uses an OAuth 2 client. * * @author Madhura Bhave * @author Phillip Webb + * @author Andy Wilkinson + * @since 3.5.0 */ -@Configuration(proxyBeanMethods = false) -@ConditionalOnBean(ClientRegistrationRepository.class) -class OAuth2WebSecurityConfiguration { - - @Bean - @ConditionalOnMissingBean - OAuth2AuthorizedClientService authorizedClientService(ClientRegistrationRepository clientRegistrationRepository) { - return new InMemoryOAuth2AuthorizedClientService(clientRegistrationRepository); - } +@AutoConfiguration(before = SecurityAutoConfiguration.class, after = OAuth2ClientAutoConfiguration.class) +@ConditionalOnClass({ EnableWebSecurity.class, OAuth2AuthorizedClientRepository.class }) +@ConditionalOnBean(OAuth2AuthorizedClientService.class) +@ConditionalOnWebApplication(type = ConditionalOnWebApplication.Type.SERVLET) +public class OAuth2ClientWebSecurityAutoConfiguration { @Bean @ConditionalOnMissingBean diff --git a/spring-boot-project/spring-boot-autoconfigure/src/main/resources/META-INF/spring/org.springframework.boot.autoconfigure.AutoConfiguration.imports b/spring-boot-project/spring-boot-autoconfigure/src/main/resources/META-INF/spring/org.springframework.boot.autoconfigure.AutoConfiguration.imports index 8325b51ba0..cc4e6fafdc 100644 --- a/spring-boot-project/spring-boot-autoconfigure/src/main/resources/META-INF/spring/org.springframework.boot.autoconfigure.AutoConfiguration.imports +++ b/spring-boot-project/spring-boot-autoconfigure/src/main/resources/META-INF/spring/org.springframework.boot.autoconfigure.AutoConfiguration.imports @@ -115,8 +115,10 @@ org.springframework.boot.autoconfigure.security.rsocket.RSocketSecurityAutoConfi org.springframework.boot.autoconfigure.security.saml2.Saml2RelyingPartyAutoConfiguration org.springframework.boot.autoconfigure.sendgrid.SendGridAutoConfiguration org.springframework.boot.autoconfigure.session.SessionAutoConfiguration -org.springframework.boot.autoconfigure.security.oauth2.client.servlet.OAuth2ClientAutoConfiguration +org.springframework.boot.autoconfigure.security.oauth2.client.OAuth2ClientAutoConfiguration +org.springframework.boot.autoconfigure.security.oauth2.client.servlet.OAuth2ClientWebSecurityAutoConfiguration org.springframework.boot.autoconfigure.security.oauth2.client.reactive.ReactiveOAuth2ClientAutoConfiguration +org.springframework.boot.autoconfigure.security.oauth2.client.reactive.ReactiveOAuth2ClientWebSecurityAutoConfiguration org.springframework.boot.autoconfigure.security.oauth2.resource.servlet.OAuth2ResourceServerAutoConfiguration org.springframework.boot.autoconfigure.security.oauth2.resource.reactive.ReactiveOAuth2ResourceServerAutoConfiguration org.springframework.boot.autoconfigure.security.oauth2.server.servlet.OAuth2AuthorizationServerAutoConfiguration diff --git a/spring-boot-project/spring-boot-autoconfigure/src/main/resources/META-INF/spring/org.springframework.boot.autoconfigure.AutoConfiguration.replacements b/spring-boot-project/spring-boot-autoconfigure/src/main/resources/META-INF/spring/org.springframework.boot.autoconfigure.AutoConfiguration.replacements new file mode 100644 index 0000000000..909fe10913 --- /dev/null +++ b/spring-boot-project/spring-boot-autoconfigure/src/main/resources/META-INF/spring/org.springframework.boot.autoconfigure.AutoConfiguration.replacements @@ -0,0 +1 @@ +org.springframework.boot.autoconfigure.security.oauth2.client.servlet.OAuth2ClientAutoConfiguration=org.springframework.boot.autoconfigure.security.oauth2.client.OAuth2ClientAutoConfiguration diff --git a/spring-boot-project/spring-boot-autoconfigure/src/test/java/org/springframework/boot/autoconfigure/security/oauth2/client/reactive/ReactiveOAuth2ClientAutoConfigurationTests.java b/spring-boot-project/spring-boot-autoconfigure/src/test/java/org/springframework/boot/autoconfigure/security/oauth2/client/reactive/ReactiveOAuth2ClientAutoConfigurationTests.java index 270ac7029a..f55c6fac9c 100644 --- a/spring-boot-project/spring-boot-autoconfigure/src/test/java/org/springframework/boot/autoconfigure/security/oauth2/client/reactive/ReactiveOAuth2ClientAutoConfigurationTests.java +++ b/spring-boot-project/spring-boot-autoconfigure/src/test/java/org/springframework/boot/autoconfigure/security/oauth2/client/reactive/ReactiveOAuth2ClientAutoConfigurationTests.java @@ -1,5 +1,5 @@ /* - * Copyright 2012-2023 the original author or authors. + * Copyright 2012-2025 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. @@ -17,8 +17,6 @@ package org.springframework.boot.autoconfigure.security.oauth2.client.reactive; import java.time.Duration; -import java.util.ArrayList; -import java.util.List; import org.junit.jupiter.api.Test; import reactor.core.publisher.Flux; @@ -26,34 +24,16 @@ import reactor.core.publisher.Flux; import org.springframework.boot.autoconfigure.AutoConfigurations; import org.springframework.boot.autoconfigure.security.reactive.ReactiveSecurityAutoConfiguration; import org.springframework.boot.test.context.FilteredClassLoader; -import org.springframework.boot.test.context.assertj.AssertableReactiveWebApplicationContext; import org.springframework.boot.test.context.runner.ApplicationContextRunner; -import org.springframework.boot.test.context.runner.ReactiveWebApplicationContextRunner; import org.springframework.boot.test.context.runner.WebApplicationContextRunner; -import org.springframework.context.ApplicationContext; -import org.springframework.context.ApplicationContextAware; -import org.springframework.context.annotation.Bean; -import org.springframework.context.annotation.Configuration; -import org.springframework.context.annotation.Import; -import org.springframework.security.config.BeanIds; -import org.springframework.security.config.annotation.web.reactive.EnableWebFluxSecurity; -import org.springframework.security.config.web.server.ServerHttpSecurity; -import org.springframework.security.oauth2.client.InMemoryReactiveOAuth2AuthorizedClientService; import org.springframework.security.oauth2.client.ReactiveOAuth2AuthorizedClientService; import org.springframework.security.oauth2.client.registration.ClientRegistration; -import org.springframework.security.oauth2.client.registration.ClientRegistrationRepository; import org.springframework.security.oauth2.client.registration.InMemoryReactiveClientRegistrationRepository; import org.springframework.security.oauth2.client.registration.ReactiveClientRegistrationRepository; -import org.springframework.security.oauth2.client.web.server.AuthenticatedPrincipalServerOAuth2AuthorizedClientRepository; -import org.springframework.security.oauth2.client.web.server.OAuth2AuthorizationCodeGrantWebFilter; -import org.springframework.security.oauth2.client.web.server.ServerOAuth2AuthorizedClientRepository; -import org.springframework.security.oauth2.client.web.server.authentication.OAuth2LoginAuthenticationWebFilter; import org.springframework.security.oauth2.core.AuthorizationGrantType; -import org.springframework.security.web.server.SecurityWebFilterChain; -import org.springframework.test.util.ReflectionTestUtils; -import org.springframework.web.server.WebFilter; import static org.assertj.core.api.Assertions.assertThat; +import static org.mockito.Mockito.mock; /** * Tests for {@link ReactiveOAuth2ClientAutoConfiguration}. @@ -62,12 +42,12 @@ import static org.assertj.core.api.Assertions.assertThat; */ class ReactiveOAuth2ClientAutoConfigurationTests { + private static final String REGISTRATION_PREFIX = "spring.security.oauth2.client.registration"; + private final ApplicationContextRunner contextRunner = new ApplicationContextRunner() .withConfiguration(AutoConfigurations.of(ReactiveOAuth2ClientAutoConfiguration.class, ReactiveSecurityAutoConfiguration.class)); - private static final String REGISTRATION_PREFIX = "spring.security.oauth2.client.registration"; - @Test void autoConfigurationShouldBackOffForServletEnvironments() { new WebApplicationContextRunner() @@ -76,16 +56,20 @@ class ReactiveOAuth2ClientAutoConfigurationTests { } @Test - void clientRegistrationRepositoryBeanShouldNotBeCreatedWhenPropertiesAbsent() { - this.contextRunner.run((context) -> assertThat(context).doesNotHaveBean(ClientRegistrationRepository.class)); + void beansShouldNotBeCreatedWhenPropertiesAbsent() { + this.contextRunner + .run((context) -> assertThat(context).doesNotHaveBean(ReactiveClientRegistrationRepository.class) + .doesNotHaveBean(ReactiveOAuth2AuthorizedClientService.class)); } @Test - void clientRegistrationRepositoryBeanShouldBeCreatedWhenPropertiesPresent() { + void beansAreCreatedWhenPropertiesPresent() { this.contextRunner .withPropertyValues(REGISTRATION_PREFIX + ".foo.client-id=abcd", REGISTRATION_PREFIX + ".foo.client-secret=secret", REGISTRATION_PREFIX + ".foo.provider=github") .run((context) -> { + assertThat(context).hasSingleBean(ReactiveClientRegistrationRepository.class); + assertThat(context).hasSingleBean(ReactiveOAuth2AuthorizedClientService.class); ReactiveClientRegistrationRepository repository = context .getBean(ReactiveClientRegistrationRepository.class); ClientRegistration registration = repository.findByRegistrationId("foo").block(Duration.ofSeconds(30)); @@ -95,24 +79,10 @@ class ReactiveOAuth2ClientAutoConfigurationTests { } @Test - void authorizedClientServiceAndRepositoryBeansAreConditionalOnClientRegistrationRepository() { - this.contextRunner.run((context) -> { - assertThat(context).doesNotHaveBean(ReactiveOAuth2AuthorizedClientService.class); - assertThat(context).doesNotHaveBean(ServerOAuth2AuthorizedClientRepository.class); - }); - } - - @Test - void configurationRegistersAuthorizedClientServiceAndRepositoryBeans() { - this.contextRunner.withUserConfiguration(ReactiveClientRepositoryConfiguration.class).run((context) -> { - assertThat(context).hasSingleBean(InMemoryReactiveOAuth2AuthorizedClientService.class); - assertThat(context).hasSingleBean(AuthenticatedPrincipalServerOAuth2AuthorizedClientRepository.class); - }); - } - - @Test - void authorizedClientServiceBeanIsConditionalOnMissingBean() { - this.contextRunner.withUserConfiguration(ReactiveOAuth2AuthorizedClientRepositoryConfiguration.class) + void clientServiceBeanIsConditionalOnMissingBean() { + this.contextRunner + .withBean("testAuthorizedClientService", ReactiveOAuth2AuthorizedClientService.class, + () -> mock(ReactiveOAuth2AuthorizedClientService.class)) .run((context) -> { assertThat(context).hasSingleBean(ReactiveOAuth2AuthorizedClientService.class); assertThat(context).hasBean("testAuthorizedClientService"); @@ -120,43 +90,11 @@ class ReactiveOAuth2ClientAutoConfigurationTests { } @Test - void authorizedClientRepositoryBeanIsConditionalOnAuthorizedClientService() { + void clientServiceBeanIsCreatedWithUserDefinedClientRegistrationRepository() { this.contextRunner - .run((context) -> assertThat(context).doesNotHaveBean(ServerOAuth2AuthorizedClientRepository.class)); - } - - @Test - void configurationRegistersAuthorizedClientRepositoryBean() { - this.contextRunner.withUserConfiguration(ReactiveOAuth2AuthorizedClientServiceConfiguration.class) - .run((context) -> assertThat(context) - .hasSingleBean(AuthenticatedPrincipalServerOAuth2AuthorizedClientRepository.class)); - } - - @Test - void authorizedClientRepositoryBeanIsConditionalOnMissingBean() { - this.contextRunner.withUserConfiguration(ReactiveOAuth2AuthorizedClientRepositoryConfiguration.class) - .run((context) -> { - assertThat(context).hasSingleBean(ServerOAuth2AuthorizedClientRepository.class); - assertThat(context).hasBean("testAuthorizedClientRepository"); - }); - } - - @Test - void securityWebFilterChainBeanConditionalOnWebApplication() { - this.contextRunner.withUserConfiguration(ReactiveOAuth2AuthorizedClientRepositoryConfiguration.class) - .run((context) -> assertThat(context).doesNotHaveBean(SecurityWebFilterChain.class)); - } - - @Test - void configurationRegistersSecurityWebFilterChainBean() { // gh-17949 - new ReactiveWebApplicationContextRunner() - .withConfiguration(AutoConfigurations.of(ReactiveOAuth2ClientAutoConfiguration.class)) - .withUserConfiguration(ReactiveOAuth2AuthorizedClientServiceConfiguration.class, - ServerHttpSecurityConfiguration.class) - .run((context) -> { - assertThat(hasFilter(context, OAuth2LoginAuthenticationWebFilter.class)).isTrue(); - assertThat(hasFilter(context, OAuth2AuthorizationCodeGrantWebFilter.class)).isTrue(); - }); + .withBean(InMemoryReactiveClientRegistrationRepository.class, + () -> new InMemoryReactiveClientRegistrationRepository(getClientRegistration("test", "test"))) + .run((context) -> assertThat(context).hasSingleBean(ReactiveOAuth2AuthorizedClientService.class)); } @Test @@ -164,11 +102,6 @@ class ReactiveOAuth2ClientAutoConfigurationTests { assertWhenClassNotPresent(Flux.class); } - @Test - void autoConfigurationConditionalOnClassEnableWebFluxSecurity() { - assertWhenClassNotPresent(EnableWebFluxSecurity.class); - } - @Test void autoConfigurationConditionalOnClassClientRegistration() { assertWhenClassNotPresent(ClientRegistration.class); @@ -182,86 +115,21 @@ class ReactiveOAuth2ClientAutoConfigurationTests { .run((context) -> assertThat(context).doesNotHaveBean(ReactiveOAuth2ClientAutoConfiguration.class)); } - @SuppressWarnings("unchecked") - private boolean hasFilter(AssertableReactiveWebApplicationContext context, Class filter) { - SecurityWebFilterChain filterChain = (SecurityWebFilterChain) context - .getBean(BeanIds.SPRING_SECURITY_FILTER_CHAIN); - List filters = (List) ReflectionTestUtils.getField(filterChain, "filters"); - return filters.stream().anyMatch(filter::isInstance); - } - - @Configuration(proxyBeanMethods = false) - static class ReactiveClientRepositoryConfiguration { - - @Bean - ReactiveClientRegistrationRepository clientRegistrationRepository() { - List registrations = new ArrayList<>(); - registrations.add(getClientRegistration("first", "https://user-info-uri.com")); - registrations.add(getClientRegistration("second", "https://other-user-info")); - return new InMemoryReactiveClientRegistrationRepository(registrations); - } - - private ClientRegistration getClientRegistration(String id, String userInfoUri) { - ClientRegistration.Builder builder = ClientRegistration.withRegistrationId(id); - builder.clientName("foo") - .clientId("foo") - .clientAuthenticationMethod( - org.springframework.security.oauth2.core.ClientAuthenticationMethod.CLIENT_SECRET_BASIC) - .authorizationGrantType(AuthorizationGrantType.AUTHORIZATION_CODE) - .scope("read") - .clientSecret("secret") - .redirectUri("https://redirect-uri.com") - .authorizationUri("https://authorization-uri.com") - .tokenUri("https://token-uri.com") - .userInfoUri(userInfoUri) - .userNameAttributeName("login"); - return builder.build(); - } - - } - - @Configuration(proxyBeanMethods = false) - @Import(ReactiveClientRepositoryConfiguration.class) - static class ReactiveOAuth2AuthorizedClientServiceConfiguration { - - @Bean - ReactiveOAuth2AuthorizedClientService testAuthorizedClientService( - ReactiveClientRegistrationRepository clientRegistrationRepository) { - return new InMemoryReactiveOAuth2AuthorizedClientService(clientRegistrationRepository); - } - - } - - @Configuration(proxyBeanMethods = false) - @Import(ReactiveOAuth2AuthorizedClientServiceConfiguration.class) - static class ReactiveOAuth2AuthorizedClientRepositoryConfiguration { - - @Bean - ServerOAuth2AuthorizedClientRepository testAuthorizedClientRepository( - ReactiveOAuth2AuthorizedClientService authorizedClientService) { - return new AuthenticatedPrincipalServerOAuth2AuthorizedClientRepository(authorizedClientService); - } - - } - - @Configuration(proxyBeanMethods = false) - static class ServerHttpSecurityConfiguration { - - @Bean - ServerHttpSecurity http() { - TestServerHttpSecurity httpSecurity = new TestServerHttpSecurity(); - return httpSecurity; - } - - static class TestServerHttpSecurity extends ServerHttpSecurity implements ApplicationContextAware { - - @Override - public void setApplicationContext(ApplicationContext applicationContext) { - super.setApplicationContext(applicationContext); - } - - } - + private ClientRegistration getClientRegistration(String id, String userInfoUri) { + ClientRegistration.Builder builder = ClientRegistration.withRegistrationId(id); + builder.clientName("foo") + .clientId("foo") + .clientAuthenticationMethod( + org.springframework.security.oauth2.core.ClientAuthenticationMethod.CLIENT_SECRET_BASIC) + .authorizationGrantType(AuthorizationGrantType.AUTHORIZATION_CODE) + .scope("read") + .clientSecret("secret") + .redirectUri("https://redirect-uri.com") + .authorizationUri("https://authorization-uri.com") + .tokenUri("https://token-uri.com") + .userInfoUri(userInfoUri) + .userNameAttributeName("login"); + return builder.build(); } } diff --git a/spring-boot-project/spring-boot-autoconfigure/src/test/java/org/springframework/boot/autoconfigure/security/oauth2/client/reactive/ReactiveOAuth2ClientWebSecurityAutoConfigurationTests.java b/spring-boot-project/spring-boot-autoconfigure/src/test/java/org/springframework/boot/autoconfigure/security/oauth2/client/reactive/ReactiveOAuth2ClientWebSecurityAutoConfigurationTests.java new file mode 100644 index 0000000000..006c58f277 --- /dev/null +++ b/spring-boot-project/spring-boot-autoconfigure/src/test/java/org/springframework/boot/autoconfigure/security/oauth2/client/reactive/ReactiveOAuth2ClientWebSecurityAutoConfigurationTests.java @@ -0,0 +1,197 @@ +/* + * Copyright 2012-2025 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.boot.autoconfigure.security.oauth2.client.reactive; + +import java.util.ArrayList; +import java.util.List; + +import org.junit.jupiter.api.Test; + +import org.springframework.boot.autoconfigure.AutoConfigurations; +import org.springframework.boot.autoconfigure.security.reactive.ReactiveSecurityAutoConfiguration; +import org.springframework.boot.test.context.assertj.AssertableReactiveWebApplicationContext; +import org.springframework.boot.test.context.runner.ApplicationContextRunner; +import org.springframework.boot.test.context.runner.ReactiveWebApplicationContextRunner; +import org.springframework.boot.test.context.runner.WebApplicationContextRunner; +import org.springframework.context.ApplicationContext; +import org.springframework.context.ApplicationContextAware; +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Configuration; +import org.springframework.context.annotation.Import; +import org.springframework.security.config.BeanIds; +import org.springframework.security.config.web.server.ServerHttpSecurity; +import org.springframework.security.oauth2.client.InMemoryReactiveOAuth2AuthorizedClientService; +import org.springframework.security.oauth2.client.ReactiveOAuth2AuthorizedClientService; +import org.springframework.security.oauth2.client.registration.ClientRegistration; +import org.springframework.security.oauth2.client.registration.InMemoryReactiveClientRegistrationRepository; +import org.springframework.security.oauth2.client.registration.ReactiveClientRegistrationRepository; +import org.springframework.security.oauth2.client.web.server.AuthenticatedPrincipalServerOAuth2AuthorizedClientRepository; +import org.springframework.security.oauth2.client.web.server.OAuth2AuthorizationCodeGrantWebFilter; +import org.springframework.security.oauth2.client.web.server.ServerOAuth2AuthorizedClientRepository; +import org.springframework.security.oauth2.client.web.server.authentication.OAuth2LoginAuthenticationWebFilter; +import org.springframework.security.oauth2.core.AuthorizationGrantType; +import org.springframework.security.web.server.SecurityWebFilterChain; +import org.springframework.test.util.ReflectionTestUtils; +import org.springframework.web.server.WebFilter; + +import static org.assertj.core.api.Assertions.assertThat; + +/** + * Tests for {@link ReactiveOAuth2ClientWebSecurityAutoConfiguration}. + * + * @author Madhura Bhave + * @author Andy Wilkinson + */ +class ReactiveOAuth2ClientWebSecurityAutoConfigurationTests { + + private final ReactiveWebApplicationContextRunner contextRunner = new ReactiveWebApplicationContextRunner() + .withConfiguration(AutoConfigurations.of(ReactiveOAuth2ClientWebSecurityAutoConfiguration.class, + ReactiveSecurityAutoConfiguration.class)); + + @Test + void autoConfigurationShouldBackOffForServletEnvironments() { + new WebApplicationContextRunner() + .withConfiguration(AutoConfigurations.of(ReactiveOAuth2ClientWebSecurityAutoConfiguration.class)) + .run((context) -> assertThat(context) + .doesNotHaveBean(ReactiveOAuth2ClientWebSecurityAutoConfiguration.class)); + } + + @Test + void autoConfigurationIsConditionalOnAuthorizedClientService() { + this.contextRunner.run((context) -> assertThat(context) + .doesNotHaveBean(ReactiveOAuth2ClientWebSecurityAutoConfiguration.class)); + } + + @Test + void configurationRegistersAuthorizedClientRepositoryBean() { + this.contextRunner.withUserConfiguration(ReactiveOAuth2AuthorizedClientServiceConfiguration.class) + .run((context) -> assertThat(context) + .hasSingleBean(AuthenticatedPrincipalServerOAuth2AuthorizedClientRepository.class)); + } + + @Test + void authorizedClientRepositoryBeanIsConditionalOnMissingBean() { + this.contextRunner.withUserConfiguration(ReactiveOAuth2AuthorizedClientRepositoryConfiguration.class) + .run((context) -> { + assertThat(context).hasSingleBean(ServerOAuth2AuthorizedClientRepository.class); + assertThat(context).hasBean("testAuthorizedClientRepository"); + }); + } + + @Test + void configurationRegistersSecurityWebFilterChainBean() { // gh-17949 + this.contextRunner + .withUserConfiguration(ReactiveOAuth2AuthorizedClientServiceConfiguration.class, + ServerHttpSecurityConfiguration.class) + .run((context) -> { + assertThat(hasFilter(context, OAuth2LoginAuthenticationWebFilter.class)).isTrue(); + assertThat(hasFilter(context, OAuth2AuthorizationCodeGrantWebFilter.class)).isTrue(); + }); + } + + @Test + void securityWebFilterChainBeanConditionalOnWebApplication() { + new ApplicationContextRunner() + .withConfiguration(AutoConfigurations.of(ReactiveOAuth2ClientWebSecurityAutoConfiguration.class, + ReactiveSecurityAutoConfiguration.class)) + .withUserConfiguration(ReactiveOAuth2AuthorizedClientRepositoryConfiguration.class) + .run((context) -> assertThat(context).doesNotHaveBean(SecurityWebFilterChain.class)); + } + + @SuppressWarnings("unchecked") + private boolean hasFilter(AssertableReactiveWebApplicationContext context, Class filter) { + SecurityWebFilterChain filterChain = (SecurityWebFilterChain) context + .getBean(BeanIds.SPRING_SECURITY_FILTER_CHAIN); + List filters = (List) ReflectionTestUtils.getField(filterChain, "filters"); + return filters.stream().anyMatch(filter::isInstance); + } + + @Configuration(proxyBeanMethods = false) + @Import(ReactiveClientRepositoryConfiguration.class) + static class ReactiveOAuth2AuthorizedClientServiceConfiguration { + + @Bean + InMemoryReactiveOAuth2AuthorizedClientService testAuthorizedClientService( + ReactiveClientRegistrationRepository clientRegistrationRepository) { + return new InMemoryReactiveOAuth2AuthorizedClientService(clientRegistrationRepository); + } + + } + + @Configuration(proxyBeanMethods = false) + @Import(ReactiveOAuth2AuthorizedClientServiceConfiguration.class) + static class ReactiveOAuth2AuthorizedClientRepositoryConfiguration { + + @Bean + ServerOAuth2AuthorizedClientRepository testAuthorizedClientRepository( + ReactiveOAuth2AuthorizedClientService authorizedClientService) { + return new AuthenticatedPrincipalServerOAuth2AuthorizedClientRepository(authorizedClientService); + } + + } + + @Configuration(proxyBeanMethods = false) + static class ReactiveClientRepositoryConfiguration { + + @Bean + ReactiveClientRegistrationRepository clientRegistrationRepository() { + List registrations = new ArrayList<>(); + registrations.add(getClientRegistration("first", "https://user-info-uri.com")); + registrations.add(getClientRegistration("second", "https://other-user-info")); + return new InMemoryReactiveClientRegistrationRepository(registrations); + } + + private ClientRegistration getClientRegistration(String id, String userInfoUri) { + ClientRegistration.Builder builder = ClientRegistration.withRegistrationId(id); + builder.clientName("foo") + .clientId("foo") + .clientAuthenticationMethod( + org.springframework.security.oauth2.core.ClientAuthenticationMethod.CLIENT_SECRET_BASIC) + .authorizationGrantType(AuthorizationGrantType.AUTHORIZATION_CODE) + .scope("read") + .clientSecret("secret") + .redirectUri("https://redirect-uri.com") + .authorizationUri("https://authorization-uri.com") + .tokenUri("https://token-uri.com") + .userInfoUri(userInfoUri) + .userNameAttributeName("login"); + return builder.build(); + } + + } + + @Configuration(proxyBeanMethods = false) + static class ServerHttpSecurityConfiguration { + + @Bean + ServerHttpSecurity http() { + TestServerHttpSecurity httpSecurity = new TestServerHttpSecurity(); + return httpSecurity; + } + + static class TestServerHttpSecurity extends ServerHttpSecurity implements ApplicationContextAware { + + @Override + public void setApplicationContext(ApplicationContext applicationContext) { + super.setApplicationContext(applicationContext); + } + + } + + } + +} diff --git a/spring-boot-project/spring-boot-autoconfigure/src/test/java/org/springframework/boot/autoconfigure/security/oauth2/client/servlet/OAuth2ClientAutoConfigurationTests.java b/spring-boot-project/spring-boot-autoconfigure/src/test/java/org/springframework/boot/autoconfigure/security/oauth2/client/servlet/OAuth2ClientAutoConfigurationTests.java new file mode 100644 index 0000000000..06811fd29c --- /dev/null +++ b/spring-boot-project/spring-boot-autoconfigure/src/test/java/org/springframework/boot/autoconfigure/security/oauth2/client/servlet/OAuth2ClientAutoConfigurationTests.java @@ -0,0 +1,103 @@ +/* + * Copyright 2012-2025 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.boot.autoconfigure.security.oauth2.client.servlet; + +import org.junit.jupiter.api.Test; + +import org.springframework.boot.autoconfigure.AutoConfigurations; +import org.springframework.boot.autoconfigure.security.oauth2.client.OAuth2ClientAutoConfiguration; +import org.springframework.boot.test.context.runner.ApplicationContextRunner; +import org.springframework.security.oauth2.client.OAuth2AuthorizedClientService; +import org.springframework.security.oauth2.client.registration.ClientRegistration; +import org.springframework.security.oauth2.client.registration.ClientRegistrationRepository; +import org.springframework.security.oauth2.client.registration.InMemoryClientRegistrationRepository; +import org.springframework.security.oauth2.core.AuthorizationGrantType; + +import static org.assertj.core.api.Assertions.assertThat; +import static org.mockito.Mockito.mock; + +/** + * Tests for {@link OAuth2ClientAutoConfiguration}. + * + * @author Madhura Bhave + * @author Andy Wilkinson + */ +class OAuth2ClientAutoConfigurationTests { + + private static final String REGISTRATION_PREFIX = "spring.security.oauth2.client.registration"; + + private final ApplicationContextRunner contextRunner = new ApplicationContextRunner() + .withConfiguration(AutoConfigurations.of(OAuth2ClientAutoConfiguration.class)); + + @Test + void beansShouldNotBeCreatedWhenPropertiesAbsent() { + this.contextRunner.run((context) -> assertThat(context).doesNotHaveBean(ClientRegistrationRepository.class) + .doesNotHaveBean(OAuth2AuthorizedClientService.class)); + } + + @Test + void beansAreCreatedWhenPropertiesPresent() { + this.contextRunner + .withPropertyValues(REGISTRATION_PREFIX + ".foo.client-id=abcd", + REGISTRATION_PREFIX + ".foo.client-secret=secret", REGISTRATION_PREFIX + ".foo.provider=github") + .run((context) -> { + assertThat(context).hasSingleBean(ClientRegistrationRepository.class); + assertThat(context).hasSingleBean(OAuth2AuthorizedClientService.class); + ClientRegistrationRepository repository = context.getBean(ClientRegistrationRepository.class); + ClientRegistration registration = repository.findByRegistrationId("foo"); + assertThat(registration).isNotNull(); + assertThat(registration.getClientSecret()).isEqualTo("secret"); + }); + } + + @Test + void clientServiceBeanIsConditionalOnMissingBean() { + this.contextRunner + .withBean("testAuthorizedClientService", OAuth2AuthorizedClientService.class, + () -> mock(OAuth2AuthorizedClientService.class)) + .run((context) -> { + assertThat(context).hasSingleBean(OAuth2AuthorizedClientService.class); + assertThat(context).hasBean("testAuthorizedClientService"); + }); + } + + @Test + void clientServiceBeanIsCreatedWithUserDefinedClientRegistrationRepository() { + this.contextRunner + .withBean(ClientRegistrationRepository.class, + () -> new InMemoryClientRegistrationRepository(getClientRegistration("test", "test"))) + .run((context) -> assertThat(context).hasSingleBean(OAuth2AuthorizedClientService.class)); + } + + private ClientRegistration getClientRegistration(String id, String userInfoUri) { + ClientRegistration.Builder builder = ClientRegistration.withRegistrationId(id); + builder.clientName("foo") + .clientId("foo") + .clientAuthenticationMethod( + org.springframework.security.oauth2.core.ClientAuthenticationMethod.CLIENT_SECRET_BASIC) + .authorizationGrantType(AuthorizationGrantType.AUTHORIZATION_CODE) + .scope("read") + .clientSecret("secret") + .redirectUri("https://redirect-uri.com") + .authorizationUri("https://authorization-uri.com") + .tokenUri("https://token-uri.com") + .userInfoUri(userInfoUri) + .userNameAttributeName("login"); + return builder.build(); + } + +} diff --git a/spring-boot-project/spring-boot-autoconfigure/src/test/java/org/springframework/boot/autoconfigure/security/oauth2/client/servlet/OAuth2ClientRegistrationRepositoryConfigurationTests.java b/spring-boot-project/spring-boot-autoconfigure/src/test/java/org/springframework/boot/autoconfigure/security/oauth2/client/servlet/OAuth2ClientRegistrationRepositoryConfigurationTests.java deleted file mode 100644 index ba567dbc58..0000000000 --- a/spring-boot-project/spring-boot-autoconfigure/src/test/java/org/springframework/boot/autoconfigure/security/oauth2/client/servlet/OAuth2ClientRegistrationRepositoryConfigurationTests.java +++ /dev/null @@ -1,57 +0,0 @@ -/* - * Copyright 2012-2023 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.boot.autoconfigure.security.oauth2.client.servlet; - -import org.junit.jupiter.api.Test; - -import org.springframework.boot.test.context.runner.ApplicationContextRunner; -import org.springframework.security.oauth2.client.registration.ClientRegistration; -import org.springframework.security.oauth2.client.registration.ClientRegistrationRepository; - -import static org.assertj.core.api.Assertions.assertThat; - -/** - * Tests for {@link OAuth2ClientRegistrationRepositoryConfiguration}. - * - * @author Madhura Bhave - */ -class OAuth2ClientRegistrationRepositoryConfigurationTests { - - private final ApplicationContextRunner contextRunner = new ApplicationContextRunner(); - - private static final String REGISTRATION_PREFIX = "spring.security.oauth2.client.registration"; - - @Test - void clientRegistrationRepositoryBeanShouldNotBeCreatedWhenPropertiesAbsent() { - this.contextRunner.withUserConfiguration(OAuth2ClientRegistrationRepositoryConfiguration.class) - .run((context) -> assertThat(context).doesNotHaveBean(ClientRegistrationRepository.class)); - } - - @Test - void clientRegistrationRepositoryBeanShouldBeCreatedWhenPropertiesPresent() { - this.contextRunner.withUserConfiguration(OAuth2ClientRegistrationRepositoryConfiguration.class) - .withPropertyValues(REGISTRATION_PREFIX + ".foo.client-id=abcd", - REGISTRATION_PREFIX + ".foo.client-secret=secret", REGISTRATION_PREFIX + ".foo.provider=github") - .run((context) -> { - ClientRegistrationRepository repository = context.getBean(ClientRegistrationRepository.class); - ClientRegistration registration = repository.findByRegistrationId("foo"); - assertThat(registration).isNotNull(); - assertThat(registration.getClientSecret()).isEqualTo("secret"); - }); - } - -} diff --git a/spring-boot-project/spring-boot-autoconfigure/src/test/java/org/springframework/boot/autoconfigure/security/oauth2/client/servlet/OAuth2WebSecurityConfigurationTests.java b/spring-boot-project/spring-boot-autoconfigure/src/test/java/org/springframework/boot/autoconfigure/security/oauth2/client/servlet/OAuth2ClientWebSecurityAutoConfigurationTests.java similarity index 72% rename from spring-boot-project/spring-boot-autoconfigure/src/test/java/org/springframework/boot/autoconfigure/security/oauth2/client/servlet/OAuth2WebSecurityConfigurationTests.java rename to spring-boot-project/spring-boot-autoconfigure/src/test/java/org/springframework/boot/autoconfigure/security/oauth2/client/servlet/OAuth2ClientWebSecurityAutoConfigurationTests.java index b9cf9f8b6b..95a57db122 100644 --- a/spring-boot-project/spring-boot-autoconfigure/src/test/java/org/springframework/boot/autoconfigure/security/oauth2/client/servlet/OAuth2WebSecurityConfigurationTests.java +++ b/spring-boot-project/spring-boot-autoconfigure/src/test/java/org/springframework/boot/autoconfigure/security/oauth2/client/servlet/OAuth2ClientWebSecurityAutoConfigurationTests.java @@ -1,5 +1,5 @@ /* - * Copyright 2012-2023 the original author or authors. + * Copyright 2012-2025 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. @@ -53,77 +53,74 @@ import org.springframework.web.filter.CompositeFilter; import static org.assertj.core.api.Assertions.assertThat; /** - * Tests for {@link OAuth2WebSecurityConfiguration}. + * Tests for {@link OAuth2ClientWebSecurityAutoConfiguration}. * * @author Madhura Bhave + * @author Andy Wilkinson */ -class OAuth2WebSecurityConfigurationTests { +class OAuth2ClientWebSecurityAutoConfigurationTests { - private final WebApplicationContextRunner contextRunner = new WebApplicationContextRunner(); + private final WebApplicationContextRunner contextRunner = new WebApplicationContextRunner() + .withConfiguration(AutoConfigurations.of(OAuth2ClientWebSecurityAutoConfiguration.class)); @Test - void securityConfigurerConfiguresOAuth2Login() { + void autoConfigurationIsConditionalOnAuthorizedClientService() { this.contextRunner - .withUserConfiguration(ClientRegistrationRepositoryConfiguration.class, - OAuth2WebSecurityConfiguration.class) - .run((context) -> { - ClientRegistrationRepository expected = context.getBean(ClientRegistrationRepository.class); - ClientRegistrationRepository actual = (ClientRegistrationRepository) ReflectionTestUtils.getField( - getSecurityFilters(context, OAuth2LoginAuthenticationFilter.class).get(0), - "clientRegistrationRepository"); - assertThat(isEqual(expected.findByRegistrationId("first"), actual.findByRegistrationId("first"))) - .isTrue(); - assertThat(isEqual(expected.findByRegistrationId("second"), actual.findByRegistrationId("second"))) - .isTrue(); - }); - } - - @Test - void securityConfigurerConfiguresAuthorizationCode() { - this.contextRunner - .withUserConfiguration(ClientRegistrationRepositoryConfiguration.class, - OAuth2WebSecurityConfiguration.class) - .run((context) -> { - ClientRegistrationRepository expected = context.getBean(ClientRegistrationRepository.class); - ClientRegistrationRepository actual = (ClientRegistrationRepository) ReflectionTestUtils.getField( - getSecurityFilters(context, OAuth2AuthorizationCodeGrantFilter.class).get(0), - "clientRegistrationRepository"); - assertThat(isEqual(expected.findByRegistrationId("first"), actual.findByRegistrationId("first"))) - .isTrue(); - assertThat(isEqual(expected.findByRegistrationId("second"), actual.findByRegistrationId("second"))) - .isTrue(); - }); - } - - @Test - void securityConfigurerBacksOffWhenClientRegistrationBeanAbsent() { - this.contextRunner.withUserConfiguration(TestConfig.class, OAuth2WebSecurityConfiguration.class) - .run((context) -> { - assertThat(getSecurityFilters(context, OAuth2LoginAuthenticationFilter.class)).isEmpty(); - assertThat(getSecurityFilters(context, OAuth2AuthorizationCodeGrantFilter.class)).isEmpty(); - }); - } - - @Test - void configurationRegistersAuthorizedClientServiceBean() { - this.contextRunner - .withUserConfiguration(ClientRegistrationRepositoryConfiguration.class, - OAuth2WebSecurityConfiguration.class) - .run((context) -> assertThat(context).hasSingleBean(OAuth2AuthorizedClientService.class)); + .run((context) -> assertThat(context).doesNotHaveBean(OAuth2ClientWebSecurityAutoConfiguration.class)); } @Test void configurationRegistersAuthorizedClientRepositoryBean() { - this.contextRunner - .withUserConfiguration(ClientRegistrationRepositoryConfiguration.class, - OAuth2WebSecurityConfiguration.class) + this.contextRunner.withUserConfiguration(OAuth2AuthorizedClientServiceConfiguration.class) .run((context) -> assertThat(context).hasSingleBean(OAuth2AuthorizedClientRepository.class)); } + @Test + void authorizedClientRepositoryBeanIsConditionalOnMissingBean() { + this.contextRunner.withUserConfiguration(OAuth2AuthorizedClientRepositoryConfiguration.class).run((context) -> { + assertThat(context).hasSingleBean(OAuth2AuthorizedClientRepository.class); + assertThat(context).hasBean("testAuthorizedClientRepository"); + }); + } + + @Test + void securityConfigurerConfiguresOAuth2Login() { + this.contextRunner.withUserConfiguration(OAuth2AuthorizedClientServiceConfiguration.class).run((context) -> { + ClientRegistrationRepository expected = context.getBean(ClientRegistrationRepository.class); + ClientRegistrationRepository actual = (ClientRegistrationRepository) ReflectionTestUtils.getField( + getSecurityFilters(context, OAuth2LoginAuthenticationFilter.class).get(0), + "clientRegistrationRepository"); + assertThat(isEqual(expected.findByRegistrationId("first"), actual.findByRegistrationId("first"))).isTrue(); + assertThat(isEqual(expected.findByRegistrationId("second"), actual.findByRegistrationId("second"))) + .isTrue(); + }); + } + + @Test + void securityConfigurerConfiguresAuthorizationCode() { + this.contextRunner.withUserConfiguration(OAuth2AuthorizedClientServiceConfiguration.class).run((context) -> { + ClientRegistrationRepository expected = context.getBean(ClientRegistrationRepository.class); + ClientRegistrationRepository actual = (ClientRegistrationRepository) ReflectionTestUtils.getField( + getSecurityFilters(context, OAuth2AuthorizationCodeGrantFilter.class).get(0), + "clientRegistrationRepository"); + assertThat(isEqual(expected.findByRegistrationId("first"), actual.findByRegistrationId("first"))).isTrue(); + assertThat(isEqual(expected.findByRegistrationId("second"), actual.findByRegistrationId("second"))) + .isTrue(); + }); + } + + @Test + void securityConfigurerBacksOffWhenClientRegistrationBeanAbsent() { + this.contextRunner.withUserConfiguration(TestConfig.class).run((context) -> { + assertThat(getSecurityFilters(context, OAuth2LoginAuthenticationFilter.class)).isEmpty(); + assertThat(getSecurityFilters(context, OAuth2AuthorizationCodeGrantFilter.class)).isEmpty(); + }); + } + @Test void securityFilterChainConfigBacksOffWhenOtherSecurityFilterChainBeanPresent() { this.contextRunner.withConfiguration(AutoConfigurations.of(WebMvcAutoConfiguration.class)) - .withUserConfiguration(TestSecurityFilterChainConfiguration.class, OAuth2WebSecurityConfiguration.class) + .withUserConfiguration(TestSecurityFilterChainConfiguration.class) .run((context) -> { assertThat(getSecurityFilters(context, OAuth2LoginAuthenticationFilter.class)).isEmpty(); assertThat(getSecurityFilters(context, OAuth2AuthorizationCodeGrantFilter.class)).isEmpty(); @@ -133,9 +130,7 @@ class OAuth2WebSecurityConfigurationTests { @Test void securityFilterChainConfigConditionalOnSecurityFilterChainClass() { - this.contextRunner - .withUserConfiguration(ClientRegistrationRepositoryConfiguration.class, - OAuth2WebSecurityConfiguration.class) + this.contextRunner.withUserConfiguration(ClientRegistrationRepositoryConfiguration.class) .withClassLoader(new FilteredClassLoader(SecurityFilterChain.class)) .run((context) -> { assertThat(getSecurityFilters(context, OAuth2LoginAuthenticationFilter.class)).isEmpty(); @@ -143,28 +138,6 @@ class OAuth2WebSecurityConfigurationTests { }); } - @Test - void authorizedClientServiceBeanIsConditionalOnMissingBean() { - this.contextRunner - .withUserConfiguration(OAuth2AuthorizedClientServiceConfiguration.class, - OAuth2WebSecurityConfiguration.class) - .run((context) -> { - assertThat(context).hasSingleBean(OAuth2AuthorizedClientService.class); - assertThat(context).hasBean("testAuthorizedClientService"); - }); - } - - @Test - void authorizedClientRepositoryBeanIsConditionalOnMissingBean() { - this.contextRunner - .withUserConfiguration(OAuth2AuthorizedClientRepositoryConfiguration.class, - OAuth2WebSecurityConfiguration.class) - .run((context) -> { - assertThat(context).hasSingleBean(OAuth2AuthorizedClientRepository.class); - assertThat(context).hasBean("testAuthorizedClientRepository"); - }); - } - private List getSecurityFilters(AssertableWebApplicationContext context, Class filter) { return getSecurityFilterChain(context).getFilters().stream().filter(filter::isInstance).toList(); } @@ -219,6 +192,30 @@ class OAuth2WebSecurityConfigurationTests { } + @Configuration(proxyBeanMethods = false) + @Import(ClientRegistrationRepositoryConfiguration.class) + static class OAuth2AuthorizedClientServiceConfiguration { + + @Bean + InMemoryOAuth2AuthorizedClientService authorizedClientService( + ClientRegistrationRepository clientRegistrationRepository) { + return new InMemoryOAuth2AuthorizedClientService(clientRegistrationRepository); + } + + } + + @Configuration(proxyBeanMethods = false) + @Import(OAuth2AuthorizedClientServiceConfiguration.class) + static class OAuth2AuthorizedClientRepositoryConfiguration { + + @Bean + OAuth2AuthorizedClientRepository testAuthorizedClientRepository( + OAuth2AuthorizedClientService authorizedClientService) { + return new AuthenticatedPrincipalOAuth2AuthorizedClientRepository(authorizedClientService); + } + + } + @Configuration(proxyBeanMethods = false) @Import(TestConfig.class) static class ClientRegistrationRepositoryConfiguration { @@ -251,7 +248,7 @@ class OAuth2WebSecurityConfigurationTests { } @Configuration(proxyBeanMethods = false) - @Import(ClientRegistrationRepositoryConfiguration.class) + @Import(OAuth2AuthorizedClientServiceConfiguration.class) static class TestSecurityFilterChainConfiguration { @Bean @@ -264,28 +261,4 @@ class OAuth2WebSecurityConfigurationTests { } - @Configuration(proxyBeanMethods = false) - @Import(ClientRegistrationRepositoryConfiguration.class) - static class OAuth2AuthorizedClientServiceConfiguration { - - @Bean - OAuth2AuthorizedClientService testAuthorizedClientService( - ClientRegistrationRepository clientRegistrationRepository) { - return new InMemoryOAuth2AuthorizedClientService(clientRegistrationRepository); - } - - } - - @Configuration(proxyBeanMethods = false) - @Import(ClientRegistrationRepositoryConfiguration.class) - static class OAuth2AuthorizedClientRepositoryConfiguration { - - @Bean - OAuth2AuthorizedClientRepository testAuthorizedClientRepository( - OAuth2AuthorizedClientService authorizedClientService) { - return new AuthenticatedPrincipalOAuth2AuthorizedClientRepository(authorizedClientService); - } - - } - } diff --git a/spring-boot-project/spring-boot-test-autoconfigure/src/main/resources/META-INF/spring/org.springframework.boot.test.autoconfigure.web.reactive.AutoConfigureWebTestClient.imports b/spring-boot-project/spring-boot-test-autoconfigure/src/main/resources/META-INF/spring/org.springframework.boot.test.autoconfigure.web.reactive.AutoConfigureWebTestClient.imports index 687e6fe7d6..593e8e6bdc 100644 --- a/spring-boot-project/spring-boot-test-autoconfigure/src/main/resources/META-INF/spring/org.springframework.boot.test.autoconfigure.web.reactive.AutoConfigureWebTestClient.imports +++ b/spring-boot-project/spring-boot-test-autoconfigure/src/main/resources/META-INF/spring/org.springframework.boot.test.autoconfigure.web.reactive.AutoConfigureWebTestClient.imports @@ -1,5 +1,6 @@ # AutoConfigureWebClient auto-configuration imports org.springframework.boot.autoconfigure.security.oauth2.client.reactive.ReactiveOAuth2ClientAutoConfiguration +org.springframework.boot.autoconfigure.security.oauth2.client.reactive.ReactiveOAuth2ClientWebSecurityAutoConfiguration org.springframework.boot.autoconfigure.security.oauth2.resource.reactive.ReactiveOAuth2ResourceServerAutoConfiguration org.springframework.boot.autoconfigure.security.reactive.ReactiveSecurityAutoConfiguration org.springframework.boot.autoconfigure.security.reactive.ReactiveUserDetailsServiceAutoConfiguration diff --git a/spring-boot-project/spring-boot-test-autoconfigure/src/main/resources/META-INF/spring/org.springframework.boot.test.autoconfigure.web.servlet.AutoConfigureMockMvc.imports b/spring-boot-project/spring-boot-test-autoconfigure/src/main/resources/META-INF/spring/org.springframework.boot.test.autoconfigure.web.servlet.AutoConfigureMockMvc.imports index 5e7b2b6573..71e5650644 100644 --- a/spring-boot-project/spring-boot-test-autoconfigure/src/main/resources/META-INF/spring/org.springframework.boot.test.autoconfigure.web.servlet.AutoConfigureMockMvc.imports +++ b/spring-boot-project/spring-boot-test-autoconfigure/src/main/resources/META-INF/spring/org.springframework.boot.test.autoconfigure.web.servlet.AutoConfigureMockMvc.imports @@ -2,8 +2,9 @@ org.springframework.boot.test.autoconfigure.web.servlet.MockMvcAutoConfiguration org.springframework.boot.test.autoconfigure.web.servlet.MockMvcWebClientAutoConfiguration org.springframework.boot.test.autoconfigure.web.servlet.MockMvcWebDriverAutoConfiguration -org.springframework.boot.autoconfigure.security.oauth2.client.servlet.OAuth2ClientAutoConfiguration +org.springframework.boot.autoconfigure.security.oauth2.client.OAuth2ClientAutoConfiguration org.springframework.boot.autoconfigure.security.oauth2.resource.servlet.OAuth2ResourceServerAutoConfiguration +org.springframework.boot.autoconfigure.security.oauth2.client.servlet.OAuth2ClientWebSecurityAutoConfiguration org.springframework.boot.autoconfigure.security.servlet.SecurityAutoConfiguration org.springframework.boot.autoconfigure.security.servlet.SecurityFilterAutoConfiguration org.springframework.boot.autoconfigure.security.servlet.UserDetailsServiceAutoConfiguration diff --git a/spring-boot-project/spring-boot-test-autoconfigure/src/test/java/org/springframework/boot/test/autoconfigure/web/servlet/WebMvcTestAutoConfigurationIntegrationTests.java b/spring-boot-project/spring-boot-test-autoconfigure/src/test/java/org/springframework/boot/test/autoconfigure/web/servlet/WebMvcTestAutoConfigurationIntegrationTests.java index c6d3352278..a502ab6134 100644 --- a/spring-boot-project/spring-boot-test-autoconfigure/src/test/java/org/springframework/boot/test/autoconfigure/web/servlet/WebMvcTestAutoConfigurationIntegrationTests.java +++ b/spring-boot-project/spring-boot-test-autoconfigure/src/test/java/org/springframework/boot/test/autoconfigure/web/servlet/WebMvcTestAutoConfigurationIntegrationTests.java @@ -1,5 +1,5 @@ /* - * Copyright 2012-2023 the original author or authors. + * Copyright 2012-2025 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. @@ -22,7 +22,7 @@ import org.springframework.beans.factory.annotation.Autowired; import org.springframework.boot.autoconfigure.freemarker.FreeMarkerAutoConfiguration; import org.springframework.boot.autoconfigure.groovy.template.GroovyTemplateAutoConfiguration; import org.springframework.boot.autoconfigure.mustache.MustacheAutoConfiguration; -import org.springframework.boot.autoconfigure.security.oauth2.client.servlet.OAuth2ClientAutoConfiguration; +import org.springframework.boot.autoconfigure.security.oauth2.client.OAuth2ClientAutoConfiguration; import org.springframework.boot.autoconfigure.security.oauth2.resource.servlet.OAuth2ResourceServerAutoConfiguration; import org.springframework.boot.autoconfigure.task.TaskExecutionAutoConfiguration; import org.springframework.boot.autoconfigure.thymeleaf.ThymeleafAutoConfiguration;