From ecc670772a701a6c90b9b29f493f45f03d76512d Mon Sep 17 00:00:00 2001 From: Andy Wilkinson Date: Wed, 20 Sep 2023 17:27:33 +0100 Subject: [PATCH] Make user details service auto-configs back off more readily Previously auto-configuration of a user details service (imperative or reactive) would only back off on the presence of certain beans. This led to situations where the im-memory service was auto-configured and the default password was logged even though another authentication mechanism was in use. This commit updates the auto-configuration so that it backs off when depending on Spring Security's OAuth2 Client and OAuth2 Resource Server modules. In the imperative case it will also back off when depending on the SAML 2 provider. Closes gh-35338 --- ...activeHealthEndpointWebExtensionTests.java | 23 +++++++--- ...FoundryActuatorAutoConfigurationTests.java | 33 +++++++++----- ...intsAutoConfigurationIntegrationTests.java | 2 + ...mentWebSecurityAutoConfigurationTests.java | 18 ++++++-- ...stractEndpointRequestIntegrationTests.java | 13 ++++-- .../ReactiveOAuth2ClientConfigurations.java | 9 ++++ ...veUserDetailsServiceAutoConfiguration.java | 6 ++- .../UserDetailsServiceAutoConfiguration.java | 17 +++---- ...orizationServerAutoConfigurationTests.java | 6 ++- ...eactiveSecurityAutoConfigurationTests.java | 31 ++++++++----- ...rDetailsServiceAutoConfigurationTests.java | 42 ++++++++++------- ...RSocketSecurityAutoConfigurationTests.java | 22 +++++++-- ...ConfigurationEarlyInitializationTests.java | 7 ++- ...rDetailsServiceAutoConfigurationTests.java | 45 ++++++++++++++----- .../docs/asciidoc/web/spring-security.adoc | 19 +++++++- 15 files changed, 215 insertions(+), 78 deletions(-) diff --git a/spring-boot-project/spring-boot-actuator-autoconfigure/src/test/java/org/springframework/boot/actuate/autoconfigure/cloudfoundry/reactive/CloudFoundryReactiveHealthEndpointWebExtensionTests.java b/spring-boot-project/spring-boot-actuator-autoconfigure/src/test/java/org/springframework/boot/actuate/autoconfigure/cloudfoundry/reactive/CloudFoundryReactiveHealthEndpointWebExtensionTests.java index e4eeaedbd9..9c68348c9f 100644 --- a/spring-boot-project/spring-boot-actuator-autoconfigure/src/test/java/org/springframework/boot/actuate/autoconfigure/cloudfoundry/reactive/CloudFoundryReactiveHealthEndpointWebExtensionTests.java +++ b/spring-boot-project/spring-boot-actuator-autoconfigure/src/test/java/org/springframework/boot/actuate/autoconfigure/cloudfoundry/reactive/CloudFoundryReactiveHealthEndpointWebExtensionTests.java @@ -35,10 +35,13 @@ import org.springframework.boot.autoconfigure.context.PropertyPlaceholderAutoCon import org.springframework.boot.autoconfigure.http.HttpMessageConvertersAutoConfiguration; import org.springframework.boot.autoconfigure.jackson.JacksonAutoConfiguration; import org.springframework.boot.autoconfigure.security.reactive.ReactiveSecurityAutoConfiguration; -import org.springframework.boot.autoconfigure.security.reactive.ReactiveUserDetailsServiceAutoConfiguration; import org.springframework.boot.autoconfigure.web.reactive.WebFluxAutoConfiguration; import org.springframework.boot.autoconfigure.web.reactive.function.client.WebClientAutoConfiguration; import org.springframework.boot.test.context.runner.ReactiveWebApplicationContextRunner; +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Configuration; +import org.springframework.security.core.userdetails.MapReactiveUserDetailsService; +import org.springframework.security.core.userdetails.User; import static org.assertj.core.api.Assertions.assertThat; @@ -52,15 +55,14 @@ class CloudFoundryReactiveHealthEndpointWebExtensionTests { private final ReactiveWebApplicationContextRunner contextRunner = new ReactiveWebApplicationContextRunner() .withPropertyValues("VCAP_APPLICATION={}") .withConfiguration(AutoConfigurations.of(ReactiveSecurityAutoConfiguration.class, - ReactiveUserDetailsServiceAutoConfiguration.class, WebFluxAutoConfiguration.class, - JacksonAutoConfiguration.class, HttpMessageConvertersAutoConfiguration.class, - PropertyPlaceholderAutoConfiguration.class, + WebFluxAutoConfiguration.class, JacksonAutoConfiguration.class, + HttpMessageConvertersAutoConfiguration.class, PropertyPlaceholderAutoConfiguration.class, ReactiveCloudFoundryActuatorAutoConfigurationTests.WebClientCustomizerConfig.class, WebClientAutoConfiguration.class, ManagementContextAutoConfiguration.class, EndpointAutoConfiguration.class, WebEndpointAutoConfiguration.class, HealthContributorAutoConfiguration.class, HealthEndpointAutoConfiguration.class, ReactiveCloudFoundryActuatorAutoConfiguration.class)) - .withUserConfiguration(TestHealthIndicator.class); + .withUserConfiguration(TestHealthIndicator.class, UserDetailsServiceConfiguration.class); @Test void healthComponentsAlwaysPresent() { @@ -82,4 +84,15 @@ class CloudFoundryReactiveHealthEndpointWebExtensionTests { } + @Configuration(proxyBeanMethods = false) + static class UserDetailsServiceConfiguration { + + @Bean + MapReactiveUserDetailsService userDetailsService() { + return new MapReactiveUserDetailsService( + User.withUsername("alice").password("secret").roles("admin").build()); + } + + } + } diff --git a/spring-boot-project/spring-boot-actuator-autoconfigure/src/test/java/org/springframework/boot/actuate/autoconfigure/cloudfoundry/reactive/ReactiveCloudFoundryActuatorAutoConfigurationTests.java b/spring-boot-project/spring-boot-actuator-autoconfigure/src/test/java/org/springframework/boot/actuate/autoconfigure/cloudfoundry/reactive/ReactiveCloudFoundryActuatorAutoConfigurationTests.java index 2f4c99c0bc..e9424f872c 100644 --- a/spring-boot-project/spring-boot-actuator-autoconfigure/src/test/java/org/springframework/boot/actuate/autoconfigure/cloudfoundry/reactive/ReactiveCloudFoundryActuatorAutoConfigurationTests.java +++ b/spring-boot-project/spring-boot-actuator-autoconfigure/src/test/java/org/springframework/boot/actuate/autoconfigure/cloudfoundry/reactive/ReactiveCloudFoundryActuatorAutoConfigurationTests.java @@ -50,7 +50,6 @@ import org.springframework.boot.autoconfigure.http.HttpMessageConvertersAutoConf import org.springframework.boot.autoconfigure.info.ProjectInfoAutoConfiguration; import org.springframework.boot.autoconfigure.jackson.JacksonAutoConfiguration; import org.springframework.boot.autoconfigure.security.reactive.ReactiveSecurityAutoConfiguration; -import org.springframework.boot.autoconfigure.security.reactive.ReactiveUserDetailsServiceAutoConfiguration; import org.springframework.boot.autoconfigure.web.reactive.WebFluxAutoConfiguration; import org.springframework.boot.autoconfigure.web.reactive.function.client.WebClientAutoConfiguration; import org.springframework.boot.test.context.runner.ReactiveWebApplicationContextRunner; @@ -61,6 +60,8 @@ import org.springframework.context.annotation.Configuration; import org.springframework.http.HttpMethod; import org.springframework.mock.http.server.reactive.MockServerHttpRequest; import org.springframework.mock.web.server.MockServerWebExchange; +import org.springframework.security.core.userdetails.MapReactiveUserDetailsService; +import org.springframework.security.core.userdetails.User; import org.springframework.security.web.server.SecurityWebFilterChain; import org.springframework.security.web.server.WebFilterChainProxy; import org.springframework.test.util.ReflectionTestUtils; @@ -84,15 +85,16 @@ class ReactiveCloudFoundryActuatorAutoConfigurationTests { private static final String V3_JSON = ApiVersion.V3.getProducedMimeType().toString(); private final ReactiveWebApplicationContextRunner contextRunner = new ReactiveWebApplicationContextRunner() - .withConfiguration(AutoConfigurations.of(ReactiveSecurityAutoConfiguration.class, - ReactiveUserDetailsServiceAutoConfiguration.class, WebFluxAutoConfiguration.class, - JacksonAutoConfiguration.class, HttpMessageConvertersAutoConfiguration.class, - PropertyPlaceholderAutoConfiguration.class, WebClientCustomizerConfig.class, - WebClientAutoConfiguration.class, ManagementContextAutoConfiguration.class, - EndpointAutoConfiguration.class, WebEndpointAutoConfiguration.class, - HealthContributorAutoConfiguration.class, HealthEndpointAutoConfiguration.class, - InfoContributorAutoConfiguration.class, InfoEndpointAutoConfiguration.class, - ProjectInfoAutoConfiguration.class, ReactiveCloudFoundryActuatorAutoConfiguration.class)); + .withConfiguration( + AutoConfigurations.of(ReactiveSecurityAutoConfiguration.class, WebFluxAutoConfiguration.class, + JacksonAutoConfiguration.class, HttpMessageConvertersAutoConfiguration.class, + PropertyPlaceholderAutoConfiguration.class, WebClientCustomizerConfig.class, + WebClientAutoConfiguration.class, ManagementContextAutoConfiguration.class, + EndpointAutoConfiguration.class, WebEndpointAutoConfiguration.class, + HealthContributorAutoConfiguration.class, HealthEndpointAutoConfiguration.class, + InfoContributorAutoConfiguration.class, InfoEndpointAutoConfiguration.class, + ProjectInfoAutoConfiguration.class, ReactiveCloudFoundryActuatorAutoConfiguration.class)) + .withUserConfiguration(UserDetailsServiceConfiguration.class); private static final String BASE_PATH = "/cloudfoundryapplication"; @@ -358,4 +360,15 @@ class ReactiveCloudFoundryActuatorAutoConfigurationTests { } + @Configuration(proxyBeanMethods = false) + static class UserDetailsServiceConfiguration { + + @Bean + MapReactiveUserDetailsService userDetailsService() { + return new MapReactiveUserDetailsService( + User.withUsername("alice").password("secret").roles("admin").build()); + } + + } + } diff --git a/spring-boot-project/spring-boot-actuator-autoconfigure/src/test/java/org/springframework/boot/actuate/autoconfigure/integrationtest/WebEndpointsAutoConfigurationIntegrationTests.java b/spring-boot-project/spring-boot-actuator-autoconfigure/src/test/java/org/springframework/boot/actuate/autoconfigure/integrationtest/WebEndpointsAutoConfigurationIntegrationTests.java index 33aa8931dd..c131bd263a 100644 --- a/spring-boot-project/spring-boot-actuator-autoconfigure/src/test/java/org/springframework/boot/actuate/autoconfigure/integrationtest/WebEndpointsAutoConfigurationIntegrationTests.java +++ b/spring-boot-project/spring-boot-actuator-autoconfigure/src/test/java/org/springframework/boot/actuate/autoconfigure/integrationtest/WebEndpointsAutoConfigurationIntegrationTests.java @@ -42,6 +42,7 @@ import org.springframework.boot.autoconfigure.mongo.MongoReactiveAutoConfigurati import org.springframework.boot.context.annotation.UserConfigurations; import org.springframework.boot.test.context.runner.ReactiveWebApplicationContextRunner; import org.springframework.boot.test.context.runner.WebApplicationContextRunner; +import org.springframework.boot.testsupport.classpath.ClassPathExclusions; import static org.assertj.core.api.Assertions.assertThat; @@ -59,6 +60,7 @@ class WebEndpointsAutoConfigurationIntegrationTests { } @Test + @ClassPathExclusions({ "spring-security-oauth2-client-*.jar", "spring-security-oauth2-resource-server-*.jar" }) void healthEndpointReactiveWebExtensionIsAutoConfigured() { reactiveWebRunner() .run((context) -> assertThat(context).hasSingleBean(ReactiveHealthEndpointWebExtension.class)); diff --git a/spring-boot-project/spring-boot-actuator-autoconfigure/src/test/java/org/springframework/boot/actuate/autoconfigure/security/reactive/ReactiveManagementWebSecurityAutoConfigurationTests.java b/spring-boot-project/spring-boot-actuator-autoconfigure/src/test/java/org/springframework/boot/actuate/autoconfigure/security/reactive/ReactiveManagementWebSecurityAutoConfigurationTests.java index fbfe9f23ef..e41082b893 100644 --- a/spring-boot-project/spring-boot-actuator-autoconfigure/src/test/java/org/springframework/boot/actuate/autoconfigure/security/reactive/ReactiveManagementWebSecurityAutoConfigurationTests.java +++ b/spring-boot-project/spring-boot-actuator-autoconfigure/src/test/java/org/springframework/boot/actuate/autoconfigure/security/reactive/ReactiveManagementWebSecurityAutoConfigurationTests.java @@ -33,7 +33,6 @@ import org.springframework.boot.actuate.autoconfigure.info.InfoEndpointAutoConfi import org.springframework.boot.autoconfigure.AutoConfigurations; 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; import org.springframework.boot.autoconfigure.web.reactive.WebFluxAutoConfiguration; import org.springframework.boot.test.context.assertj.AssertableReactiveWebApplicationContext; import org.springframework.boot.test.context.runner.ReactiveWebApplicationContextRunner; @@ -48,6 +47,8 @@ import org.springframework.mock.http.server.reactive.MockServerHttpRequest; import org.springframework.mock.http.server.reactive.MockServerHttpResponse; import org.springframework.security.authentication.ReactiveAuthenticationManager; import org.springframework.security.config.web.server.ServerHttpSecurity; +import org.springframework.security.core.userdetails.MapReactiveUserDetailsService; +import org.springframework.security.core.userdetails.User; import org.springframework.security.web.server.SecurityWebFilterChain; import org.springframework.security.web.server.WebFilterChainProxy; import org.springframework.web.server.ServerWebExchange; @@ -70,8 +71,8 @@ class ReactiveManagementWebSecurityAutoConfigurationTests { HealthEndpointAutoConfiguration.class, InfoEndpointAutoConfiguration.class, WebFluxAutoConfiguration.class, EnvironmentEndpointAutoConfiguration.class, EndpointAutoConfiguration.class, WebEndpointAutoConfiguration.class, - ReactiveSecurityAutoConfiguration.class, ReactiveUserDetailsServiceAutoConfiguration.class, - ReactiveManagementWebSecurityAutoConfiguration.class)); + ReactiveSecurityAutoConfiguration.class, ReactiveManagementWebSecurityAutoConfiguration.class)) + .withUserConfiguration(UserDetailsServiceConfiguration.class); @Test void permitAllForHealth() { @@ -155,6 +156,17 @@ class ReactiveManagementWebSecurityAutoConfigurationTests { } + @Configuration(proxyBeanMethods = false) + static class UserDetailsServiceConfiguration { + + @Bean + MapReactiveUserDetailsService userDetailsService() { + return new MapReactiveUserDetailsService( + User.withUsername("alice").password("secret").roles("admin").build()); + } + + } + @Configuration(proxyBeanMethods = false) static class CustomSecurityConfiguration { diff --git a/spring-boot-project/spring-boot-actuator-autoconfigure/src/test/java/org/springframework/boot/actuate/autoconfigure/security/servlet/AbstractEndpointRequestIntegrationTests.java b/spring-boot-project/spring-boot-actuator-autoconfigure/src/test/java/org/springframework/boot/actuate/autoconfigure/security/servlet/AbstractEndpointRequestIntegrationTests.java index 35236a3c56..9417437d92 100644 --- a/spring-boot-project/spring-boot-actuator-autoconfigure/src/test/java/org/springframework/boot/actuate/autoconfigure/security/servlet/AbstractEndpointRequestIntegrationTests.java +++ b/spring-boot-project/spring-boot-actuator-autoconfigure/src/test/java/org/springframework/boot/actuate/autoconfigure/security/servlet/AbstractEndpointRequestIntegrationTests.java @@ -37,7 +37,6 @@ import org.springframework.boot.actuate.endpoint.web.annotation.ServletEndpoint; import org.springframework.boot.autoconfigure.AutoConfigurations; import org.springframework.boot.autoconfigure.jackson.JacksonAutoConfiguration; import org.springframework.boot.autoconfigure.security.servlet.SecurityAutoConfiguration; -import org.springframework.boot.autoconfigure.security.servlet.UserDetailsServiceAutoConfiguration; import org.springframework.boot.test.context.assertj.AssertableWebApplicationContext; import org.springframework.boot.test.context.runner.WebApplicationContextRunner; import org.springframework.boot.web.servlet.context.AnnotationConfigServletWebServerApplicationContext; @@ -45,6 +44,8 @@ import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; import org.springframework.http.HttpStatus; import org.springframework.security.config.annotation.web.builders.HttpSecurity; +import org.springframework.security.core.userdetails.User; +import org.springframework.security.provisioning.InMemoryUserDetailsManager; import org.springframework.security.web.SecurityFilterChain; import org.springframework.test.web.reactive.server.WebTestClient; @@ -100,8 +101,8 @@ abstract class AbstractEndpointRequestIntegrationTests { return createContextRunner().withPropertyValues("management.endpoints.web.exposure.include=*") .withUserConfiguration(BaseConfiguration.class, SecurityConfiguration.class) .withConfiguration(AutoConfigurations.of(JacksonAutoConfiguration.class, SecurityAutoConfiguration.class, - UserDetailsServiceAutoConfiguration.class, EndpointAutoConfiguration.class, - WebEndpointAutoConfiguration.class, ManagementContextAutoConfiguration.class)); + EndpointAutoConfiguration.class, WebEndpointAutoConfiguration.class, + ManagementContextAutoConfiguration.class)); } @@ -189,6 +190,12 @@ abstract class AbstractEndpointRequestIntegrationTests { @Configuration(proxyBeanMethods = false) static class SecurityConfiguration { + @Bean + InMemoryUserDetailsManager userDetailsManager() { + return new InMemoryUserDetailsManager( + User.withUsername("user").password("{noop}password").roles("admin").build()); + } + @Bean SecurityFilterChain securityFilterChain(HttpSecurity http) throws Exception { http.authorizeHttpRequests((requests) -> { 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 8eb3871b93..8d25fbc2e3 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 @@ -28,6 +28,7 @@ import org.springframework.boot.autoconfigure.security.oauth2.client.OAuth2Clien import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Conditional; import org.springframework.context.annotation.Configuration; +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; @@ -37,6 +38,7 @@ import org.springframework.security.oauth2.client.registration.ReactiveClientReg 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 org.springframework.security.web.server.WebFilterChainProxy; import static org.springframework.security.config.Customizer.withDefaults; @@ -92,6 +94,13 @@ class ReactiveOAuth2ClientConfigurations { return http.build(); } + @Configuration(proxyBeanMethods = false) + @ConditionalOnMissingBean(WebFilterChainProxy.class) + @EnableWebFluxSecurity + static class EnableWebFluxSecurityConfiguration { + + } + } } diff --git a/spring-boot-project/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/security/reactive/ReactiveUserDetailsServiceAutoConfiguration.java b/spring-boot-project/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/security/reactive/ReactiveUserDetailsServiceAutoConfiguration.java index 1c27555263..17d6e1315f 100644 --- a/spring-boot-project/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/security/reactive/ReactiveUserDetailsServiceAutoConfiguration.java +++ b/spring-boot-project/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/security/reactive/ReactiveUserDetailsServiceAutoConfiguration.java @@ -28,6 +28,7 @@ import org.springframework.boot.autoconfigure.condition.AnyNestedCondition; 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.ConditionalOnMissingClass; import org.springframework.boot.autoconfigure.condition.ConditionalOnWebApplication; import org.springframework.boot.autoconfigure.rsocket.RSocketMessagingAutoConfiguration; import org.springframework.boot.autoconfigure.security.SecurityProperties; @@ -57,11 +58,12 @@ import org.springframework.util.StringUtils; */ @AutoConfiguration(before = ReactiveSecurityAutoConfiguration.class, after = RSocketMessagingAutoConfiguration.class) @ConditionalOnClass({ ReactiveAuthenticationManager.class }) +@ConditionalOnMissingClass({ "org.springframework.security.oauth2.client.registration.ClientRegistrationRepository", + "org.springframework.security.oauth2.server.resource.introspection.ReactiveOpaqueTokenIntrospector" }) @ConditionalOnMissingBean( value = { ReactiveAuthenticationManager.class, ReactiveUserDetailsService.class, ReactiveAuthenticationManagerResolver.class }, - type = { "org.springframework.security.oauth2.jwt.ReactiveJwtDecoder", - "org.springframework.security.oauth2.server.resource.introspection.ReactiveOpaqueTokenIntrospector" }) + type = { "org.springframework.security.oauth2.jwt.ReactiveJwtDecoder" }) @Conditional(ReactiveUserDetailsServiceAutoConfiguration.ReactiveUserDetailsServiceCondition.class) @EnableConfigurationProperties(SecurityProperties.class) public class ReactiveUserDetailsServiceAutoConfiguration { diff --git a/spring-boot-project/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/security/servlet/UserDetailsServiceAutoConfiguration.java b/spring-boot-project/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/security/servlet/UserDetailsServiceAutoConfiguration.java index 55c3dec9a6..396b50856c 100644 --- a/spring-boot-project/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/security/servlet/UserDetailsServiceAutoConfiguration.java +++ b/spring-boot-project/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/security/servlet/UserDetailsServiceAutoConfiguration.java @@ -28,6 +28,7 @@ import org.springframework.boot.autoconfigure.EnableAutoConfiguration; 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.ConditionalOnMissingClass; import org.springframework.boot.autoconfigure.security.SecurityProperties; import org.springframework.context.annotation.Bean; import org.springframework.security.authentication.AuthenticationManager; @@ -43,9 +44,7 @@ import org.springframework.util.StringUtils; /** * {@link EnableAutoConfiguration Auto-configuration} for a Spring Security in-memory * {@link AuthenticationManager}. Adds an {@link InMemoryUserDetailsManager} with a - * default user and generated password. This can be disabled by providing a bean of type - * {@link AuthenticationManager}, {@link AuthenticationProvider} or - * {@link UserDetailsService}. + * default user and generated password. * * @author Dave Syer * @author Rob Winch @@ -54,14 +53,12 @@ import org.springframework.util.StringUtils; */ @AutoConfiguration @ConditionalOnClass(AuthenticationManager.class) +@ConditionalOnMissingClass({ "org.springframework.security.oauth2.client.registration.ClientRegistrationRepository", + "org.springframework.security.oauth2.server.resource.introspection.OpaqueTokenIntrospector", + "org.springframework.security.saml2.provider.service.registration.RelyingPartyRegistrationRepository" }) @ConditionalOnBean(ObjectPostProcessor.class) -@ConditionalOnMissingBean( - value = { AuthenticationManager.class, AuthenticationProvider.class, UserDetailsService.class, - AuthenticationManagerResolver.class }, - type = { "org.springframework.security.oauth2.jwt.JwtDecoder", - "org.springframework.security.oauth2.server.resource.introspection.OpaqueTokenIntrospector", - "org.springframework.security.oauth2.client.registration.ClientRegistrationRepository", - "org.springframework.security.saml2.provider.service.registration.RelyingPartyRegistrationRepository" }) +@ConditionalOnMissingBean(value = { AuthenticationManager.class, AuthenticationProvider.class, UserDetailsService.class, + AuthenticationManagerResolver.class }, type = "org.springframework.security.oauth2.jwt.JwtDecoder") public class UserDetailsServiceAutoConfiguration { private static final String NOOP_PASSWORD_PREFIX = "{noop}"; diff --git a/spring-boot-project/spring-boot-autoconfigure/src/test/java/org/springframework/boot/autoconfigure/security/oauth2/server/servlet/OAuth2AuthorizationServerAutoConfigurationTests.java b/spring-boot-project/spring-boot-autoconfigure/src/test/java/org/springframework/boot/autoconfigure/security/oauth2/server/servlet/OAuth2AuthorizationServerAutoConfigurationTests.java index 3f2a89e737..c1c043eecd 100644 --- a/spring-boot-project/spring-boot-autoconfigure/src/test/java/org/springframework/boot/autoconfigure/security/oauth2/server/servlet/OAuth2AuthorizationServerAutoConfigurationTests.java +++ b/spring-boot-project/spring-boot-autoconfigure/src/test/java/org/springframework/boot/autoconfigure/security/oauth2/server/servlet/OAuth2AuthorizationServerAutoConfigurationTests.java @@ -23,6 +23,7 @@ import org.springframework.boot.autoconfigure.security.servlet.SecurityAutoConfi import org.springframework.boot.autoconfigure.security.servlet.UserDetailsServiceAutoConfiguration; import org.springframework.boot.test.context.FilteredClassLoader; import org.springframework.boot.test.context.runner.WebApplicationContextRunner; +import org.springframework.boot.testsupport.classpath.ClassPathExclusions; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; import org.springframework.security.oauth2.core.AuthorizationGrantType; @@ -59,8 +60,11 @@ class OAuth2AuthorizationServerAutoConfigurationTests { } @Test + @ClassPathExclusions({ "spring-security-oauth2-client-*.jar", "spring-security-oauth2-resource-server-*.jar", + "spring-security-saml2-service-provider-*.jar" }) void autoConfigurationDoesNotCauseUserDetailsServiceToBackOff() { - this.contextRunner.run((context) -> assertThat(context).hasBean("inMemoryUserDetailsManager")); + this.contextRunner.run((context) -> assertThat(context).hasSingleBean(UserDetailsServiceAutoConfiguration.class) + .hasBean("inMemoryUserDetailsManager")); } @Test diff --git a/spring-boot-project/spring-boot-autoconfigure/src/test/java/org/springframework/boot/autoconfigure/security/reactive/ReactiveSecurityAutoConfigurationTests.java b/spring-boot-project/spring-boot-autoconfigure/src/test/java/org/springframework/boot/autoconfigure/security/reactive/ReactiveSecurityAutoConfigurationTests.java index 54bdfb739a..b046e89d97 100644 --- a/spring-boot-project/spring-boot-autoconfigure/src/test/java/org/springframework/boot/autoconfigure/security/reactive/ReactiveSecurityAutoConfigurationTests.java +++ b/spring-boot-project/spring-boot-autoconfigure/src/test/java/org/springframework/boot/autoconfigure/security/reactive/ReactiveSecurityAutoConfigurationTests.java @@ -26,6 +26,8 @@ import org.springframework.boot.test.context.runner.ReactiveWebApplicationContex import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; import org.springframework.security.config.annotation.web.reactive.EnableWebFluxSecurity; +import org.springframework.security.core.userdetails.MapReactiveUserDetailsService; +import org.springframework.security.core.userdetails.User; import org.springframework.security.web.server.WebFilterChainProxy; import org.springframework.web.reactive.config.WebFluxConfigurer; @@ -39,27 +41,24 @@ import static org.mockito.Mockito.mock; */ class ReactiveSecurityAutoConfigurationTests { - private final ReactiveWebApplicationContextRunner contextRunner = new ReactiveWebApplicationContextRunner(); + private final ReactiveWebApplicationContextRunner contextRunner = new ReactiveWebApplicationContextRunner() + .withConfiguration(AutoConfigurations.of(ReactiveSecurityAutoConfiguration.class)); @Test void backsOffWhenWebFilterChainProxyBeanPresent() { - this.contextRunner.withConfiguration(AutoConfigurations.of(ReactiveSecurityAutoConfiguration.class)) - .withUserConfiguration(WebFilterChainProxyConfiguration.class) + this.contextRunner.withUserConfiguration(WebFilterChainProxyConfiguration.class) .run((context) -> assertThat(context).hasSingleBean(WebFilterChainProxy.class)); } @Test void backsOffWhenReactiveAuthenticationManagerNotPresent() { - this.contextRunner.withConfiguration(AutoConfigurations.of(ReactiveSecurityAutoConfiguration.class)) - .run((context) -> assertThat(context).hasSingleBean(ReactiveSecurityAutoConfiguration.class) - .doesNotHaveBean(EnableWebFluxSecurityConfiguration.class)); + this.contextRunner.run((context) -> assertThat(context).hasSingleBean(ReactiveSecurityAutoConfiguration.class) + .doesNotHaveBean(EnableWebFluxSecurityConfiguration.class)); } @Test void enablesWebFluxSecurity() { - this.contextRunner - .withConfiguration(AutoConfigurations.of(ReactiveSecurityAutoConfiguration.class, - ReactiveUserDetailsServiceAutoConfiguration.class)) + this.contextRunner.withUserConfiguration(UserDetailsServiceConfiguration.class) .run((context) -> assertThat(context).getBean(WebFilterChainProxy.class).isNotNull()); } @@ -68,8 +67,7 @@ class ReactiveSecurityAutoConfigurationTests { this.contextRunner .withClassLoader(new FilteredClassLoader(Flux.class, EnableWebFluxSecurity.class, WebFilterChainProxy.class, WebFluxConfigurer.class)) - .withConfiguration(AutoConfigurations.of(ReactiveSecurityAutoConfiguration.class, - ReactiveUserDetailsServiceAutoConfiguration.class)) + .withUserConfiguration(UserDetailsServiceConfiguration.class) .run((context) -> assertThat(context).doesNotHaveBean(WebFilterChainProxy.class)); } @@ -83,4 +81,15 @@ class ReactiveSecurityAutoConfigurationTests { } + @Configuration(proxyBeanMethods = false) + static class UserDetailsServiceConfiguration { + + @Bean + MapReactiveUserDetailsService userDetailsService() { + return new MapReactiveUserDetailsService( + User.withUsername("alice").password("secret").roles("admin").build()); + } + + } + } diff --git a/spring-boot-project/spring-boot-autoconfigure/src/test/java/org/springframework/boot/autoconfigure/security/reactive/ReactiveUserDetailsServiceAutoConfigurationTests.java b/spring-boot-project/spring-boot-autoconfigure/src/test/java/org/springframework/boot/autoconfigure/security/reactive/ReactiveUserDetailsServiceAutoConfigurationTests.java index e2389c322a..96efa632a6 100644 --- a/spring-boot-project/spring-boot-autoconfigure/src/test/java/org/springframework/boot/autoconfigure/security/reactive/ReactiveUserDetailsServiceAutoConfigurationTests.java +++ b/spring-boot-project/spring-boot-autoconfigure/src/test/java/org/springframework/boot/autoconfigure/security/reactive/ReactiveUserDetailsServiceAutoConfigurationTests.java @@ -25,6 +25,7 @@ import org.springframework.boot.autoconfigure.rsocket.RSocketMessagingAutoConfig import org.springframework.boot.autoconfigure.rsocket.RSocketStrategiesAutoConfiguration; import org.springframework.boot.autoconfigure.security.SecurityProperties; import org.springframework.boot.context.properties.EnableConfigurationProperties; +import org.springframework.boot.test.context.FilteredClassLoader; import org.springframework.boot.test.context.runner.ApplicationContextRunner; import org.springframework.boot.test.context.runner.ReactiveWebApplicationContextRunner; import org.springframework.context.annotation.Bean; @@ -39,6 +40,7 @@ import org.springframework.security.core.userdetails.ReactiveUserDetailsService; import org.springframework.security.core.userdetails.User; import org.springframework.security.core.userdetails.UserDetails; import org.springframework.security.crypto.password.PasswordEncoder; +import org.springframework.security.oauth2.client.registration.ClientRegistrationRepository; import org.springframework.security.oauth2.jwt.ReactiveJwtDecoder; import org.springframework.security.oauth2.server.resource.introspection.ReactiveOpaqueTokenIntrospector; @@ -58,15 +60,21 @@ class ReactiveUserDetailsServiceAutoConfigurationTests { @Test void configuresADefaultUser() { - this.contextRunner.withUserConfiguration(TestSecurityConfiguration.class).run((context) -> { - ReactiveUserDetailsService userDetailsService = context.getBean(ReactiveUserDetailsService.class); - assertThat(userDetailsService.findByUsername("user").block(Duration.ofSeconds(30))).isNotNull(); - }); + this.contextRunner + .withClassLoader( + new FilteredClassLoader(ClientRegistrationRepository.class, ReactiveOpaqueTokenIntrospector.class)) + .withUserConfiguration(TestSecurityConfiguration.class) + .run((context) -> { + ReactiveUserDetailsService userDetailsService = context.getBean(ReactiveUserDetailsService.class); + assertThat(userDetailsService.findByUsername("user").block(Duration.ofSeconds(30))).isNotNull(); + }); } @Test void userDetailsServiceWhenRSocketConfigured() { new ApplicationContextRunner() + .withClassLoader( + new FilteredClassLoader(ClientRegistrationRepository.class, ReactiveOpaqueTokenIntrospector.class)) .withConfiguration(AutoConfigurations.of(ReactiveUserDetailsServiceAutoConfiguration.class, RSocketMessagingAutoConfiguration.class, RSocketStrategiesAutoConfiguration.class)) .withUserConfiguration(TestRSocketSecurityConfiguration.class) @@ -109,20 +117,21 @@ class ReactiveUserDetailsServiceAutoConfigurationTests { } @Test - void doesNotConfigureDefaultUserIfResourceServerWithOpaqueIsUsed() { - this.contextRunner.withUserConfiguration(ReactiveOpaqueTokenIntrospectorConfiguration.class).run((context) -> { - assertThat(context).hasSingleBean(ReactiveOpaqueTokenIntrospector.class); - assertThat(context).doesNotHaveBean(ReactiveUserDetailsService.class); - }); + void doesNotConfigureDefaultUserIfResourceServerIsPresent() { + this.contextRunner.run((context) -> assertThat(context).doesNotHaveBean(ReactiveUserDetailsService.class)); } @Test void userDetailsServiceWhenPasswordEncoderAbsentAndDefaultPassword() { - this.contextRunner.withUserConfiguration(TestSecurityConfiguration.class).run(((context) -> { - MapReactiveUserDetailsService userDetailsService = context.getBean(MapReactiveUserDetailsService.class); - String password = userDetailsService.findByUsername("user").block(Duration.ofSeconds(30)).getPassword(); - assertThat(password).startsWith("{noop}"); - })); + this.contextRunner + .withClassLoader( + new FilteredClassLoader(ClientRegistrationRepository.class, ReactiveOpaqueTokenIntrospector.class)) + .withUserConfiguration(TestSecurityConfiguration.class) + .run(((context) -> { + MapReactiveUserDetailsService userDetailsService = context.getBean(MapReactiveUserDetailsService.class); + String password = userDetailsService.findByUsername("user").block(Duration.ofSeconds(30)).getPassword(); + assertThat(password).startsWith("{noop}"); + })); } @Test @@ -142,7 +151,10 @@ class ReactiveUserDetailsServiceAutoConfigurationTests { } private void testPasswordEncoding(Class configClass, String providedPassword, String expectedPassword) { - this.contextRunner.withUserConfiguration(configClass) + this.contextRunner + .withClassLoader( + new FilteredClassLoader(ClientRegistrationRepository.class, ReactiveOpaqueTokenIntrospector.class)) + .withUserConfiguration(configClass) .withPropertyValues("spring.security.user.password=" + providedPassword) .run(((context) -> { MapReactiveUserDetailsService userDetailsService = context.getBean(MapReactiveUserDetailsService.class); diff --git a/spring-boot-project/spring-boot-autoconfigure/src/test/java/org/springframework/boot/autoconfigure/security/rsocket/RSocketSecurityAutoConfigurationTests.java b/spring-boot-project/spring-boot-autoconfigure/src/test/java/org/springframework/boot/autoconfigure/security/rsocket/RSocketSecurityAutoConfigurationTests.java index 95d8072690..a479bb5d1d 100644 --- a/spring-boot-project/spring-boot-autoconfigure/src/test/java/org/springframework/boot/autoconfigure/security/rsocket/RSocketSecurityAutoConfigurationTests.java +++ b/spring-boot-project/spring-boot-autoconfigure/src/test/java/org/springframework/boot/autoconfigure/security/rsocket/RSocketSecurityAutoConfigurationTests.java @@ -22,12 +22,15 @@ import org.junit.jupiter.api.Test; import org.springframework.boot.autoconfigure.AutoConfigurations; import org.springframework.boot.autoconfigure.rsocket.RSocketMessagingAutoConfiguration; import org.springframework.boot.autoconfigure.rsocket.RSocketStrategiesAutoConfiguration; -import org.springframework.boot.autoconfigure.security.reactive.ReactiveUserDetailsServiceAutoConfiguration; import org.springframework.boot.rsocket.server.RSocketServerCustomizer; import org.springframework.boot.test.context.FilteredClassLoader; import org.springframework.boot.test.context.runner.ApplicationContextRunner; +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Configuration; import org.springframework.messaging.rsocket.annotation.support.RSocketMessageHandler; import org.springframework.security.config.annotation.rsocket.RSocketSecurity; +import org.springframework.security.core.userdetails.MapReactiveUserDetailsService; +import org.springframework.security.core.userdetails.User; import org.springframework.security.messaging.handler.invocation.reactive.AuthenticationPrincipalArgumentResolver; import org.springframework.security.rsocket.core.SecuritySocketAcceptorInterceptor; @@ -42,9 +45,9 @@ import static org.assertj.core.api.Assertions.assertThat; class RSocketSecurityAutoConfigurationTests { private final ApplicationContextRunner contextRunner = new ApplicationContextRunner() - .withConfiguration(AutoConfigurations.of(ReactiveUserDetailsServiceAutoConfiguration.class, - RSocketSecurityAutoConfiguration.class, RSocketMessagingAutoConfiguration.class, - RSocketStrategiesAutoConfiguration.class)); + .withConfiguration(AutoConfigurations.of(RSocketSecurityAutoConfiguration.class, + RSocketMessagingAutoConfiguration.class, RSocketStrategiesAutoConfiguration.class)) + .withUserConfiguration(UserDetailsServiceConfiguration.class); @Test void autoConfigurationEnablesRSocketSecurity() { @@ -81,4 +84,15 @@ class RSocketSecurityAutoConfigurationTests { }); } + @Configuration(proxyBeanMethods = false) + static class UserDetailsServiceConfiguration { + + @Bean + MapReactiveUserDetailsService userDetailsService() { + return new MapReactiveUserDetailsService( + User.withUsername("alice").password("secret").roles("admin").build()); + } + + } + } diff --git a/spring-boot-project/spring-boot-autoconfigure/src/test/java/org/springframework/boot/autoconfigure/security/servlet/SecurityFilterAutoConfigurationEarlyInitializationTests.java b/spring-boot-project/spring-boot-autoconfigure/src/test/java/org/springframework/boot/autoconfigure/security/servlet/SecurityFilterAutoConfigurationEarlyInitializationTests.java index c186f9c009..40bcf8bddc 100644 --- a/spring-boot-project/spring-boot-autoconfigure/src/test/java/org/springframework/boot/autoconfigure/security/servlet/SecurityFilterAutoConfigurationEarlyInitializationTests.java +++ b/spring-boot-project/spring-boot-autoconfigure/src/test/java/org/springframework/boot/autoconfigure/security/servlet/SecurityFilterAutoConfigurationEarlyInitializationTests.java @@ -1,5 +1,5 @@ /* - * Copyright 2012-2019 the original author or authors. + * 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. @@ -37,6 +37,8 @@ import org.springframework.boot.test.system.CapturedOutput; import org.springframework.boot.test.system.OutputCaptureExtension; import org.springframework.boot.test.util.TestPropertyValues; import org.springframework.boot.test.web.client.TestRestTemplate; +import org.springframework.boot.testsupport.classpath.ClassPathExclusions; +import org.springframework.boot.testsupport.web.servlet.DirtiesUrlFactories; import org.springframework.boot.web.embedded.tomcat.TomcatServletWebServerFactory; import org.springframework.boot.web.servlet.context.AnnotationConfigServletWebServerApplicationContext; import org.springframework.context.annotation.Bean; @@ -63,6 +65,9 @@ class SecurityFilterAutoConfigurationEarlyInitializationTests { Pattern.MULTILINE); @Test + @DirtiesUrlFactories + @ClassPathExclusions({ "spring-security-oauth2-client-*.jar", "spring-security-oauth2-resource-server-*.jar", + "spring-security-saml2-service-provider-*.jar" }) void testSecurityFilterDoesNotCauseEarlyInitialization(CapturedOutput output) { try (AnnotationConfigServletWebServerApplicationContext context = new AnnotationConfigServletWebServerApplicationContext()) { TestPropertyValues.of("server.port:0").applyTo(context); diff --git a/spring-boot-project/spring-boot-autoconfigure/src/test/java/org/springframework/boot/autoconfigure/security/servlet/UserDetailsServiceAutoConfigurationTests.java b/spring-boot-project/spring-boot-autoconfigure/src/test/java/org/springframework/boot/autoconfigure/security/servlet/UserDetailsServiceAutoConfigurationTests.java index b0375a3afc..660a2ea972 100644 --- a/spring-boot-project/spring-boot-autoconfigure/src/test/java/org/springframework/boot/autoconfigure/security/servlet/UserDetailsServiceAutoConfigurationTests.java +++ b/spring-boot-project/spring-boot-autoconfigure/src/test/java/org/springframework/boot/autoconfigure/security/servlet/UserDetailsServiceAutoConfigurationTests.java @@ -17,6 +17,7 @@ package org.springframework.boot.autoconfigure.security.servlet; import java.util.Collections; +import java.util.function.Function; import org.junit.jupiter.api.Test; import org.junit.jupiter.api.extension.ExtendWith; @@ -24,6 +25,7 @@ import org.junit.jupiter.api.extension.ExtendWith; import org.springframework.boot.autoconfigure.AutoConfigurations; import org.springframework.boot.autoconfigure.security.SecurityProperties; import org.springframework.boot.context.properties.EnableConfigurationProperties; +import org.springframework.boot.test.context.FilteredClassLoader; import org.springframework.boot.test.context.runner.ApplicationContextRunner; import org.springframework.boot.test.system.CapturedOutput; import org.springframework.boot.test.system.OutputCaptureExtension; @@ -64,7 +66,7 @@ class UserDetailsServiceAutoConfigurationTests { @Test void testDefaultUsernamePassword(CapturedOutput output) { - this.contextRunner.run((context) -> { + this.contextRunner.with(noOtherFormsOfAuthenticationOnTheClasspath()).run((context) -> { UserDetailsService manager = context.getBean(UserDetailsService.class); assertThat(output).contains("Using generated security password:"); assertThat(manager.loadUserByUsername("user")).isNotNull(); @@ -126,11 +128,13 @@ class UserDetailsServiceAutoConfigurationTests { @Test void userDetailsServiceWhenPasswordEncoderAbsentAndDefaultPassword() { - this.contextRunner.withUserConfiguration(TestSecurityConfiguration.class).run(((context) -> { - InMemoryUserDetailsManager userDetailsService = context.getBean(InMemoryUserDetailsManager.class); - String password = userDetailsService.loadUserByUsername("user").getPassword(); - assertThat(password).startsWith("{noop}"); - })); + this.contextRunner.with(noOtherFormsOfAuthenticationOnTheClasspath()) + .withUserConfiguration(TestSecurityConfiguration.class) + .run(((context) -> { + InMemoryUserDetailsManager userDetailsService = context.getBean(InMemoryUserDetailsManager.class); + String password = userDetailsService.loadUserByUsername("user").getPassword(); + assertThat(password).startsWith("{noop}"); + })); } @Test @@ -150,20 +154,39 @@ class UserDetailsServiceAutoConfigurationTests { } @Test - void userDetailsServiceWhenClientRegistrationRepositoryBeanPresent() { - this.contextRunner.withUserConfiguration(TestConfigWithClientRegistrationRepository.class) + void userDetailsServiceWhenClientRegistrationRepositoryPresent() { + this.contextRunner + .withClassLoader( + new FilteredClassLoader(OpaqueTokenIntrospector.class, RelyingPartyRegistrationRepository.class)) + .run(((context) -> assertThat(context).doesNotHaveBean(InMemoryUserDetailsManager.class))); + } + + @Test + void userDetailsServiceWhenOpaqueTokenIntrospectorPresent() { + this.contextRunner + .withClassLoader(new FilteredClassLoader(ClientRegistrationRepository.class, + RelyingPartyRegistrationRepository.class)) .run(((context) -> assertThat(context).doesNotHaveBean(InMemoryUserDetailsManager.class))); } @Test - void userDetailsServiceWhenRelyingPartyRegistrationRepositoryBeanPresent() { + void userDetailsServiceWhenRelyingPartyRegistrationRepositoryPresent() { this.contextRunner - .withBean(RelyingPartyRegistrationRepository.class, () -> mock(RelyingPartyRegistrationRepository.class)) + .withClassLoader(new FilteredClassLoader(ClientRegistrationRepository.class, OpaqueTokenIntrospector.class)) .run(((context) -> assertThat(context).doesNotHaveBean(InMemoryUserDetailsManager.class))); } + private Function noOtherFormsOfAuthenticationOnTheClasspath() { + return (contextRunner) -> contextRunner + .withClassLoader(new FilteredClassLoader(ClientRegistrationRepository.class, OpaqueTokenIntrospector.class, + RelyingPartyRegistrationRepository.class)); + } + private void testPasswordEncoding(Class configClass, String providedPassword, String expectedPassword) { - this.contextRunner.withUserConfiguration(configClass) + this.contextRunner.with(noOtherFormsOfAuthenticationOnTheClasspath()) + .withClassLoader(new FilteredClassLoader(ClientRegistrationRepository.class, OpaqueTokenIntrospector.class, + RelyingPartyRegistrationRepository.class)) + .withUserConfiguration(configClass) .withPropertyValues("spring.security.user.password=" + providedPassword) .run(((context) -> { InMemoryUserDetailsManager userDetailsService = context.getBean(InMemoryUserDetailsManager.class); diff --git a/spring-boot-project/spring-boot-docs/src/docs/asciidoc/web/spring-security.adoc b/spring-boot-project/spring-boot-docs/src/docs/asciidoc/web/spring-security.adoc index 2fc187ddba..b893db2472 100644 --- a/spring-boot-project/spring-boot-docs/src/docs/asciidoc/web/spring-security.adoc +++ b/spring-boot-project/spring-boot-docs/src/docs/asciidoc/web/spring-security.adoc @@ -34,10 +34,18 @@ You can provide a different `AuthenticationEventPublisher` by adding a bean for === MVC Security The default security configuration is implemented in `SecurityAutoConfiguration` and `UserDetailsServiceAutoConfiguration`. `SecurityAutoConfiguration` imports `SpringBootWebSecurityConfiguration` for web security and `UserDetailsServiceAutoConfiguration` configures authentication, which is also relevant in non-web applications. -To switch off the default web application security configuration completely or to combine multiple Spring Security components such as OAuth2 Client and Resource Server, add a bean of type `SecurityFilterChain` (doing so does not disable the `UserDetailsService` configuration or Actuator's security). +To switch off the default web application security configuration completely or to combine multiple Spring Security components such as OAuth2 Client and Resource Server, add a bean of type `SecurityFilterChain` (doing so does not disable the `UserDetailsService` configuration or Actuator's security). To also switch off the `UserDetailsService` configuration, you can add a bean of type `UserDetailsService`, `AuthenticationProvider`, or `AuthenticationManager`. +The auto-configuration of a `UserDetailsService` will also back off any of the following Spring Security modules is on the classpath: + +- `spring-security-oauth2-client` +- `spring-security-oauth2-resource-server` +- `spring-security-saml2-service-provider` + +To use `UserDetailsService` in addition to one or more of these dependencies, define your own `InMemoryUserDetailsManager` bean. + Access rules can be overridden by adding a custom `SecurityFilterChain` bean. Spring Boot provides convenience methods that can be used to override access rules for actuator endpoints and static resources. `EndpointRequest` can be used to create a `RequestMatcher` that is based on the configprop:management.endpoints.web.base-path[] property. @@ -50,10 +58,17 @@ Spring Boot provides convenience methods that can be used to override access rul Similar to Spring MVC applications, you can secure your WebFlux applications by adding the `spring-boot-starter-security` dependency. The default security configuration is implemented in `ReactiveSecurityAutoConfiguration` and `UserDetailsServiceAutoConfiguration`. `ReactiveSecurityAutoConfiguration` imports `WebFluxSecurityConfiguration` for web security and `UserDetailsServiceAutoConfiguration` configures authentication, which is also relevant in non-web applications. -To switch off the default web application security configuration completely, you can add a bean of type `WebFilterChainProxy` (doing so does not disable the `UserDetailsService` configuration or Actuator's security). +To switch off the default web application security configuration completely, you can add a bean of type `WebFilterChainProxy` (doing so does not disable the `UserDetailsService` configuration or Actuator's security). To also switch off the `UserDetailsService` configuration, you can add a bean of type `ReactiveUserDetailsService` or `ReactiveAuthenticationManager`. +The auto-configuration will also back off when any of the following Spring Security modules is on the classpath: + +- `spring-security-oauth2-client` +- `spring-security-oauth2-resource-server` + +To use `ReactiveUserDetailsService` in addition to one or more of these dependencies, define your own `MapReactiveUserDetailsService` bean. + Access rules and the use of multiple Spring Security components such as OAuth 2 Client and Resource Server can be configured by adding a custom `SecurityWebFilterChain` bean. Spring Boot provides convenience methods that can be used to override access rules for actuator endpoints and static resources. `EndpointRequest` can be used to create a `ServerWebExchangeMatcher` that is based on the configprop:management.endpoints.web.base-path[] property.