Skip to content

Commit ecc6707

Browse files
committed
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
1 parent ab3c579 commit ecc6707

File tree

15 files changed

+215
-78
lines changed

15 files changed

+215
-78
lines changed

spring-boot-project/spring-boot-actuator-autoconfigure/src/test/java/org/springframework/boot/actuate/autoconfigure/cloudfoundry/reactive/CloudFoundryReactiveHealthEndpointWebExtensionTests.java

Lines changed: 18 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -35,10 +35,13 @@
3535
import org.springframework.boot.autoconfigure.http.HttpMessageConvertersAutoConfiguration;
3636
import org.springframework.boot.autoconfigure.jackson.JacksonAutoConfiguration;
3737
import org.springframework.boot.autoconfigure.security.reactive.ReactiveSecurityAutoConfiguration;
38-
import org.springframework.boot.autoconfigure.security.reactive.ReactiveUserDetailsServiceAutoConfiguration;
3938
import org.springframework.boot.autoconfigure.web.reactive.WebFluxAutoConfiguration;
4039
import org.springframework.boot.autoconfigure.web.reactive.function.client.WebClientAutoConfiguration;
4140
import org.springframework.boot.test.context.runner.ReactiveWebApplicationContextRunner;
41+
import org.springframework.context.annotation.Bean;
42+
import org.springframework.context.annotation.Configuration;
43+
import org.springframework.security.core.userdetails.MapReactiveUserDetailsService;
44+
import org.springframework.security.core.userdetails.User;
4245

4346
import static org.assertj.core.api.Assertions.assertThat;
4447

