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
pull/37473/head
Andy Wilkinson 1 year ago
parent 1d60e42a73
commit ee9c74556d

@ -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 {
}
}
}

@ -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 {
}
}
}

@ -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 {
}
}
}

@ -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,

@ -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 {

@ -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

Loading…
Cancel
Save