Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Excluding ReactiveUserDetailsServiceAutoConfiguration can cause a WebFlux app to fail to start due to a null authentication manager #37504

Closed
wilkinsona opened this issue Sep 21, 2023 · 7 comments
Assignees
Labels
type: bug A general bug
Milestone

Comments

@wilkinsona
Copy link
Member

While looking at the reactive side of #35338 I have learned that an app that depends on spring-boot-starter-security and spring-boot-starter-webflux will fail to start if ReactiveUserDetailsServiceAutoConfiguration is excluded:

org.springframework.beans.factory.BeanCreationException: Error creating bean with name 'httpHandler' defined in class path resource [org/springframework/boot/autoconfigure/web/reactive/HttpHandlerAutoConfiguration$AnnotationConfig.class]: Bean instantiation via factory method failed; nested exception is org.springframework.beans.BeanInstantiationException: Failed to instantiate [org.springframework.http.server.reactive.HttpHandler]: Factory method 'httpHandler' threw exception; nested exception is org.springframework.beans.factory.BeanCreationException: Error creating bean with name 'org.springframework.security.config.annotation.web.reactive.WebFluxSecurityConfiguration.WebFilterChainFilter' defined in class path resource [org/springframework/security/config/annotation/web/reactive/WebFluxSecurityConfiguration.class]: Bean instantiation via factory method failed; nested exception is org.springframework.beans.BeanInstantiationException: Failed to instantiate [org.springframework.security.web.server.WebFilterChainProxy]: Factory method 'springSecurityWebFilterChainFilter' threw exception; nested exception is java.lang.IllegalArgumentException: authenticationManager cannot be null
        at org.springframework.beans.factory.support.ConstructorResolver.instantiate(ConstructorResolver.java:653) ~[spring-beans-5.3.30.jar:5.3.30]
        at org.springframework.beans.factory.support.ConstructorResolver.instantiateUsingFactoryMethod(ConstructorResolver.java:633) ~[spring-beans-5.3.30.jar:5.3.30]
        at org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.instantiateUsingFactoryMethod(AbstractAutowireCapableBeanFactory.java:1352) ~[spring-beans-5.3.30.jar:5.3.30]
        at org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.createBeanInstance(AbstractAutowireCapableBeanFactory.java:1195) ~[spring-beans-5.3.30.jar:5.3.30]
        at org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.doCreateBean(AbstractAutowireCapableBeanFactory.java:582) ~[spring-beans-5.3.30.jar:5.3.30]
        at org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.createBean(AbstractAutowireCapableBeanFactory.java:542) ~[spring-beans-5.3.30.jar:5.3.30]
        at org.springframework.beans.factory.support.AbstractBeanFactory.lambda$doGetBean$0(AbstractBeanFactory.java:335) ~[spring-beans-5.3.30.jar:5.3.30]
        at org.springframework.beans.factory.support.DefaultSingletonBeanRegistry.getSingleton(DefaultSingletonBeanRegistry.java:234) ~[spring-beans-5.3.30.jar:5.3.30]
        at org.springframework.beans.factory.support.AbstractBeanFactory.doGetBean(AbstractBeanFactory.java:333) ~[spring-beans-5.3.30.jar:5.3.30]
        at org.springframework.beans.factory.support.AbstractBeanFactory.getBean(AbstractBeanFactory.java:208) ~[spring-beans-5.3.30.jar:5.3.30]
        at org.springframework.beans.factory.support.DefaultListableBeanFactory.preInstantiateSingletons(DefaultListableBeanFactory.java:955) ~[spring-beans-5.3.30.jar:5.3.30]
        at org.springframework.context.support.AbstractApplicationContext.finishBeanFactoryInitialization(AbstractApplicationContext.java:921) ~[spring-context-5.3.30.jar:5.3.30]
        at org.springframework.context.support.AbstractApplicationContext.refresh(AbstractApplicationContext.java:583) ~[spring-context-5.3.30.jar:5.3.30]
        at org.springframework.boot.web.reactive.context.ReactiveWebServerApplicationContext.refresh(ReactiveWebServerApplicationContext.java:66) ~[spring-boot-2.7.16.jar:2.7.16]
        at org.springframework.boot.SpringApplication.refresh(SpringApplication.java:731) ~[spring-boot-2.7.16.jar:2.7.16]
        at org.springframework.boot.SpringApplication.refreshContext(SpringApplication.java:408) ~[spring-boot-2.7.16.jar:2.7.16]
        at org.springframework.boot.SpringApplication.run(SpringApplication.java:307) ~[spring-boot-2.7.16.jar:2.7.16]
        at org.springframework.boot.SpringApplication.run(SpringApplication.java:1303) ~[spring-boot-2.7.16.jar:2.7.16]
        at org.springframework.boot.SpringApplication.run(SpringApplication.java:1292) ~[spring-boot-2.7.16.jar:2.7.16]
        at com.example.reactivesecurityproblem.ReactiveSecurityProblemApplication.main(ReactiveSecurityProblemApplication.java:10) ~[main/:na]