@@ -52,15 +55,14 @@ class CloudFoundryReactiveHealthEndpointWebExtensionTests {
5255
private final ReactiveWebApplicationContextRunner contextRunner = new ReactiveWebApplicationContextRunner()
5356
.withPropertyValues("VCAP_APPLICATION={}")
5457
.withConfiguration(AutoConfigurations.of(ReactiveSecurityAutoConfiguration.class,
55-
ReactiveUserDetailsServiceAutoConfiguration.class, WebFluxAutoConfiguration.class,
56-
JacksonAutoConfiguration.class, HttpMessageConvertersAutoConfiguration.class,
57-
PropertyPlaceholderAutoConfiguration.class,
58+
WebFluxAutoConfiguration.class, JacksonAutoConfiguration.class,
59+
HttpMessageConvertersAutoConfiguration.class, PropertyPlaceholderAutoConfiguration.class,
5860
ReactiveCloudFoundryActuatorAutoConfigurationTests.WebClientCustomizerConfig.class,
5961
WebClientAutoConfiguration.class, ManagementContextAutoConfiguration.class,
6062
EndpointAutoConfiguration.class, WebEndpointAutoConfiguration.class,
6163
HealthContributorAutoConfiguration.class, HealthEndpointAutoConfiguration.class,
6264
ReactiveCloudFoundryActuatorAutoConfiguration.class))
63-
.withUserConfiguration(TestHealthIndicator.class);
65+
.withUserConfiguration(TestHealthIndicator.class, UserDetailsServiceConfiguration.class);
6466

6567
@Test
6668
void healthComponentsAlwaysPresent() {
@@ -82,4 +84,15 @@ public Health health() {
8284

8385
}
8486

87+
@Configuration(proxyBeanMethods = false)
88+
static class UserDetailsServiceConfiguration {
89+
90+
@Bean
91+
MapReactiveUserDetailsService userDetailsService() {
92+
return new MapReactiveUserDetailsService(
93+
User.withUsername("alice").password("secret").roles("admin").build());
94+
}
95+
96+
}
97+
8598
}

spring-boot-project/spring-boot-actuator-autoconfigure/src/test/java/org/springframework/boot/actuate/autoconfigure/cloudfoundry/reactive/ReactiveCloudFoundryActuatorAutoConfigurationTests.java

Lines changed: 23 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -50,7 +50,6 @@
5050
import org.springframework.boot.autoconfigure.info.ProjectInfoAutoConfiguration;
5151
import org.springframework.boot.autoconfigure.jackson.JacksonAutoConfiguration;
5252
import org.springframework.boot.autoconfigure.security.reactive.ReactiveSecurityAutoConfiguration;
53-
import org.springframework.boot.autoconfigure.security.reactive.ReactiveUserDetailsServiceAutoConfiguration;
5453
import org.springframework.boot.autoconfigure.web.reactive.WebFluxAutoConfiguration;
5554
import org.springframework.boot.autoconfigure.web.reactive.function.client.WebClientAutoConfiguration;
5655
import org.springframework.boot.test.context.runner.ReactiveWebApplicationContextRunner;
@@ -61,6 +60,8 @@
6160
import org.springframework.http.HttpMethod;
6261
import org.springframework.mock.http.server.reactive.MockServerHttpRequest;
6362
import org.springframework.mock.web.server.MockServerWebExchange;
63+
import org.springframework.security.core.userdetails.MapReactiveUserDetailsService;
64+
import org.springframework.security.core.userdetails.User;
6465
import org.springframework.security.web.server.SecurityWebFilterChain;
6566
import org.springframework.security.web.server.WebFilterChainProxy;
6667
import org.springframework.test.util.ReflectionTestUtils;
@@ -84,15 +85,16 @@ class ReactiveCloudFoundryActuatorAutoConfigurationTests {
8485
private static final String V3_JSON = ApiVersion.V3.getProducedMimeType().toString();
8586

8687
private final ReactiveWebApplicationContextRunner contextRunner = new ReactiveWebApplicationContextRunner()
87-
.withConfiguration(AutoConfigurations.of(ReactiveSecurityAutoConfiguration.class,
88-
ReactiveUserDetailsServiceAutoConfiguration.class, WebFluxAutoConfiguration.class,
89-
JacksonAutoConfiguration.class, HttpMessageConvertersAutoConfiguration.class,
90-
PropertyPlaceholderAutoConfiguration.class, WebClientCustomizerConfig.class,
91-
WebClientAutoConfiguration.class, ManagementContextAutoConfiguration.class,
92-
EndpointAutoConfiguration.class, WebEndpointAutoConfiguration.class,
93-
HealthContributorAutoConfiguration.class, HealthEndpointAutoConfiguration.class,
94-
InfoContributorAutoConfiguration.class, InfoEndpointAutoConfiguration.class,
95-
ProjectInfoAutoConfiguration.class, ReactiveCloudFoundryActuatorAutoConfiguration.class));
88+
.withConfiguration(
89+
AutoConfigurations.of(ReactiveSecurityAutoConfiguration.class, WebFluxAutoConfiguration.class,
90+
JacksonAutoConfiguration.class, HttpMessageConvertersAutoConfiguration.class,
91+
PropertyPlaceholderAutoConfiguration.class, WebClientCustomizerConfig.class,
92+
WebClientAutoConfiguration.class, ManagementContextAutoConfiguration.class,
93+
EndpointAutoConfiguration.class, WebEndpointAutoConfiguration.class,
94+
HealthContributorAutoConfiguration.class, HealthEndpointAutoConfiguration.class,
95+
InfoContributorAutoConfiguration.class, InfoEndpointAutoConfiguration.class,
96+
ProjectInfoAutoConfiguration.class, ReactiveCloudFoundryActuatorAutoConfiguration.class))
97+
.withUserConfiguration(UserDetailsServiceConfiguration.class);
9698

9799
private static final String BASE_PATH = "/cloudfoundryapplication";
98100

@@ -358,4 +360,15 @@ WebClientCustomizer webClientCustomizer() {
358360

359361
}
360362

363+
@Configuration(proxyBeanMethods = false)
364+
static class UserDetailsServiceConfiguration {
365+
366+
@Bean
367+
MapReactiveUserDetailsService userDetailsService() {
368+
return new MapReactiveUserDetailsService(
369+
User.withUsername("alice").password("secret").roles("admin").build());
370+
}
371+
372+
}
373+
361374
}

spring-boot-project/spring-boot-actuator-autoconfigure/src/test/java/org/springframework/boot/actuate/autoconfigure/integrationtest/WebEndpointsAutoConfigurationIntegrationTests.java

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -42,6 +42,7 @@
4242
import org.springframework.boot.context.annotation.UserConfigurations;
4343
import org.springframework.boot.test.context.runner.ReactiveWebApplicationContextRunner;
4444
import org.springframework.boot.test.context.runner.WebApplicationContextRunner;
45+
import org.springframework.boot.testsupport.classpath.ClassPathExclusions;
4546

4647
import static org.assertj.core.api.Assertions.assertThat;
4748

@@ -59,6 +60,7 @@ void healthEndpointWebExtensionIsAutoConfigured() {
5960
}
6061

6162
@Test
63+
@ClassPathExclusions({ "spring-security-oauth2-client-*.jar", "spring-security-oauth2-resource-server-*.jar" })
6264
void healthEndpointReactiveWebExtensionIsAutoConfigured() {
6365
reactiveWebRunner()
6466
.run((context) -> assertThat(context).hasSingleBean(ReactiveHealthEndpointWebExtension.class));

spring-boot-project/spring-boot-actuator-autoconfigure/src/test/java/org/springframework/boot/actuate/autoconfigure/security/reactive/ReactiveManagementWebSecurityAutoConfigurationTests.java

Lines changed: 15 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -33,7 +33,6 @@
3333
import org.springframework.boot.autoconfigure.AutoConfigurations;
3434
import org.springframework.boot.autoconfigure.security.oauth2.resource.reactive.ReactiveOAuth2ResourceServerAutoConfiguration;
3535
import org.springframework.boot.autoconfigure.security.reactive.ReactiveSecurityAutoConfiguration;
36-
import org.springframework.boot.autoconfigure.security.reactive.ReactiveUserDetailsServiceAutoConfiguration;
3736
import org.springframework.boot.autoconfigure.web.reactive.WebFluxAutoConfiguration;
3837
import org.springframework.boot.test.context.assertj.AssertableReactiveWebApplicationContext;
3938
import org.springframework.boot.test.context.runner.ReactiveWebApplicationContextRunner;
@@ -48,6 +47,8 @@
4847
import org.springframework.mock.http.server.reactive.MockServerHttpResponse;
4948
import org.springframework.security.authentication.ReactiveAuthenticationManager;
5049
import org.springframework.security.config.web.server.ServerHttpSecurity;
50+
import org.springframework.security.core.userdetails.MapReactiveUserDetailsService;
51+
import org.springframework.security.core.userdetails.User;
5152
import org.springframework.security.web.server.SecurityWebFilterChain;
5253
import org.springframework.security.web.server.WebFilterChainProxy;
5354
import org.springframework.web.server.ServerWebExchange;
@@ -70,8 +71,8 @@ class ReactiveManagementWebSecurityAutoConfigurationTests {
7071
HealthEndpointAutoConfiguration.class, InfoEndpointAutoConfiguration.class,
7172
WebFluxAutoConfiguration.class, EnvironmentEndpointAutoConfiguration.class,
7273
EndpointAutoConfiguration.class, WebEndpointAutoConfiguration.class,
73-
ReactiveSecurityAutoConfiguration.class, ReactiveUserDetailsServiceAutoConfiguration.class,
74-
ReactiveManagementWebSecurityAutoConfiguration.class));
74+
ReactiveSecurityAutoConfiguration.class, ReactiveManagementWebSecurityAutoConfiguration.class))
75+
.withUserConfiguration(UserDetailsServiceConfiguration.class);
7576

