Starting with the current Spring Boot 3.2.1-SNAPSHOT there is big difference in the security defaults between webmvc and webflux.
From the 3.2 release notes:
...
spring-security-oauth2-client,spring-security-oauth2-resource-server, andspring-security-saml2-service-providerIf you are using one of the above dependencies yet still require anInMemoryUserDetailsManagerorMapReactiveUserDetailsServicein your application, define the required bean in your application
As soon as you put spring-security-oauth2-resource-server on the classpath:
- a reactive application completely backs out of applying any security
- a servlet application still fails on the secure side.
Note: there was an auto-configuration problem in 3.2.0 for webflux (see https://github.com/spring-projects/spring-boot/issues/38713) - we're now validating the behaviour on 3.2.1-SNAPSHOT:
My test setup:
@SpringBootTest(webEnvironment = RANDOM_PORT)
class SpringBootApplicationTests {
@SpringBootApplication
static class Application {
}
@LocalServerPort
int port;
@Test
void testUnauthorized() {
var client = WebTestClient.bindToServer().baseUrl("http://localhost:" + port).build();
client.get().uri("/").exchange().expectStatus().isUnauthorized();
}
}
With spring-boot-starter-web + spring-boot-starter-security:
* Spring Boot 3.1.6 - :heavy_check_mark: HTTP 401
* Spring Boot 3.2.1-SNAPSHOT - :heavy_check_mark: HTTP 401
With spring-boot-starter-webflux + spring-boot-starter-security:
* Spring Boot 3.1.6 - :heavy_check_mark: HTTP 401
* Spring Boot 3.2.1-SNAPSHOT - :heavy_check_mark: HTTP 401
With spring-boot-starter-web + spring-boot-starter-oauth2-resource-server:
* Spring Boot 3.1.6 - :heavy_check_mark: HTTP 401
* Spring Boot 3.2.1-SNAPSHOT - :heavy_check_mark: HTTP 401
With spring-boot-starter-webflux + spring-boot-starter-oauth2-resource-server:
* Spring Boot 3.1.6 - :heavy_check_mark: HTTP 401
* Spring Boot 3.2.1-SNAPSHOT - :question: HTTP 404
Discussion in https://github.com/spring-projects/spring-boot/issues/38713 implies that all of this is expected behaviour ? * I don't think it is desired that webmvc behaves differently from webflux ? * Can I please stress that adding a starter suddenly stripping security is VERY unexpected ? * This also means that upgrading from Spring Boot 3.1.6 to 3.2.1 will suddenly strip security with default/no configuration, at least for webflux. * Besides being a good idea or not, this is not mentioned in the release notes either
Comment From: wilkinsona
Thanks, @tgeens. The difference in behaviour between MVC and WebFlux is unintended. It may be that our hands are tied as there are some significant differences in how Spring Security is configured for servlet vs reactive apps. We'll take a look.
Comment From: wilkinsona
As I suspected, there's a significant difference in how Spring Security's reactive implementation behaves that limits our options. With a Servlet-based app, we configure a default SecurityFilterChain if neither the user nor any other auto-configuration has provided one:
@Bean
SecurityFilterChain defaultSecurityFilterChain(HttpSecurity http) throws Exception {
http.authorizeHttpRequests((requests) -> requests.anyRequest().authenticated());
http.formLogin(withDefaults());
http.httpBasic(withDefaults());
return http.build();
}
This works well, despite there being no UserDetailsService.
The equivalent filter configuration in a reactive app is the following:
@Bean
SecurityWebFilterChain defaultSecurityFilterChain(ServerHttpSecurity http) throws Exception {
http.authorizeExchange((exchange) -> exchange.anyExchange().authenticated());
http.formLogin(withDefaults());
http.httpBasic(withDefaults());
return http.build();
}
Unfortunately, this doesn't work as basic authentication on the reactive side requires an authentication manager:
Caused by: java.lang.IllegalArgumentException: authenticationManager cannot be null
at org.springframework.util.Assert.notNull(Assert.java:172) ~[spring-core-6.1.2-SNAPSHOT.jar:6.1.2-SNAPSHOT]
at org.springframework.security.web.server.authentication.AuthenticationWebFilter.<init>(AuthenticationWebFilter.java:94) ~[spring-security-web-6.2.1-SNAPSHOT.jar:6.2.1-SNAPSHOT]
at org.springframework.security.config.web.server.ServerHttpSecurity$HttpBasicSpec.configure(ServerHttpSecurity.java:2305) ~[spring-security-config-6.2.1-SNAPSHOT.jar:6.2.1-SNAPSHOT]
One option might be for us to provide an empty authentication manager if there's neither a ReactiveAuthenticationManager nor a ReactiveUserDetailsService in the context:
@Bean
ReactiveAuthenticationManager defaultAuthenticationManager() {
return (authentication) -> Mono.error(new UsernameNotFoundException(authentication.getName()));
}
Comment From: wilkinsona
On the reactive side, the SecurityWebFilterChain doesn't appear to be needed to achieve the same behaviour as the servlet-side – the ReactiveAuthenticationManager that denies access to every Authentication is sufficient.
Comment From: wilkinsona
Closing in favor of the re-opening of #38713. That fix, which has not yet been released, needs to be reconsidered