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
This commit is contained in:
Andy Wilkinson 2023-09-20 17:27:33 +01:00
parent ab3c5799ac
commit ecc670772a
15 changed files with 215 additions and 78 deletions

View File

@ -35,10 +35,13 @@ import org.springframework.boot.autoconfigure.context.PropertyPlaceholderAutoCon
import org.springframework.boot.autoconfigure.http.HttpMessageConvertersAutoConfiguration; import org.springframework.boot.autoconfigure.http.HttpMessageConvertersAutoConfiguration;
import org.springframework.boot.autoconfigure.jackson.JacksonAutoConfiguration; import org.springframework.boot.autoconfigure.jackson.JacksonAutoConfiguration;
import org.springframework.boot.autoconfigure.security.reactive.ReactiveSecurityAutoConfiguration; 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.WebFluxAutoConfiguration;
import org.springframework.boot.autoconfigure.web.reactive.function.client.WebClientAutoConfiguration; import org.springframework.boot.autoconfigure.web.reactive.function.client.WebClientAutoConfiguration;
import org.springframework.boot.test.context.runner.ReactiveWebApplicationContextRunner; 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; import static org.assertj.core.api.Assertions.assertThat;
@ -52,15 +55,14 @@ class CloudFoundryReactiveHealthEndpointWebExtensionTests {
private final ReactiveWebApplicationContextRunner contextRunner = new ReactiveWebApplicationContextRunner() private final ReactiveWebApplicationContextRunner contextRunner = new ReactiveWebApplicationContextRunner()
.withPropertyValues("VCAP_APPLICATION={}") .withPropertyValues("VCAP_APPLICATION={}")
.withConfiguration(AutoConfigurations.of(ReactiveSecurityAutoConfiguration.class, .withConfiguration(AutoConfigurations.of(ReactiveSecurityAutoConfiguration.class,
ReactiveUserDetailsServiceAutoConfiguration.class, WebFluxAutoConfiguration.class, WebFluxAutoConfiguration.class, JacksonAutoConfiguration.class,
JacksonAutoConfiguration.class, HttpMessageConvertersAutoConfiguration.class, HttpMessageConvertersAutoConfiguration.class, PropertyPlaceholderAutoConfiguration.class,
PropertyPlaceholderAutoConfiguration.class,
ReactiveCloudFoundryActuatorAutoConfigurationTests.WebClientCustomizerConfig.class, ReactiveCloudFoundryActuatorAutoConfigurationTests.WebClientCustomizerConfig.class,
WebClientAutoConfiguration.class, ManagementContextAutoConfiguration.class, WebClientAutoConfiguration.class, ManagementContextAutoConfiguration.class,
EndpointAutoConfiguration.class, WebEndpointAutoConfiguration.class, EndpointAutoConfiguration.class, WebEndpointAutoConfiguration.class,
HealthContributorAutoConfiguration.class, HealthEndpointAutoConfiguration.class, HealthContributorAutoConfiguration.class, HealthEndpointAutoConfiguration.class,
ReactiveCloudFoundryActuatorAutoConfiguration.class)) ReactiveCloudFoundryActuatorAutoConfiguration.class))
.withUserConfiguration(TestHealthIndicator.class); .withUserConfiguration(TestHealthIndicator.class, UserDetailsServiceConfiguration.class);
@Test @Test
void healthComponentsAlwaysPresent() { 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());
}
}
} }

View File

