Skip to content

Commit afad358

Browse files
committed
Align reactive web security more closely with servlet web security
There are some notable differences in the behavior of Spring Security's reactive and servlet-based web security. Notably, Servlet-based web security (`@EnableWebSecurity`) works without any authentication manager, rejecting requests as not authorized. By contrast reactive-based web security (`@EnableWebFluxSecurity`) fails to start up when there's no authentication manager, either provided directly as a bean or derived from a ReactiveUserDetailsService. There are also further differences at runtime where empty Monos from all ReactiveAuthenticationManagers results in an internal error and a 500 response whereas a similar situation in the servlet implementation results in a 401. Previously, to accommodate these differences in behavior, Spring Boot's auto-configuration would behave differently. In the Servlet case, web security would be enabled whenever the necessary dependencies were on the classpath. In the reactive case, web security would back off in the absence of an authentication manager to prevent a start up failure. While this difference is rooted in Spring Security, it is undesirable and something that we want to avoid Spring Boot users being exposed to where possible. Unfortunately, the situation is more likely to occur than before as ReactiveUserDetailsServiceAutoConfiguration now backs off more readily (gh-35338). This makes it more likely that the context will contain neither a reactive authetication manager not a reactive user details service. This commit reworks the auto-configurations related to reactive security. ReactiveSecurityAutoConfiguration will now auto-configure an "empty" reactive authentication manager that denies access through Mono.error in the absence of a ReactiveAuthenticationManager, ReactiveUserDetailsService, or SecurityWebFilterChain. The last of these is to allow for the situation where a filter chain has been defined with an authentication manager configured directly on it. This configuration of an authentication manager allows `@EnableWebFluxSecurity` to be auto-configured more readily, removing one of the differences between reactive- and Servlet-based security. Corresponding updates to the auto-configurations for reactive OAuth2 support have also been made. They no longer try to auto-configure `@EnableWebFluxSecurity`, relying instead upon ReactiveSecurityAutoConfiguration, which they are ordered before, to do that instead. Closes gh-38713
1 parent 964ccbb commit afad358

File tree

6 files changed

+29
-61
lines changed

6 files changed

+29
-61
lines changed

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

Lines changed: 0 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -28,7 +28,6 @@
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;
3231
import org.springframework.security.config.web.server.ServerHttpSecurity;
3332
import org.springframework.security.oauth2.client.InMemoryReactiveOAuth2AuthorizedClientService;
3433
import org.springframework.security.oauth2.client.ReactiveOAuth2AuthorizedClientService;
@@ -38,7 +37,6 @@
3837
import org.springframework.security.oauth2.client.web.server.AuthenticatedPrincipalServerOAuth2AuthorizedClientRepository;
3938
import org.springframework.security.oauth2.client.web.server.ServerOAuth2AuthorizedClientRepository;
4039
import org.springframework.security.web.server.SecurityWebFilterChain;
41-
import org.springframework.security.web.server.WebFilterChainProxy;
4240

4341
import static org.springframework.security.config.Customizer.withDefaults;
4442

@@ -94,13 +92,6 @@ SecurityWebFilterChain springSecurityFilterChain(ServerHttpSecurity http) {
9492
return http.build();
9593
}
9694

97-
@Configuration(proxyBeanMethods = false)
98-
@ConditionalOnMissingBean(WebFilterChainProxy.class)
99-
@EnableWebFluxSecurity
100-
static class EnableWebFluxSecurityConfiguration {
101-
102-
}
103-
10495
}
10596

10697
}

spring-boot-project/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/security/oauth2/resource/reactive/ReactiveOAuth2ResourceServerJwkConfiguration.java

Lines changed: 0 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -35,7 +35,6 @@
3535
import org.springframework.context.annotation.Bean;
3636
import org.springframework.context.annotation.Conditional;
3737
import org.springframework.context.annotation.Configuration;
38-
import org.springframework.security.config.annotation.web.reactive.EnableWebFluxSecurity;
3938
import org.springframework.security.config.web.server.ServerHttpSecurity;
4039
import org.springframework.security.config.web.server.ServerHttpSecurity.OAuth2ResourceServerSpec;
4140
import org.springframework.security.oauth2.core.DelegatingOAuth2TokenValidator;
@@ -50,7 +49,6 @@
5049
import org.springframework.security.oauth2.jwt.ReactiveJwtDecoder;
5150
import org.springframework.security.oauth2.jwt.SupplierReactiveJwtDecoder;
5251
import org.springframework.security.web.server.SecurityWebFilterChain;
53-
import org.springframework.security.web.server.WebFilterChainProxy;
5452
import org.springframework.util.CollectionUtils;
5553