Caused by: org.springframework.beans.BeanInstantiationException: Failed to instantiate [org.springframework.http.server.reactive.HttpHandler]: Factory method 'httpHandler' threw exception; nested exception is org.springframework.beans.factory.BeanCreationException: Error creating bean with name 'org.springframework.security.config.annotation.web.reactive.WebFluxSecurityConfiguration.WebFilterChainFilter' defined in class path resource [org/springframework/security/config/annotation/web/reactive/WebFluxSecurityConfiguration.class]: Bean instantiation via factory method failed; nested exception is org.springframework.beans.BeanInstantiationException: Failed to instantiate [org.springframework.security.web.server.WebFilterChainProxy]: Factory method 'springSecurityWebFilterChainFilter' threw exception; nested exception is java.lang.IllegalArgumentException: authenticationManager cannot be null
        at org.springframework.beans.factory.support.SimpleInstantiationStrategy.instantiate(SimpleInstantiationStrategy.java:185) ~[spring-beans-5.3.30.jar:5.3.30]
        at org.springframework.beans.factory.support.ConstructorResolver.instantiate(ConstructorResolver.java:648) ~[spring-beans-5.3.30.jar:5.3.30]
        ... 19 common frames omitted
Caused by: org.springframework.beans.factory.BeanCreationException: Error creating bean with name 'org.springframework.security.config.annotation.web.reactive.WebFluxSecurityConfiguration.WebFilterChainFilter' defined in class path resource [org/springframework/security/config/annotation/web/reactive/WebFluxSecurityConfiguration.class]: Bean instantiation via factory method failed; nested exception is org.springframework.beans.BeanInstantiationException: Failed to instantiate [org.springframework.security.web.server.WebFilterChainProxy]: Factory method 'springSecurityWebFilterChainFilter' threw exception; nested exception is java.lang.IllegalArgumentException: authenticationManager cannot be null
        at org.springframework.beans.factory.support.ConstructorResolver.instantiate(ConstructorResolver.java:653) ~[spring-beans-5.3.30.jar:5.3.30]
        at org.springframework.beans.factory.support.ConstructorResolver.instantiateUsingFactoryMethod(ConstructorResolver.java:481) ~[spring-beans-5.3.30.jar:5.3.30]
        at org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.instantiateUsingFactoryMethod(AbstractAutowireCapableBeanFactory.java:1352) ~[spring-beans-5.3.30.jar:5.3.30]
        at org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.createBeanInstance(AbstractAutowireCapableBeanFactory.java:1195) ~[spring-beans-5.3.30.jar:5.3.30]
        at org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.doCreateBean(AbstractAutowireCapableBeanFactory.java:582) ~[spring-beans-5.3.30.jar:5.3.30]
        at org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.createBean(AbstractAutowireCapableBeanFactory.java:542) ~[spring-beans-5.3.30.jar:5.3.30]
        at org.springframework.beans.factory.support.AbstractBeanFactory.lambda$doGetBean$0(AbstractBeanFactory.java:335) ~[spring-beans-5.3.30.jar:5.3.30]
        at org.springframework.beans.factory.support.DefaultSingletonBeanRegistry.getSingleton(DefaultSingletonBeanRegistry.java:234) ~[spring-beans-5.3.30.jar:5.3.30]
        at org.springframework.beans.factory.support.AbstractBeanFactory.doGetBean(AbstractBeanFactory.java:333) ~[spring-beans-5.3.30.jar:5.3.30]
        at org.springframework.beans.factory.support.AbstractBeanFactory.getBean(AbstractBeanFactory.java:208) ~[spring-beans-5.3.30.jar:5.3.30]
        at org.springframework.beans.factory.support.DefaultListableBeanFactory$1.orderedStream(DefaultListableBeanFactory.java:481) ~[spring-beans-5.3.30.jar:5.3.30]
        at org.springframework.web.server.adapter.WebHttpHandlerBuilder.applicationContext(WebHttpHandlerBuilder.java:172) ~[spring-web-5.3.30.jar:5.3.30]
        at org.springframework.boot.autoconfigure.web.reactive.HttpHandlerAutoConfiguration$AnnotationConfig.httpHandler(HttpHandlerAutoConfiguration.java:64) ~[spring-boot-autoconfigure-2.7.16.jar:2.7.16]
        at java.base/jdk.internal.reflect.NativeMethodAccessorImpl.invoke0(Native Method) ~[na:na]
        at java.base/jdk.internal.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:77) ~[na:na]
        at java.base/jdk.internal.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43) ~[na:na]
        at java.base/java.lang.reflect.Method.invoke(Method.java:568) ~[na:na]
        at org.springframework.beans.factory.support.SimpleInstantiationStrategy.instantiate(SimpleInstantiationStrategy.java:154) ~[spring-beans-5.3.30.jar:5.3.30]
        ... 20 common frames omitted