@ -50,7 +50,6 @@ import org.springframework.boot.autoconfigure.http.HttpMessageConvertersAutoConf
import org.springframework.boot.autoconfigure.info.ProjectInfoAutoConfiguration; import org.springframework.boot.autoconfigure.info.ProjectInfoAutoConfiguration;
import org.springframework.boot.autoconfigure.jackson.JacksonAutoConfiguration; import org.springframework.boot.autoconfigure.jackson.JacksonAutoConfiguration;
import org.springframework.boot.autoconfigure.security.reactive.ReactiveSecurityAutoConfiguration; 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.WebFluxAutoConfiguration;
import org.springframework.boot.autoconfigure.web.reactive.function.client.WebClientAutoConfiguration; import org.springframework.boot.autoconfigure.web.reactive.function.client.WebClientAutoConfiguration;
import org.springframework.boot.test.context.runner.ReactiveWebApplicationContextRunner; 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.http.HttpMethod;
import org.springframework.mock.http.server.reactive.MockServerHttpRequest; import org.springframework.mock.http.server.reactive.MockServerHttpRequest;
import org.springframework.mock.web.server.MockServerWebExchange; 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.SecurityWebFilterChain;
import org.springframework.security.web.server.WebFilterChainProxy; import org.springframework.security.web.server.WebFilterChainProxy;
import org.springframework.test.util.ReflectionTestUtils; import org.springframework.test.util.ReflectionTestUtils;
@ -84,15 +85,16 @@ class ReactiveCloudFoundryActuatorAutoConfigurationTests {
private static final String V3_JSON = ApiVersion.V3.getProducedMimeType().toString(); private static final String V3_JSON = ApiVersion.V3.getProducedMimeType().toString();
private final ReactiveWebApplicationContextRunner contextRunner = new ReactiveWebApplicationContextRunner() private final ReactiveWebApplicationContextRunner contextRunner = new ReactiveWebApplicationContextRunner()
.withConfiguration(AutoConfigurations.of(ReactiveSecurityAutoConfiguration.class, .withConfiguration(
ReactiveUserDetailsServiceAutoConfiguration.class, WebFluxAutoConfiguration.class, AutoConfigurations.of(ReactiveSecurityAutoConfiguration.class, WebFluxAutoConfiguration.class,
JacksonAutoConfiguration.class, HttpMessageConvertersAutoConfiguration.class, JacksonAutoConfiguration.class, HttpMessageConvertersAutoConfiguration.class,
PropertyPlaceholderAutoConfiguration.class, WebClientCustomizerConfig.class, PropertyPlaceholderAutoConfiguration.class, WebClientCustomizerConfig.class,
WebClientAutoConfiguration.class, ManagementContextAutoConfiguration.class, WebClientAutoConfiguration.class, ManagementContextAutoConfiguration.class,
EndpointAutoConfiguration.class, WebEndpointAutoConfiguration.class, EndpointAutoConfiguration.class, WebEndpointAutoConfiguration.class,
HealthContributorAutoConfiguration.class, HealthEndpointAutoConfiguration.class, HealthContributorAutoConfiguration.class, HealthEndpointAutoConfiguration.class,
InfoContributorAutoConfiguration.class, InfoEndpointAutoConfiguration.class, InfoContributorAutoConfiguration.class, InfoEndpointAutoConfiguration.class,
ProjectInfoAutoConfiguration.class, ReactiveCloudFoundryActuatorAutoConfiguration.class)); ProjectInfoAutoConfiguration.class, ReactiveCloudFoundryActuatorAutoConfiguration.class))
.withUserConfiguration(UserDetailsServiceConfiguration.class);
private static final String BASE_PATH = "/cloudfoundryapplication"; 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());
}
}
} }

View File