5654
/**
@@ -179,13 +177,6 @@ private void customDecoder(OAuth2ResourceServerSpec server, ReactiveJwtDecoder d
179177
server.jwt((jwt) -> jwt.jwtDecoder(decoder));
180178
}
181179

182-
@Configuration(proxyBeanMethods = false)
183-
@ConditionalOnMissingBean(WebFilterChainProxy.class)
184-
@EnableWebFluxSecurity
185-
static class EnableWebFluxSecurityConfiguration {
186-
187-
}
188-
189180
}
190181

191182
}

spring-boot-project/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/security/oauth2/resource/reactive/ReactiveOAuth2ResourceServerOpaqueTokenConfiguration.java

Lines changed: 0 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -22,12 +22,10 @@
2222
import org.springframework.boot.autoconfigure.security.oauth2.resource.OAuth2ResourceServerProperties;
2323
import org.springframework.context.annotation.Bean;
2424
import org.springframework.context.annotation.Configuration;
25-
import org.springframework.security.config.annotation.web.reactive.EnableWebFluxSecurity;
2625
import org.springframework.security.config.web.server.ServerHttpSecurity;
2726
import org.springframework.security.oauth2.server.resource.introspection.ReactiveOpaqueTokenIntrospector;
2827
import org.springframework.security.oauth2.server.resource.introspection.SpringReactiveOpaqueTokenIntrospector;
2928
import org.springframework.security.web.server.SecurityWebFilterChain;
30-
import org.springframework.security.web.server.WebFilterChainProxy;
3129

3230
import static org.springframework.security.config.Customizer.withDefaults;
3331

@@ -66,13 +64,6 @@ SecurityWebFilterChain springSecurityFilterChain(ServerHttpSecurity http) {
6664
return http.build();
6765
}
6866

69-
@Configuration(proxyBeanMethods = false)
70-
@ConditionalOnMissingBean(WebFilterChainProxy.class)
71-
@EnableWebFluxSecurity
72-
static class EnableWebFluxSecurityConfiguration {
73-
74-
}
75-
7667
}
7768

7869
}

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

Lines changed: 14 additions & 26 deletions
Original file line numberDiff line numberDiff line change
@@ -17,21 +17,21 @@
1717
package org.springframework.boot.autoconfigure.security.reactive;
1818

1919
import reactor.core.publisher.Flux;
20+
import reactor.core.publisher.Mono;
2021

2122
import org.springframework.boot.autoconfigure.AutoConfiguration;
2223
import org.springframework.boot.autoconfigure.EnableAutoConfiguration;
23-
import org.springframework.boot.autoconfigure.condition.AnyNestedCondition;
24-
import org.springframework.boot.autoconfigure.condition.ConditionalOnBean;
2524
import org.springframework.boot.autoconfigure.condition.ConditionalOnClass;
2625
import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean;
2726
import org.springframework.boot.autoconfigure.condition.ConditionalOnWebApplication;
2827
import org.springframework.boot.autoconfigure.security.SecurityProperties;
2928
import org.springframework.boot.context.properties.EnableConfigurationProperties;
30-
import org.springframework.context.annotation.Conditional;
29+
import org.springframework.context.annotation.Bean;
3130
import org.springframework.context.annotation.Configuration;
3231
import org.springframework.security.authentication.ReactiveAuthenticationManager;
3332
import org.springframework.security.config.annotation.web.reactive.EnableWebFluxSecurity;
3433
import org.springframework.security.core.userdetails.ReactiveUserDetailsService;
34+
import org.springframework.security.core.userdetails.UsernameNotFoundException;
3535
import org.springframework.security.web.server.SecurityWebFilterChain;
3636
import org.springframework.security.web.server.WebFilterChainProxy;
3737
import org.springframework.web.reactive.config.WebFluxConfigurer;
@@ -52,33 +52,21 @@
5252
@ConditionalOnClass({ Flux.class, EnableWebFluxSecurity.class, WebFilterChainProxy.class, WebFluxConfigurer.class })
5353
public class ReactiveSecurityAutoConfiguration {
5454

55-
@Configuration(proxyBeanMethods = false)
56-
@ConditionalOnMissingBean(WebFilterChainProxy.class)
5755
@ConditionalOnWebApplication(type = ConditionalOnWebApplication.Type.REACTIVE)
58-
@Conditional(EnableWebFluxSecurityCondition.class)
59-
@EnableWebFluxSecurity
60-
static class EnableWebFluxSecurityConfiguration {
61-
62-
}
63-
64-
static final class EnableWebFluxSecurityCondition extends AnyNestedCondition {
65-
66-
EnableWebFluxSecurityCondition() {
67-
super(ConfigurationPhase.REGISTER_BEAN);
68-
}
69-
70-
@ConditionalOnBean(ReactiveAuthenticationManager.class)
71-
static final class ConditionalOnReactiveAuthenticationManagerBean {
72-
73-
}
74-
75-
@ConditionalOnBean(ReactiveUserDetailsService.class)
76-
static final class ConditionalOnReactiveUserDetailsService {
56+
@Configuration(proxyBeanMethods = false)
57+
class SpringBootWebFluxSecurityConfiguration {
7758

59+
@Bean
60+
@ConditionalOnMissingBean({ ReactiveAuthenticationManager.class, ReactiveUserDetailsService.class,
61+
SecurityWebFilterChain.class })
62+
ReactiveAuthenticationManager denyAllAuthenticationManager() {
63+
return (authentication) -> Mono.error(new UsernameNotFoundException(authentication.getName()));
7864
}
7965

80-
@ConditionalOnBean(SecurityWebFilterChain.class)
81-
static final class ConditionalOnSecurityWebFilterChain {
66+
@Configuration(proxyBeanMethods = false)
67+
@ConditionalOnMissingBean(WebFilterChainProxy.class)
68+
@EnableWebFluxSecurity
69+
static class EnableWebFluxSecurityConfiguration {
8270

8371
}
8472

spring-boot-project/spring-boot-autoconfigure/src/test/java/org/springframework/boot/autoconfigure/security/oauth2/resource/reactive/ReactiveOAuth2ResourceServerAutoConfigurationTests.java

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -740,6 +740,7 @@ private Consumer<OAuth2TokenValidator<Jwt>> audClaimValidator() {
740740
.isEqualTo("aud");
741741
}
742742

743+
@EnableWebFluxSecurity
743744
static class TestConfig {
744745

745746
@Bean
@@ -781,7 +782,6 @@ ReactiveOpaqueTokenIntrospector decoder() {
781782

782783
}
783784

784-
@EnableWebFluxSecurity
785785
@Configuration(proxyBeanMethods = false)
786786
static class SecurityWebFilterChainConfig {
787787

spring-boot-project/spring-boot-autoconfigure/src/test/java/org/springframework/boot/autoconfigure/security/reactive/ReactiveSecurityAutoConfigurationTests.java

Lines changed: 14 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -20,7 +20,6 @@
2020
import reactor.core.publisher.Flux;
2121

2222
import org.springframework.boot.autoconfigure.AutoConfigurations;
23-
import org.springframework.boot.autoconfigure.security.reactive.ReactiveSecurityAutoConfiguration.EnableWebFluxSecurityConfiguration;
2423
import org.springframework.boot.test.context.FilteredClassLoader;
2524
import org.springframework.boot.test.context.runner.ReactiveWebApplicationContextRunner;
2625
import org.springframework.context.annotation.Bean;
@@ -53,28 +52,36 @@ void backsOffWhenWebFilterChainProxyBeanPresent() {
5352
}
5453

5554
@Test
56-
void backsOffWhenReactiveAuthenticationManagerNotPresent() {
55+
void autoConfiguresDenyAllReactiveAuthenticationManagerWhenNoAlternativeIsAvailable() {
5756
this.contextRunner.run((context) -> assertThat(context).hasSingleBean(ReactiveSecurityAutoConfiguration.class)
58-
.doesNotHaveBean(EnableWebFluxSecurityConfiguration.class));
57+
.hasBean("denyAllAuthenticationManager"));
5958
}
6059

6160
@Test
6261
void enablesWebFluxSecurityWhenUserDetailsServiceIsPresent() {
63-
this.contextRunner.withUserConfiguration(UserDetailsServiceConfiguration.class)
64-
.run((context) -> assertThat(context).getBean(WebFilterChainProxy.class).isNotNull());
62+
this.contextRunner.withUserConfiguration(UserDetailsServiceConfiguration.class).run((context) -> {
63+
assertThat(context).hasSingleBean(WebFilterChainProxy.class);
64+
assertThat(context).doesNotHaveBean("denyAllAuthenticationManager");
65+
});
6566
}
6667

6768
@Test
6869
void enablesWebFluxSecurityWhenReactiveAuthenticationManagerIsPresent() {
6970
this.contextRunner
7071
.withBean(ReactiveAuthenticationManager.class, () -> mock(ReactiveAuthenticationManager.class))
71-
.run((context) -> assertThat(context).getBean(WebFilterChainProxy.class).isNotNull());
72+
.run((context) -> {
73+
assertThat(context).hasSingleBean(WebFilterChainProxy.class);
74+
assertThat(context).doesNotHaveBean("denyAllAuthenticationManager");
75+
});
7276
}
7377

7478
@Test
7579
void enablesWebFluxSecurityWhenSecurityWebFilterChainIsPresent() {
7680
this.contextRunner.withBean(SecurityWebFilterChain.class, () -> mock(SecurityWebFilterChain.class))
77-
.run((context) -> assertThat(context).getBean(WebFilterChainProxy.class).isNotNull());
81+
.run((context) -> {
82+
assertThat(context).hasSingleBean(WebFilterChainProxy.class);
83+
assertThat(context).doesNotHaveBean("denyAllAuthenticationManager");
84+
});
7885
}
7986

8087
@Test

0 commit comments

Comments
 (0)