Caused by: org.springframework.beans.BeanInstantiationException: Failed to instantiate [org.springframework.security.web.server.WebFilterChainProxy]: Factory method 'springSecurityWebFilterChainFilter' threw exception; nested exception is java.lang.IllegalArgumentException: authenticationManager cannot be null
        at org.springframework.beans.factory.support.SimpleInstantiationStrategy.instantiate(SimpleInstantiationStrategy.java:185) ~[spring-beans-5.3.30.jar:5.3.30]
        at org.springframework.beans.factory.support.ConstructorResolver.instantiate(ConstructorResolver.java:648) ~[spring-beans-5.3.30.jar:5.3.30]
        ... 37 common frames omitted
Caused by: java.lang.IllegalArgumentException: authenticationManager cannot be null
        at org.springframework.util.Assert.notNull(Assert.java:201) ~[spring-core-5.3.30.jar:5.3.30]
        at org.springframework.security.web.server.authentication.AuthenticationWebFilter.<init>(AuthenticationWebFilter.java:94) ~[spring-security-web-5.7.11.jar:5.7.11]
        at org.springframework.security.config.web.server.ServerHttpSecurity$HttpBasicSpec.configure(ServerHttpSecurity.java:2117) ~[spring-security-config-5.7.11.jar:5.7.11]
        at org.springframework.security.config.web.server.ServerHttpSecurity.build(ServerHttpSecurity.java:1409) ~[spring-security-config-5.7.11.jar:5.7.11]
        at org.springframework.security.config.annotation.web.reactive.WebFluxSecurityConfiguration.springSecurityFilterChain(WebFluxSecurityConfiguration.java:109) ~[spring-security-config-5.7.11.jar:5.7.11]
        at org.springframework.security.config.annotation.web.reactive.WebFluxSecurityConfiguration.springSecurityFilterChain(WebFluxSecurityConfiguration.java:92) ~[spring-security-config-5.7.11.jar:5.7.11]
        at org.springframework.security.config.annotation.web.reactive.WebFluxSecurityConfiguration.getSecurityWebFilterChains(WebFluxSecurityConfiguration.java:85) ~[spring-security-config-5.7.11.jar:5.7.11]
        at org.springframework.security.config.annotation.web.reactive.WebFluxSecurityConfiguration.springSecurityWebFilterChainFilter(WebFluxSecurityConfiguration.java:69) ~[spring-security-config-5.7.11.jar:5.7.11]
        at java.base/jdk.internal.reflect.NativeMethodAccessorImpl.invoke0(Native Method) ~[na:na]
        at java.base/jdk.internal.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:77) ~[na:na]
        at java.base/jdk.internal.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43) ~[na:na]
        at java.base/java.lang.reflect.Method.invoke(Method.java:568) ~[na:na]
        at org.springframework.beans.factory.support.SimpleInstantiationStrategy.instantiate(SimpleInstantiationStrategy.java:154) ~[spring-beans-5.3.30.jar:5.3.30]
        ... 38 common frames omitted

The problem does not occur if ReactiveSecurityAutoConfiguration is also excluded. Perhaps ReactiveSecurityAutoConfiguration needs to back off in the absence of an AuthenticationManager bean but we'll need to be careful about unintended side-effects.

@wilkinsona wilkinsona added the type: bug A general bug label Sep 21, 2023
@wilkinsona wilkinsona added this to the 2.7.x milestone Sep 21, 2023
@wilkinsona wilkinsona self-assigned this Sep 25, 2023
@wilkinsona
Copy link
Member Author

needs to back off in the absence of an AuthenticationManager bean

It's more complex than this. Spring Security can either use an AuthenticationManager bean directly or it can use a ReactiveUserDetailsService from which it will create a UserDetailsRepositoryReactiveAuthenticationManager.

With this extra complexity, I think there's an increased risk of regression. Given that this hasn't been reported by users, I intend to only fix this in 3.2.x to help with #35338. We can then back port the changes if needed and if they haven't caused any problems.

@wilkinsona wilkinsona modified the milestones: 2.7.x, 3.2.x Sep 25, 2023
@wilkinsona
Copy link
Member Author