@ -42,6 +42,7 @@ import org.springframework.boot.autoconfigure.mongo.MongoReactiveAutoConfigurati
import org.springframework.boot.context.annotation.UserConfigurations; import org.springframework.boot.context.annotation.UserConfigurations;
import org.springframework.boot.test.context.runner.ReactiveWebApplicationContextRunner; import org.springframework.boot.test.context.runner.ReactiveWebApplicationContextRunner;
import org.springframework.boot.test.context.runner.WebApplicationContextRunner; import org.springframework.boot.test.context.runner.WebApplicationContextRunner;
import org.springframework.boot.testsupport.classpath.ClassPathExclusions;
import static org.assertj.core.api.Assertions.assertThat; import static org.assertj.core.api.Assertions.assertThat;
@ -59,6 +60,7 @@ class WebEndpointsAutoConfigurationIntegrationTests {
} }
@Test @Test
@ClassPathExclusions({ "spring-security-oauth2-client-*.jar", "spring-security-oauth2-resource-server-*.jar" })
void healthEndpointReactiveWebExtensionIsAutoConfigured() { void healthEndpointReactiveWebExtensionIsAutoConfigured() {
reactiveWebRunner() reactiveWebRunner()
.run((context) -> assertThat(context).hasSingleBean(ReactiveHealthEndpointWebExtension.class)); .run((context) -> assertThat(context).hasSingleBean(ReactiveHealthEndpointWebExtension.class));

View File

@ -33,7 +33,6 @@ import org.springframework.boot.actuate.autoconfigure.info.InfoEndpointAutoConfi
import org.springframework.boot.autoconfigure.AutoConfigurations; import org.springframework.boot.autoconfigure.AutoConfigurations;
import org.springframework.boot.autoconfigure.security.oauth2.resource.reactive.ReactiveOAuth2ResourceServerAutoConfiguration; 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.ReactiveSecurityAutoConfiguration;
import org.springframework.boot.autoconfigure.security.reactive.ReactiveUserDetailsServiceAutoConfiguration;
import org.springframework.boot.autoconfigure.web.reactive.WebFluxAutoConfiguration; import org.springframework.boot.autoconfigure.web.reactive.WebFluxAutoConfiguration;
import org.springframework.boot.test.context.assertj.AssertableReactiveWebApplicationContext; import org.springframework.boot.test.context.assertj.AssertableReactiveWebApplicationContext;
import org.springframework.boot.test.context.runner.ReactiveWebApplicationContextRunner; 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.mock.http.server.reactive.MockServerHttpResponse;
import org.springframework.security.authentication.ReactiveAuthenticationManager; import org.springframework.security.authentication.ReactiveAuthenticationManager;
import org.springframework.security.config.web.server.ServerHttpSecurity; 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.SecurityWebFilterChain;
import org.springframework.security.web.server.WebFilterChainProxy; import org.springframework.security.web.server.WebFilterChainProxy;
import org.springframework.web.server.ServerWebExchange; import org.springframework.web.server.ServerWebExchange;
@ -70,8 +71,8 @@ class ReactiveManagementWebSecurityAutoConfigurationTests {
HealthEndpointAutoConfiguration.class, InfoEndpointAutoConfiguration.class, HealthEndpointAutoConfiguration.class, InfoEndpointAutoConfiguration.class,
WebFluxAutoConfiguration.class, EnvironmentEndpointAutoConfiguration.class, WebFluxAutoConfiguration.class, EnvironmentEndpointAutoConfiguration.class,
EndpointAutoConfiguration.class, WebEndpointAutoConfiguration.class, EndpointAutoConfiguration.class, WebEndpointAutoConfiguration.class,
ReactiveSecurityAutoConfiguration.class, ReactiveUserDetailsServiceAutoConfiguration.class, ReactiveSecurityAutoConfiguration.class, ReactiveManagementWebSecurityAutoConfiguration.class))
ReactiveManagementWebSecurityAutoConfiguration.class)); .withUserConfiguration(UserDetailsServiceConfiguration.class);
@Test @Test
void permitAllForHealth() { 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) @Configuration(proxyBeanMethods = false)
static class CustomSecurityConfiguration { static class CustomSecurityConfiguration {

View File

@ -37,7 +37,6 @@ import org.springframework.boot.actuate.endpoint.web.annotation.ServletEndpoint;
import org.springframework.boot.autoconfigure.AutoConfigurations; import org.springframework.boot.autoconfigure.AutoConfigurations;
import org.springframework.boot.autoconfigure.jackson.JacksonAutoConfiguration; import org.springframework.boot.autoconfigure.jackson.JacksonAutoConfiguration;
import org.springframework.boot.autoconfigure.security.servlet.SecurityAutoConfiguration; 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.assertj.AssertableWebApplicationContext;
import org.springframework.boot.test.context.runner.WebApplicationContextRunner; import org.springframework.boot.test.context.runner.WebApplicationContextRunner;
import org.springframework.boot.web.servlet.context.AnnotationConfigServletWebServerApplicationContext; 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.context.annotation.Configuration;
import org.springframework.http.HttpStatus; import org.springframework.http.HttpStatus;
import org.springframework.security.config.annotation.web.builders.HttpSecurity; 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.security.web.SecurityFilterChain;
import org.springframework.test.web.reactive.server.WebTestClient; import org.springframework.test.web.reactive.server.WebTestClient;
@ -100,8 +101,8 @@ abstract class AbstractEndpointRequestIntegrationTests {
return createContextRunner().withPropertyValues("management.endpoints.web.exposure.include=*") return createContextRunner().withPropertyValues("management.endpoints.web.exposure.include=*")
.withUserConfiguration(BaseConfiguration.class, SecurityConfiguration.class) .withUserConfiguration(BaseConfiguration.class, SecurityConfiguration.class)
.withConfiguration(AutoConfigurations.of(JacksonAutoConfiguration.class, SecurityAutoConfiguration.class, .withConfiguration(AutoConfigurations.of(JacksonAutoConfiguration.class, SecurityAutoConfiguration.class,
UserDetailsServiceAutoConfiguration.class, EndpointAutoConfiguration.class, EndpointAutoConfiguration.class, WebEndpointAutoConfiguration.class,
WebEndpointAutoConfiguration.class, ManagementContextAutoConfiguration.class)); ManagementContextAutoConfiguration.class));
} }
@ -189,6 +190,12 @@ abstract class AbstractEndpointRequestIntegrationTests {
@Configuration(proxyBeanMethods = false) @Configuration(proxyBeanMethods = false)
static class SecurityConfiguration { static class SecurityConfiguration {
@Bean
InMemoryUserDetailsManager userDetailsManager() {
return new InMemoryUserDetailsManager(
User.withUsername("user").password("{noop}password").roles("admin").build());
}
@Bean @Bean
SecurityFilterChain securityFilterChain(HttpSecurity http) throws Exception { SecurityFilterChain securityFilterChain(HttpSecurity http) throws Exception {
http.authorizeHttpRequests((requests) -> { http.authorizeHttpRequests((requests) -> {

View File

@ -28,6 +28,7 @@ import org.springframework.boot.autoconfigure.security.oauth2.client.OAuth2Clien
import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Conditional; import org.springframework.context.annotation.Conditional;
import org.springframework.context.annotation.Configuration; 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.config.web.server.ServerHttpSecurity;
import org.springframework.security.oauth2.client.InMemoryReactiveOAuth2AuthorizedClientService; import org.springframework.security.oauth2.client.InMemoryReactiveOAuth2AuthorizedClientService;
import org.springframework.security.oauth2.client.ReactiveOAuth2AuthorizedClientService; 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.AuthenticatedPrincipalServerOAuth2AuthorizedClientRepository;
import org.springframework.security.oauth2.client.web.server.ServerOAuth2AuthorizedClientRepository; import org.springframework.security.oauth2.client.web.server.ServerOAuth2AuthorizedClientRepository;
import org.springframework.security.web.server.SecurityWebFilterChain; import org.springframework.security.web.server.SecurityWebFilterChain;
import org.springframework.security.web.server.WebFilterChainProxy;
import static org.springframework.security.config.Customizer.withDefaults; import static org.springframework.security.config.Customizer.withDefaults;
@ -92,6 +94,13 @@ class ReactiveOAuth2ClientConfigurations {
return http.build(); return http.build();
} }
@Configuration(proxyBeanMethods = false)
@ConditionalOnMissingBean(WebFilterChainProxy.class)
@EnableWebFluxSecurity
static class EnableWebFluxSecurityConfiguration {
}
} }
} }

View File

@ -28,6 +28,7 @@ import org.springframework.boot.autoconfigure.condition.AnyNestedCondition;
import org.springframework.boot.autoconfigure.condition.ConditionalOnBean; import org.springframework.boot.autoconfigure.condition.ConditionalOnBean;
import org.springframework.boot.autoconfigure.condition.ConditionalOnClass; import org.springframework.boot.autoconfigure.condition.ConditionalOnClass;
import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean; 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.condition.ConditionalOnWebApplication;
import org.springframework.boot.autoconfigure.rsocket.RSocketMessagingAutoConfiguration; import org.springframework.boot.autoconfigure.rsocket.RSocketMessagingAutoConfiguration;
import org.springframework.boot.autoconfigure.security.SecurityProperties; import org.springframework.boot.autoconfigure.security.SecurityProperties;
@ -57,11 +58,12 @@ import org.springframework.util.StringUtils;
*/ */
@AutoConfiguration(before = ReactiveSecurityAutoConfiguration.class, after = RSocketMessagingAutoConfiguration.class) @AutoConfiguration(before = ReactiveSecurityAutoConfiguration.class, after = RSocketMessagingAutoConfiguration.class)
@ConditionalOnClass({ ReactiveAuthenticationManager.class }) @ConditionalOnClass({ ReactiveAuthenticationManager.class })
@ConditionalOnMissingClass({ "org.springframework.security.oauth2.client.registration.ClientRegistrationRepository",
"org.springframework.security.oauth2.server.resource.introspection.ReactiveOpaqueTokenIntrospector" })
@ConditionalOnMissingBean( @ConditionalOnMissingBean(
value = { ReactiveAuthenticationManager.class, ReactiveUserDetailsService.class, value = { ReactiveAuthenticationManager.class, ReactiveUserDetailsService.class,
ReactiveAuthenticationManagerResolver.class }, ReactiveAuthenticationManagerResolver.class },
type = { "org.springframework.security.oauth2.jwt.ReactiveJwtDecoder", type = { "org.springframework.security.oauth2.jwt.ReactiveJwtDecoder" })
"org.springframework.security.oauth2.server.resource.introspection.ReactiveOpaqueTokenIntrospector" })
@Conditional(ReactiveUserDetailsServiceAutoConfiguration.ReactiveUserDetailsServiceCondition.class) @Conditional(ReactiveUserDetailsServiceAutoConfiguration.ReactiveUserDetailsServiceCondition.class)
@EnableConfigurationProperties(SecurityProperties.class) @EnableConfigurationProperties(SecurityProperties.class)
public class ReactiveUserDetailsServiceAutoConfiguration { public class ReactiveUserDetailsServiceAutoConfiguration {

View File

@ -28,6 +28,7 @@ import org.springframework.boot.autoconfigure.EnableAutoConfiguration;
import org.springframework.boot.autoconfigure.condition.ConditionalOnBean; import org.springframework.boot.autoconfigure.condition.ConditionalOnBean;
import org.springframework.boot.autoconfigure.condition.ConditionalOnClass; import org.springframework.boot.autoconfigure.condition.ConditionalOnClass;
import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean; import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean;
import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingClass;
import org.springframework.boot.autoconfigure.security.SecurityProperties; import org.springframework.boot.autoconfigure.security.SecurityProperties;
import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Bean;
import org.springframework.security.authentication.AuthenticationManager; 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 EnableAutoConfiguration Auto-configuration} for a Spring Security in-memory
* {@link AuthenticationManager}. Adds an {@link InMemoryUserDetailsManager} with a * {@link AuthenticationManager}. Adds an {@link InMemoryUserDetailsManager} with a
* default user and generated password. This can be disabled by providing a bean of type * default user and generated password.
* {@link AuthenticationManager}, {@link AuthenticationProvider} or
* {@link UserDetailsService}.
* *
* @author Dave Syer * @author Dave Syer
* @author Rob Winch * @author Rob Winch
@ -54,14 +53,12 @@ import org.springframework.util.StringUtils;
*/ */
@AutoConfiguration @AutoConfiguration
@ConditionalOnClass(AuthenticationManager.class) @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) @ConditionalOnBean(ObjectPostProcessor.class)
@ConditionalOnMissingBean( @ConditionalOnMissingBean(value = { AuthenticationManager.class, AuthenticationProvider.class, UserDetailsService.class,
value = { AuthenticationManager.class, AuthenticationProvider.class, UserDetailsService.class, AuthenticationManagerResolver.class }, type = "org.springframework.security.oauth2.jwt.JwtDecoder")
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" })
public class UserDetailsServiceAutoConfiguration { public class UserDetailsServiceAutoConfiguration {
private static final String NOOP_PASSWORD_PREFIX = "{noop}"; private static final String NOOP_PASSWORD_PREFIX = "{noop}";

View File

@ -23,6 +23,7 @@ import org.springframework.boot.autoconfigure.security.servlet.SecurityAutoConfi
import org.springframework.boot.autoconfigure.security.servlet.UserDetailsServiceAutoConfiguration; import org.springframework.boot.autoconfigure.security.servlet.UserDetailsServiceAutoConfiguration;
import org.springframework.boot.test.context.FilteredClassLoader; import org.springframework.boot.test.context.FilteredClassLoader;
import org.springframework.boot.test.context.runner.WebApplicationContextRunner; 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.Bean;
import org.springframework.context.annotation.Configuration; import org.springframework.context.annotation.Configuration;
import org.springframework.security.oauth2.core.AuthorizationGrantType; import org.springframework.security.oauth2.core.AuthorizationGrantType;
@ -59,8 +60,11 @@ class OAuth2AuthorizationServerAutoConfigurationTests {
} }
@Test @Test
@ClassPathExclusions({ "spring-security-oauth2-client-*.jar", "spring-security-oauth2-resource-server-*.jar",
"spring-security-saml2-service-provider-*.jar" })
void autoConfigurationDoesNotCauseUserDetailsServiceToBackOff() { void autoConfigurationDoesNotCauseUserDetailsServiceToBackOff() {
this.contextRunner.run((context) -> assertThat(context).hasBean("inMemoryUserDetailsManager")); this.contextRunner.run((context) -> assertThat(context).hasSingleBean(UserDetailsServiceAutoConfiguration.class)
.hasBean("inMemoryUserDetailsManager"));
} }
@Test @Test

View File

@ -26,6 +26,8 @@ import org.springframework.boot.test.context.runner.ReactiveWebApplicationContex
import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration; import org.springframework.context.annotation.Configuration;
import org.springframework.security.config.annotation.web.reactive.EnableWebFluxSecurity; 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.security.web.server.WebFilterChainProxy;
import org.springframework.web.reactive.config.WebFluxConfigurer; import org.springframework.web.reactive.config.WebFluxConfigurer;
@ -39,27 +41,24 @@ import static org.mockito.Mockito.mock;
*/ */
class ReactiveSecurityAutoConfigurationTests { class ReactiveSecurityAutoConfigurationTests {
private final ReactiveWebApplicationContextRunner contextRunner = new ReactiveWebApplicationContextRunner(); private final ReactiveWebApplicationContextRunner contextRunner = new ReactiveWebApplicationContextRunner()
.withConfiguration(AutoConfigurations.of(ReactiveSecurityAutoConfiguration.class));
@Test @Test
void backsOffWhenWebFilterChainProxyBeanPresent() { void backsOffWhenWebFilterChainProxyBeanPresent() {
this.contextRunner.withConfiguration(AutoConfigurations.of(ReactiveSecurityAutoConfiguration.class)) this.contextRunner.withUserConfiguration(WebFilterChainProxyConfiguration.class)
.withUserConfiguration(WebFilterChainProxyConfiguration.class)
.run((context) -> assertThat(context).hasSingleBean(WebFilterChainProxy.class)); .run((context) -> assertThat(context).hasSingleBean(WebFilterChainProxy.class));
} }
@Test @Test
void backsOffWhenReactiveAuthenticationManagerNotPresent() { void backsOffWhenReactiveAuthenticationManagerNotPresent() {
this.contextRunner.withConfiguration(AutoConfigurations.of(ReactiveSecurityAutoConfiguration.class)) this.contextRunner.run((context) -> assertThat(context).hasSingleBean(ReactiveSecurityAutoConfiguration.class)
.run((context) -> assertThat(context).hasSingleBean(ReactiveSecurityAutoConfiguration.class) .doesNotHaveBean(EnableWebFluxSecurityConfiguration.class));
.doesNotHaveBean(EnableWebFluxSecurityConfiguration.class));
} }
@Test @Test
void enablesWebFluxSecurity() { void enablesWebFluxSecurity() {
this.contextRunner this.contextRunner.withUserConfiguration(UserDetailsServiceConfiguration.class)
.withConfiguration(AutoConfigurations.of(ReactiveSecurityAutoConfiguration.class,
ReactiveUserDetailsServiceAutoConfiguration.class))
.run((context) -> assertThat(context).getBean(WebFilterChainProxy.class).isNotNull()); .run((context) -> assertThat(context).getBean(WebFilterChainProxy.class).isNotNull());
} }
@ -68,8 +67,7 @@ class ReactiveSecurityAutoConfigurationTests {
this.contextRunner this.contextRunner
.withClassLoader(new FilteredClassLoader(Flux.class, EnableWebFluxSecurity.class, WebFilterChainProxy.class, .withClassLoader(new FilteredClassLoader(Flux.class, EnableWebFluxSecurity.class, WebFilterChainProxy.class,
WebFluxConfigurer.class)) WebFluxConfigurer.class))
.withConfiguration(AutoConfigurations.of(ReactiveSecurityAutoConfiguration.class, .withUserConfiguration(UserDetailsServiceConfiguration.class)
ReactiveUserDetailsServiceAutoConfiguration.class))
.run((context) -> assertThat(context).doesNotHaveBean(WebFilterChainProxy.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());
}
}
} }

View File

@ -25,6 +25,7 @@ import org.springframework.boot.autoconfigure.rsocket.RSocketMessagingAutoConfig
import org.springframework.boot.autoconfigure.rsocket.RSocketStrategiesAutoConfiguration; import org.springframework.boot.autoconfigure.rsocket.RSocketStrategiesAutoConfiguration;
import org.springframework.boot.autoconfigure.security.SecurityProperties; import org.springframework.boot.autoconfigure.security.SecurityProperties;
import org.springframework.boot.context.properties.EnableConfigurationProperties; 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.ApplicationContextRunner;
import org.springframework.boot.test.context.runner.ReactiveWebApplicationContextRunner; import org.springframework.boot.test.context.runner.ReactiveWebApplicationContextRunner;
import org.springframework.context.annotation.Bean; 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.User;
import org.springframework.security.core.userdetails.UserDetails; import org.springframework.security.core.userdetails.UserDetails;
import org.springframework.security.crypto.password.PasswordEncoder; 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.jwt.ReactiveJwtDecoder;
import org.springframework.security.oauth2.server.resource.introspection.ReactiveOpaqueTokenIntrospector; import org.springframework.security.oauth2.server.resource.introspection.ReactiveOpaqueTokenIntrospector;
@ -58,15 +60,21 @@ class ReactiveUserDetailsServiceAutoConfigurationTests {
@Test @Test
void configuresADefaultUser() { void configuresADefaultUser() {
this.contextRunner.withUserConfiguration(TestSecurityConfiguration.class).run((context) -> { this.contextRunner
ReactiveUserDetailsService userDetailsService = context.getBean(ReactiveUserDetailsService.class); .withClassLoader(
assertThat(userDetailsService.findByUsername("user").block(Duration.ofSeconds(30))).isNotNull(); 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 @Test
void userDetailsServiceWhenRSocketConfigured() { void userDetailsServiceWhenRSocketConfigured() {
new ApplicationContextRunner() new ApplicationContextRunner()
.withClassLoader(
new FilteredClassLoader(ClientRegistrationRepository.class, ReactiveOpaqueTokenIntrospector.class))
.withConfiguration(AutoConfigurations.of(ReactiveUserDetailsServiceAutoConfiguration.class, .withConfiguration(AutoConfigurations.of(ReactiveUserDetailsServiceAutoConfiguration.class,
RSocketMessagingAutoConfiguration.class, RSocketStrategiesAutoConfiguration.class)) RSocketMessagingAutoConfiguration.class, RSocketStrategiesAutoConfiguration.class))
.withUserConfiguration(TestRSocketSecurityConfiguration.class) .withUserConfiguration(TestRSocketSecurityConfiguration.class)
@ -109,20 +117,21 @@ class ReactiveUserDetailsServiceAutoConfigurationTests {
} }
@Test @Test
void doesNotConfigureDefaultUserIfResourceServerWithOpaqueIsUsed() { void doesNotConfigureDefaultUserIfResourceServerIsPresent() {
this.contextRunner.withUserConfiguration(ReactiveOpaqueTokenIntrospectorConfiguration.class).run((context) -> { this.contextRunner.run((context) -> assertThat(context).doesNotHaveBean(ReactiveUserDetailsService.class));
assertThat(context).hasSingleBean(ReactiveOpaqueTokenIntrospector.class);
assertThat(context).doesNotHaveBean(ReactiveUserDetailsService.class);
});
} }
@Test @Test
void userDetailsServiceWhenPasswordEncoderAbsentAndDefaultPassword() { void userDetailsServiceWhenPasswordEncoderAbsentAndDefaultPassword() {
this.contextRunner.withUserConfiguration(TestSecurityConfiguration.class).run(((context) -> { this.contextRunner
MapReactiveUserDetailsService userDetailsService = context.getBean(MapReactiveUserDetailsService.class); .withClassLoader(
String password = userDetailsService.findByUsername("user").block(Duration.ofSeconds(30)).getPassword(); new FilteredClassLoader(ClientRegistrationRepository.class, ReactiveOpaqueTokenIntrospector.class))
assertThat(password).startsWith("{noop}"); .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 @Test
@ -142,7 +151,10 @@ class ReactiveUserDetailsServiceAutoConfigurationTests {
} }
private void testPasswordEncoding(Class<?> configClass, String providedPassword, String expectedPassword) { 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) .withPropertyValues("spring.security.user.password=" + providedPassword)
.run(((context) -> { .run(((context) -> {
MapReactiveUserDetailsService userDetailsService = context.getBean(MapReactiveUserDetailsService.class); MapReactiveUserDetailsService userDetailsService = context.getBean(MapReactiveUserDetailsService.class);

View File

@ -22,12 +22,15 @@ import org.junit.jupiter.api.Test;
import org.springframework.boot.autoconfigure.AutoConfigurations; import org.springframework.boot.autoconfigure.AutoConfigurations;
import org.springframework.boot.autoconfigure.rsocket.RSocketMessagingAutoConfiguration; import org.springframework.boot.autoconfigure.rsocket.RSocketMessagingAutoConfiguration;
import org.springframework.boot.autoconfigure.rsocket.RSocketStrategiesAutoConfiguration; 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.rsocket.server.RSocketServerCustomizer;
import org.springframework.boot.test.context.FilteredClassLoader; import org.springframework.boot.test.context.FilteredClassLoader;
import org.springframework.boot.test.context.runner.ApplicationContextRunner; 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.messaging.rsocket.annotation.support.RSocketMessageHandler;
import org.springframework.security.config.annotation.rsocket.RSocketSecurity; 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.messaging.handler.invocation.reactive.AuthenticationPrincipalArgumentResolver;
import org.springframework.security.rsocket.core.SecuritySocketAcceptorInterceptor; import org.springframework.security.rsocket.core.SecuritySocketAcceptorInterceptor;
@ -42,9 +45,9 @@ import static org.assertj.core.api.Assertions.assertThat;
class RSocketSecurityAutoConfigurationTests { class RSocketSecurityAutoConfigurationTests {
private final ApplicationContextRunner contextRunner = new ApplicationContextRunner() private final ApplicationContextRunner contextRunner = new ApplicationContextRunner()
.withConfiguration(AutoConfigurations.of(ReactiveUserDetailsServiceAutoConfiguration.class, .withConfiguration(AutoConfigurations.of(RSocketSecurityAutoConfiguration.class,
RSocketSecurityAutoConfiguration.class, RSocketMessagingAutoConfiguration.class, RSocketMessagingAutoConfiguration.class, RSocketStrategiesAutoConfiguration.class))
RSocketStrategiesAutoConfiguration.class)); .withUserConfiguration(UserDetailsServiceConfiguration.class);
@Test @Test
void autoConfigurationEnablesRSocketSecurity() { 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());
}
}
} }

View File

@ -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"); * Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with 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.system.OutputCaptureExtension;
import org.springframework.boot.test.util.TestPropertyValues; import org.springframework.boot.test.util.TestPropertyValues;
import org.springframework.boot.test.web.client.TestRestTemplate; 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.embedded.tomcat.TomcatServletWebServerFactory;
import org.springframework.boot.web.servlet.context.AnnotationConfigServletWebServerApplicationContext; import org.springframework.boot.web.servlet.context.AnnotationConfigServletWebServerApplicationContext;
import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Bean;
@ -63,6 +65,9 @@ class SecurityFilterAutoConfigurationEarlyInitializationTests {
Pattern.MULTILINE); Pattern.MULTILINE);
@Test @Test
@DirtiesUrlFactories
@ClassPathExclusions({ "spring-security-oauth2-client-*.jar", "spring-security-oauth2-resource-server-*.jar",
"spring-security-saml2-service-provider-*.jar" })
void testSecurityFilterDoesNotCauseEarlyInitialization(CapturedOutput output) { void testSecurityFilterDoesNotCauseEarlyInitialization(CapturedOutput output) {
try (AnnotationConfigServletWebServerApplicationContext context = new AnnotationConfigServletWebServerApplicationContext()) { try (AnnotationConfigServletWebServerApplicationContext context = new AnnotationConfigServletWebServerApplicationContext()) {
TestPropertyValues.of("server.port:0").applyTo(context); TestPropertyValues.of("server.port:0").applyTo(context);

View File

@ -17,6 +17,7 @@
package org.springframework.boot.autoconfigure.security.servlet; package org.springframework.boot.autoconfigure.security.servlet;
import java.util.Collections; import java.util.Collections;
import java.util.function.Function;
import org.junit.jupiter.api.Test; import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.extension.ExtendWith; 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.AutoConfigurations;
import org.springframework.boot.autoconfigure.security.SecurityProperties; import org.springframework.boot.autoconfigure.security.SecurityProperties;
import org.springframework.boot.context.properties.EnableConfigurationProperties; 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.ApplicationContextRunner;
import org.springframework.boot.test.system.CapturedOutput; import org.springframework.boot.test.system.CapturedOutput;
import org.springframework.boot.test.system.OutputCaptureExtension; import org.springframework.boot.test.system.OutputCaptureExtension;
@ -64,7 +66,7 @@ class UserDetailsServiceAutoConfigurationTests {
@Test @Test
void testDefaultUsernamePassword(CapturedOutput output) { void testDefaultUsernamePassword(CapturedOutput output) {
this.contextRunner.run((context) -> { this.contextRunner.with(noOtherFormsOfAuthenticationOnTheClasspath()).run((context) -> {
UserDetailsService manager = context.getBean(UserDetailsService.class); UserDetailsService manager = context.getBean(UserDetailsService.class);
assertThat(output).contains("Using generated security password:"); assertThat(output).contains("Using generated security password:");
assertThat(manager.loadUserByUsername("user")).isNotNull(); assertThat(manager.loadUserByUsername("user")).isNotNull();
@ -126,11 +128,13 @@ class UserDetailsServiceAutoConfigurationTests {
@Test @Test
void userDetailsServiceWhenPasswordEncoderAbsentAndDefaultPassword() { void userDetailsServiceWhenPasswordEncoderAbsentAndDefaultPassword() {
this.contextRunner.withUserConfiguration(TestSecurityConfiguration.class).run(((context) -> { this.contextRunner.with(noOtherFormsOfAuthenticationOnTheClasspath())
InMemoryUserDetailsManager userDetailsService = context.getBean(InMemoryUserDetailsManager.class); .withUserConfiguration(TestSecurityConfiguration.class)
String password = userDetailsService.loadUserByUsername("user").getPassword(); .run(((context) -> {
assertThat(password).startsWith("{noop}"); InMemoryUserDetailsManager userDetailsService = context.getBean(InMemoryUserDetailsManager.class);
})); String password = userDetailsService.loadUserByUsername("user").getPassword();
assertThat(password).startsWith("{noop}");
}));
} }
@Test @Test
@ -150,20 +154,39 @@ class UserDetailsServiceAutoConfigurationTests {
} }
@Test @Test
void userDetailsServiceWhenClientRegistrationRepositoryBeanPresent() { void userDetailsServiceWhenClientRegistrationRepositoryPresent() {
this.contextRunner.withUserConfiguration(TestConfigWithClientRegistrationRepository.class) this.contextRunner
.withClassLoader(
new FilteredClassLoader(OpaqueTokenIntrospector.class, RelyingPartyRegistrationRepository.class))
.run(((context) -> assertThat(context).doesNotHaveBean(InMemoryUserDetailsManager.class))); .run(((context) -> assertThat(context).doesNotHaveBean(InMemoryUserDetailsManager.class)));
} }
@Test @Test
void userDetailsServiceWhenRelyingPartyRegistrationRepositoryBeanPresent() { void userDetailsServiceWhenOpaqueTokenIntrospectorPresent() {
this.contextRunner this.contextRunner
.withBean(RelyingPartyRegistrationRepository.class, () -> mock(RelyingPartyRegistrationRepository.class)) .withClassLoader(new FilteredClassLoader(ClientRegistrationRepository.class,
RelyingPartyRegistrationRepository.class))
.run(((context) -> assertThat(context).doesNotHaveBean(InMemoryUserDetailsManager.class))); .run(((context) -> assertThat(context).doesNotHaveBean(InMemoryUserDetailsManager.class)));
} }
@Test
void userDetailsServiceWhenRelyingPartyRegistrationRepositoryPresent() {
this.contextRunner
.withClassLoader(new FilteredClassLoader(ClientRegistrationRepository.class, OpaqueTokenIntrospector.class))
.run(((context) -> assertThat(context).doesNotHaveBean(InMemoryUserDetailsManager.class)));
}
private Function<ApplicationContextRunner, ApplicationContextRunner> noOtherFormsOfAuthenticationOnTheClasspath() {
return (contextRunner) -> contextRunner
.withClassLoader(new FilteredClassLoader(ClientRegistrationRepository.class, OpaqueTokenIntrospector.class,
RelyingPartyRegistrationRepository.class));
}
private void testPasswordEncoding(Class<?> configClass, String providedPassword, String expectedPassword) { 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) .withPropertyValues("spring.security.user.password=" + providedPassword)
.run(((context) -> { .run(((context) -> {
InMemoryUserDetailsManager userDetailsService = context.getBean(InMemoryUserDetailsManager.class); InMemoryUserDetailsManager userDetailsService = context.getBean(InMemoryUserDetailsManager.class);

View File

@ -34,10 +34,18 @@ You can provide a different `AuthenticationEventPublisher` by adding a bean for
=== MVC Security === MVC Security
The default security configuration is implemented in `SecurityAutoConfiguration` and `UserDetailsServiceAutoConfiguration`. 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. `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`. 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. 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. 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. `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. 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`. 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. `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`. 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. 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. 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. `EndpointRequest` can be used to create a `ServerWebExchangeMatcher` that is based on the configprop:management.endpoints.web.base-path[] property.