Make reactive security back off without authentication manager

If there's no authentication manager bean or no bean from which
one can be created, Spring Security's reactive support may fail to
bootstrap due to a null authentication manager.

This commit causes the auto-configuration that enables WebFlux
security to back off in the absence of an AuthenticationManager bean
and a ReactiveUserDetailsService (from which Spring Security can
create an AuthenticationManager) bean. Other reactive security
auto-configuration that can configure things such that WebFlux security
can be bootstrapped without an AuthenticationManager has been updated
to enable WebFlux security rather than relying on another
auto-configuration class to do so.

Fixes gh-37504
This commit is contained in:
Andy Wilkinson 2023-09-25 11:51:58 +01:00
parent 1d60e42a73
commit ee9c74556d
6 changed files with 54 additions and 4 deletions

View File

@ -35,6 +35,7 @@ import org.springframework.boot.autoconfigure.security.oauth2.resource.OAuth2Res
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Conditional;
import org.springframework.context.annotation.Configuration;
import org.springframework.security.config.annotation.web.reactive.EnableWebFluxSecurity;
import org.springframework.security.config.web.server.ServerHttpSecurity;
import org.springframework.security.config.web.server.ServerHttpSecurity.OAuth2ResourceServerSpec;
import org.springframework.security.oauth2.core.DelegatingOAuth2TokenValidator;
@ -49,6 +50,7 @@ import org.springframework.security.oauth2.jwt.NimbusReactiveJwtDecoder.JwkSetUr
import org.springframework.security.oauth2.jwt.ReactiveJwtDecoder;
import org.springframework.security.oauth2.jwt.SupplierReactiveJwtDecoder;
import org.springframework.security.web.server.SecurityWebFilterChain;
import org.springframework.security.web.server.WebFilterChainProxy;
import org.springframework.util.CollectionUtils;
/**
@ -177,6 +179,13 @@ class ReactiveOAuth2ResourceServerJwkConfiguration {
server.jwt((jwt) -> jwt.jwtDecoder(decoder));
}
@Configuration(proxyBeanMethods = false)
@ConditionalOnMissingBean(WebFilterChainProxy.class)
@EnableWebFluxSecurity
static class EnableWebFluxSecurityConfiguration {
}
}
}

View File

@ -22,10 +22,12 @@ import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty;
import org.springframework.boot.autoconfigure.security.oauth2.resource.OAuth2ResourceServerProperties;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.security.config.annotation.web.reactive.EnableWebFluxSecurity;
import org.springframework.security.config.web.server.ServerHttpSecurity;
import org.springframework.security.oauth2.server.resource.introspection.ReactiveOpaqueTokenIntrospector;
import org.springframework.security.oauth2.server.resource.introspection.SpringReactiveOpaqueTokenIntrospector;
import org.springframework.security.web.server.SecurityWebFilterChain;
import org.springframework.security.web.server.WebFilterChainProxy;
import static org.springframework.security.config.Customizer.withDefaults;
@ -64,6 +66,13 @@ class ReactiveOAuth2ResourceServerOpaqueTokenConfiguration {
return http.build();
}
@Configuration(proxyBeanMethods = false)
@ConditionalOnMissingBean(WebFilterChainProxy.class)
@EnableWebFluxSecurity
static class EnableWebFluxSecurityConfiguration {
}
}
}

View File

@ -1,5 +1,5 @@
/*
* Copyright 2012-2022 the original author or authors.
* Copyright 2012-2023 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@ -20,13 +20,18 @@ import reactor.core.publisher.Flux;
import org.springframework.boot.autoconfigure.AutoConfiguration;
import org.springframework.boot.autoconfigure.EnableAutoConfiguration;
import org.springframework.boot.autoconfigure.condition.AnyNestedCondition;
import org.springframework.boot.autoconfigure.condition.ConditionalOnBean;
import org.springframework.boot.autoconfigure.condition.ConditionalOnClass;
import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean;
import org.springframework.boot.autoconfigure.condition.ConditionalOnWebApplication;
import org.springframework.boot.autoconfigure.security.SecurityProperties;
import org.springframework.boot.context.properties.EnableConfigurationProperties;
import org.springframework.context.annotation.Conditional;
import org.springframework.context.annotation.Configuration;
import org.springframework.security.authentication.AuthenticationManager;
import org.springframework.security.config.annotation.web.reactive.EnableWebFluxSecurity;
import org.springframework.security.core.userdetails.ReactiveUserDetailsService;
import org.springframework.security.web.server.WebFilterChainProxy;
import org.springframework.web.reactive.config.WebFluxConfigurer;
@ -49,9 +54,28 @@ public class ReactiveSecurityAutoConfiguration {
@Configuration(proxyBeanMethods = false)
@ConditionalOnMissingBean(WebFilterChainProxy.class)
@ConditionalOnWebApplication(type = ConditionalOnWebApplication.Type.REACTIVE)
@Conditional(ReactiveAuthenticationManagerCondition.class)
@EnableWebFluxSecurity
static class EnableWebFluxSecurityConfiguration {
}
static final class ReactiveAuthenticationManagerCondition extends AnyNestedCondition {
ReactiveAuthenticationManagerCondition() {
super(ConfigurationPhase.REGISTER_BEAN);
}
@ConditionalOnBean(AuthenticationManager.class)
static final class ConditionalOnAuthenticationManagerBean {
}
@ConditionalOnBean(ReactiveUserDetailsService.class)
static final class ConditionalOnReactiveUserDetailsService {
}
}
}

View File

@ -1,5 +1,5 @@
/*
* Copyright 2012-2022 the original author or authors.
* Copyright 2012-2023 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@ -55,7 +55,7 @@ import org.springframework.util.StringUtils;
* @author Madhura Bhave
* @since 2.0.0
*/
@AutoConfiguration(after = RSocketMessagingAutoConfiguration.class)
@AutoConfiguration(before = ReactiveSecurityAutoConfiguration.class, after = RSocketMessagingAutoConfiguration.class)
@ConditionalOnClass({ ReactiveAuthenticationManager.class })
@ConditionalOnMissingBean(
value = { ReactiveAuthenticationManager.class, ReactiveUserDetailsService.class,

View File

@ -740,7 +740,6 @@ class ReactiveOAuth2ResourceServerAutoConfigurationTests {
.isEqualTo("aud");
}
@EnableWebFluxSecurity
static class TestConfig {
@Bean
@ -782,6 +781,7 @@ class ReactiveOAuth2ResourceServerAutoConfigurationTests {
}
@EnableWebFluxSecurity
@Configuration(proxyBeanMethods = false)
static class SecurityWebFilterChainConfig {

View File

@ -20,6 +20,7 @@ import org.junit.jupiter.api.Test;
import reactor.core.publisher.Flux;
import org.springframework.boot.autoconfigure.AutoConfigurations;
import org.springframework.boot.autoconfigure.security.reactive.ReactiveSecurityAutoConfiguration.EnableWebFluxSecurityConfiguration;
import org.springframework.boot.test.context.FilteredClassLoader;
import org.springframework.boot.test.context.runner.ReactiveWebApplicationContextRunner;
import org.springframework.context.annotation.Bean;
@ -47,6 +48,13 @@ class ReactiveSecurityAutoConfigurationTests {
.run((context) -> assertThat(context).hasSingleBean(WebFilterChainProxy.class));
}
@Test
void backsOffWhenReactiveAuthenticationManagerNotPresent() {
this.contextRunner.withConfiguration(AutoConfigurations.of(ReactiveSecurityAutoConfiguration.class))
.run((context) -> assertThat(context).hasSingleBean(ReactiveSecurityAutoConfiguration.class)
.doesNotHaveBean(EnableWebFluxSecurityConfiguration.class));
}
@Test
void enablesWebFluxSecurity() {
this.contextRunner