7677
@Test
7778
void permitAllForHealth() {
@@ -155,6 +156,17 @@ protected ServerWebExchange createExchange(ServerHttpRequest request, ServerHttp
155156

156157
}
157158

159+
@Configuration(proxyBeanMethods = false)
160+
static class UserDetailsServiceConfiguration {
161+
162+
@Bean
163+
MapReactiveUserDetailsService userDetailsService() {
164+
return new MapReactiveUserDetailsService(
165+
User.withUsername("alice").password("secret").roles("admin").build());
166+
}
167+
168+
}
169+
158170
@Configuration(proxyBeanMethods = false)
159171
static class CustomSecurityConfiguration {
160172

spring-boot-project/spring-boot-actuator-autoconfigure/src/test/java/org/springframework/boot/actuate/autoconfigure/security/servlet/AbstractEndpointRequestIntegrationTests.java

Lines changed: 10 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -37,14 +37,15 @@
3737
import org.springframework.boot.autoconfigure.AutoConfigurations;
3838
import org.springframework.boot.autoconfigure.jackson.JacksonAutoConfiguration;
3939
import org.springframework.boot.autoconfigure.security.servlet.SecurityAutoConfiguration;
40-
import org.springframework.boot.autoconfigure.security.servlet.UserDetailsServiceAutoConfiguration;
4140
import org.springframework.boot.test.context.assertj.AssertableWebApplicationContext;
4241
import org.springframework.boot.test.context.runner.WebApplicationContextRunner;
4342
import org.springframework.boot.web.servlet.context.AnnotationConfigServletWebServerApplicationContext;
4443
import org.springframework.context.annotation.Bean;
4544
import org.springframework.context.annotation.Configuration;
4645
import org.springframework.http.HttpStatus;
4746
import org.springframework.security.config.annotation.web.builders.HttpSecurity;
47+
import org.springframework.security.core.userdetails.User;
48+
import org.springframework.security.provisioning.InMemoryUserDetailsManager;
4849
import org.springframework.security.web.SecurityFilterChain;
4950
import org.springframework.test.web.reactive.server.WebTestClient;
5051

@@ -100,8 +101,8 @@ protected final WebApplicationContextRunner getContextRunner() {
100101
return createContextRunner().withPropertyValues("management.endpoints.web.exposure.include=*")
101102
.withUserConfiguration(BaseConfiguration.class, SecurityConfiguration.class)
102103
.withConfiguration(AutoConfigurations.of(JacksonAutoConfiguration.class, SecurityAutoConfiguration.class,
103-
UserDetailsServiceAutoConfiguration.class, EndpointAutoConfiguration.class,
104-
WebEndpointAutoConfiguration.class, ManagementContextAutoConfiguration.class));
104+
EndpointAutoConfiguration.class, WebEndpointAutoConfiguration.class,
105+
ManagementContextAutoConfiguration.class));
105106

106107
}
107108