There's further complexity. Whether or not @EnableWebFluxSecurity requires an AuthenticationManager depends on the security configuration. For example, the original stack trace in this issue shows a failure when trying to use HTTP basic without an authentication manager. If another form of security is being used, there may be no failure.

With a change in place to only enable WebFlux security when there's an AuthenticationManager bean or a UserDetailsRepositoryReactiveAuthenticationManager, ReactiveManagementWebSecurityAutoConfigurationTests.backOffIfReactiveOAuth2ResourceServerAutoConfigurationPresent() fails because there's no springSecurityFilterChain bean.

@wilkinsona wilkinsona modified the milestones: 3.2.x, 3.2.0-RC1 Sep 25, 2023
@wilkinsona
Copy link
Member Author

Re-opening as one of the conditions is wrong. It should be using ReactiveAuthenticationManager but it is using AuthenticationManager instead.

@wilkinsona wilkinsona reopened this Sep 29, 2023
spencergibb added a commit to spring-cloud/spring-cloud-gateway that referenced this issue Sep 29, 2023
See spring-projects/spring-boot#37504

Adds @EnableWebFluxSecurity and copied MapReactiveUserDetailsService beans from boot.
@wilkinsona
Copy link
Member Author

With thanks to @jzheaux, we also need to consider the case where the user's defined a SecurityWebFilterChain. In that case a ReactiveAuthenticationManager bean may not be needed as it could have been configured directly using the DSL.

@sanderino666
Copy link

This issue occurred in one of our test classes when upgrading to Spring Boot 3.2.0 (Webflux app). As stated in the release notes you need to define a bean if you need the MapReactiveUserDetailsService (https://github.com/spring-projects/spring-boot/wiki/Spring-Boot-3.2-Release-Notes#auto-configured-user-details-service).

For people searching for a potential solution, adding the snippet below in a test config solved it for us.

@Bean
MapReactiveUserDetailsService userDetailsService() {
  UserDetails userDetails = User.withUsername("admin").password("admin").roles("ADMIN").build();
  return new MapReactiveUserDetailsService(List.of(userDetails));
}

wilkinsona added a commit that referenced this issue Dec 11, 2023
Following the changes in gh-37504, the reactive resource server
auto-configuration could enable WebFlux security in situations where
it was otherwise in active. This could then result in an application
failing to start as no authentication manager is available.

This commit updates the configurations that enable WebFlux security
so that they fully back off unless their related configurations are
active. Previously, only the configuration of the
SecurityWebFilterChain would back off. This has been expanded to
cover `@EnableWebFluxSecurity` as well. This has required splitting
the configuration classes up so that the condition evaluation order
can be controlled more precisely. We need to ensure that the JWT
decoder bean or the opaque token introspector bean has been defined
before evaluation of the conditions for `@EnableWebFluxSecurity`.
Without this control, the import through `@EnableWebFluxSecurity` in
one location where the conditions do not matchcan prevent a
successful import in another where they do.

Fixes gh-38713
@NadChel
Copy link

NadChel commented Mar 28, 2024

Came here guided by this comment on TestEnableWebfluxSecurityAutoConfiguration hidden in the depths of spring-cloud-gateway-server's test directory

/*
 See https://github.com/spring-projects/spring-boot/issues/37504.
 Because gateway has normal spring security and spring security oauth 2
 we need an explicit @EnableWebFluxSecurity and a ReactiveUserDetailsService
 */
@AutoConfiguration(before = ReactiveSecurityAutoConfiguration.class)
@ConditionalOnClass({ DispatcherHandler.class, MapReactiveUserDetailsService.class })
@EnableWebFluxSecurity
public class TestEnableWebfluxSecurityAutoConfiguration {

Have been painfully debugging for two days trying to figure out why on earth I have a WebFilterChainProxy in my test's context (along with its overprotective filters) even after explicitly excluding ReactiveSecurityAutoConfiguration (couldn't reproduce it elsewhere). I found the culprit!

Are you all absolutely sure we can't get rid of it and solve this issue a better way? I'm sorry I'm not suggesting anything specific, but I'm done with debugging for a while

@wilkinsona
Copy link
Member Author

I suspect that code in Spring Cloud Gateway can be removed or at least refined thanks to #38713.

plum7ree added a commit to plum7ree/food-delivery that referenced this issue Jul 15, 2024
2. gateway oauth2login + oauth2ResourceServer + MapReactiveUserDetailService implemented.

from spring 3.2.x, need to implement MapReactiveUserDetailService by my own. ref: spring-projects/spring-boot#37504 (comment)
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
type: bug A general bug
Projects
None yet
Development

No branches or pull requests

3 participants