@@ -189,6 +190,12 @@ public EndpointServlet get() {
189190
@Configuration(proxyBeanMethods = false)
190191
static class SecurityConfiguration {
191192

193+
@Bean
194+
InMemoryUserDetailsManager userDetailsManager() {
195+
return new InMemoryUserDetailsManager(
196+
User.withUsername("user").password("{noop}password").roles("admin").build());
197+
}
198+
192199
@Bean
193200
SecurityFilterChain securityFilterChain(HttpSecurity http) throws Exception {
194201
http.authorizeHttpRequests((requests) -> {

spring-boot-project/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/security/oauth2/client/reactive/ReactiveOAuth2ClientConfigurations.java

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -28,6 +28,7 @@
2828
import org.springframework.context.annotation.Bean;
2929
import org.springframework.context.annotation.Conditional;
3030
import org.springframework.context.annotation.Configuration;
31+
import org.springframework.security.config.annotation.web.reactive.EnableWebFluxSecurity;
3132
import org.springframework.security.config.web.server.ServerHttpSecurity;
3233
import org.springframework.security.oauth2.client.InMemoryReactiveOAuth2AuthorizedClientService;
3334
import org.springframework.security.oauth2.client.ReactiveOAuth2AuthorizedClientService;
@@ -37,6 +38,7 @@
3738
import org.springframework.security.oauth2.client.web.server.AuthenticatedPrincipalServerOAuth2AuthorizedClientRepository;
3839
import org.springframework.security.oauth2.client.web.server.ServerOAuth2AuthorizedClientRepository;
3940
import org.springframework.security.web.server.SecurityWebFilterChain;
41+
import org.springframework.security.web.server.WebFilterChainProxy;
4042

4143
import static org.springframework.security.config.Customizer.withDefaults;
4244

@@ -92,6 +94,13 @@ SecurityWebFilterChain springSecurityFilterChain(ServerHttpSecurity http) {
9294
return http.build();
9395
}
9496

97+
@Configuration(proxyBeanMethods = false)
98+
@ConditionalOnMissingBean(WebFilterChainProxy.class)
99+
@EnableWebFluxSecurity
100+
static class EnableWebFluxSecurityConfiguration {
101+
102+
}
103+
95104
}
96105

97106
}

spring-boot-project/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/security/reactive/ReactiveUserDetailsServiceAutoConfiguration.java

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -28,6 +28,7 @@
2828
import org.springframework.boot.autoconfigure.condition.ConditionalOnBean;
2929
import org.springframework.boot.autoconfigure.condition.ConditionalOnClass;
3030
import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean;
31+
import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingClass;
3132
import org.springframework.boot.autoconfigure.condition.ConditionalOnWebApplication;
3233
import org.springframework.boot.autoconfigure.rsocket.RSocketMessagingAutoConfiguration;
3334
import org.springframework.boot.autoconfigure.security.SecurityProperties;
@@ -57,11 +58,12 @@
5758
*/
5859
@AutoConfiguration(before = ReactiveSecurityAutoConfiguration.class, after = RSocketMessagingAutoConfiguration.class)
5960
@ConditionalOnClass({ ReactiveAuthenticationManager.class })
61+
@ConditionalOnMissingClass({ "org.springframework.security.oauth2.client.registration.ClientRegistrationRepository",
62+
"org.springframework.security.oauth2.server.resource.introspection.ReactiveOpaqueTokenIntrospector" })
6063
@ConditionalOnMissingBean(
6164
value = { ReactiveAuthenticationManager.class, ReactiveUserDetailsService.class,
6265
ReactiveAuthenticationManagerResolver.class },
63-
type = { "org.springframework.security.oauth2.jwt.ReactiveJwtDecoder",
64-
"org.springframework.security.oauth2.server.resource.introspection.ReactiveOpaqueTokenIntrospector" })
66+
type = { "org.springframework.security.oauth2.jwt.ReactiveJwtDecoder" })
6567
@Conditional(ReactiveUserDetailsServiceAutoConfiguration.ReactiveUserDetailsServiceCondition.class)
6668
@EnableConfigurationProperties(SecurityProperties.class)
6769
public class ReactiveUserDetailsServiceAutoConfiguration {

spring-boot-project/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/security/servlet/UserDetailsServiceAutoConfiguration.java

Lines changed: 7 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -28,6 +28,7 @@
2828
import org.springframework.boot.autoconfigure.condition.ConditionalOnBean;
2929
import org.springframework.boot.autoconfigure.condition.ConditionalOnClass;
3030
import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean;
31+
import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingClass;
3132
import org.springframework.boot.autoconfigure.security.SecurityProperties;
3233
import org.springframework.context.annotation.Bean;
3334
import org.springframework.security.authentication.AuthenticationManager;
@@ -43,9 +44,7 @@
4344
/**
4445
* {@link EnableAutoConfiguration Auto-configuration} for a Spring Security in-memory
4546
* {@link AuthenticationManager}. Adds an {@link InMemoryUserDetailsManager} with a
46-
* default user and generated password. This can be disabled by providing a bean of type
47-
* {@link AuthenticationManager}, {@link AuthenticationProvider} or
48-
* {@link UserDetailsService}.
47+
* default user and generated password.
4948
*
5049
* @author Dave Syer
5150
* @author Rob Winch
@@ -54,14 +53,12 @@
5453
*/
5554
@AutoConfiguration
5655
@ConditionalOnClass(AuthenticationManager.class)
56+
@ConditionalOnMissingClass({ "org.springframework.security.oauth2.client.registration.ClientRegistrationRepository",
57+
"org.springframework.security.oauth2.server.resource.introspection.OpaqueTokenIntrospector",
58+
"org.springframework.security.saml2.provider.service.registration.RelyingPartyRegistrationRepository" })
5759
@ConditionalOnBean(ObjectPostProcessor.class)
58-
@ConditionalOnMissingBean(
59-
value = { AuthenticationManager.class, AuthenticationProvider.class, UserDetailsService.class,
60-
AuthenticationManagerResolver.class },
61-
type = { "org.springframework.security.oauth2.jwt.JwtDecoder",
62-
"org.springframework.security.oauth2.server.resource.introspection.OpaqueTokenIntrospector",
63-
"org.springframework.security.oauth2.client.registration.ClientRegistrationRepository",
64-
"org.springframework.security.saml2.provider.service.registration.RelyingPartyRegistrationRepository" })
60+
@ConditionalOnMissingBean(value = { AuthenticationManager.class, AuthenticationProvider.class, UserDetailsService.class,
61+
AuthenticationManagerResolver.class }, type = "org.springframework.security.oauth2.jwt.JwtDecoder")
6562
public class UserDetailsServiceAutoConfiguration {
6663

6764
private static final String NOOP_PASSWORD_PREFIX = "{noop}";

spring-boot-project/spring-boot-autoconfigure/src/test/java/org/springframework/boot/autoconfigure/security/oauth2/server/servlet/OAuth2AuthorizationServerAutoConfigurationTests.java

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -23,6 +23,7 @@
2323
import org.springframework.boot.autoconfigure.security.servlet.UserDetailsServiceAutoConfiguration;
2424
import org.springframework.boot.test.context.FilteredClassLoader;
2525
import org.springframework.boot.test.context.runner.WebApplicationContextRunner;
26+
import org.springframework.boot.testsupport.classpath.ClassPathExclusions;
2627
import org.springframework.context.annotation.Bean;
2728
import org.springframework.context.annotation.Configuration;
2829
import org.springframework.security.oauth2.core.AuthorizationGrantType;
@@ -59,8 +60,11 @@ void autoConfigurationConditionalOnClassOauth2Authorization() {
5960
}
6061

6162
@Test
63+
@ClassPathExclusions({ "spring-security-oauth2-client-*.jar", "spring-security-oauth2-resource-server-*.jar",
64+
"spring-security-saml2-service-provider-*.jar" })
6265
void autoConfigurationDoesNotCauseUserDetailsServiceToBackOff() {
63-
this.contextRunner.run((context) -> assertThat(context).hasBean("inMemoryUserDetailsManager"));
66+
this.contextRunner.run((context) -> assertThat(context).hasSingleBean(UserDetailsServiceAutoConfiguration.class)
67+
.hasBean("inMemoryUserDetailsManager"));
6468
}
6569

6670
@Test

0 commit comments

Comments
 (0)