Spring Boot version: 2.4.1
Motivation
I'd like to run my Spring Boot application as a remote application for local development and deploying to a Docker container.
Symptom
When starting the application, the following stack trace is raised. I guess the relevant message is: Found WebSecurityConfigurerAdapter as well as SecurityFilterChain. Please select just one.
When removing spring.devtools.remote.secret
from the configuration, then the application starts but this disables the remote application feature.
2021-02-09 15:31:06.672 ERROR [,,] 1 --- [ main] o.s.boot.SpringApplication : Application run failed
org.springframework.beans.factory.BeanCreationException: Error creating bean with name 'springSecurityFilterChain' defined in class path resource [org/springframework/security/config/annotation/web/configuration/WebSecurityConfiguration.class]: Bean instantiation via factory method failed; nested exception is org.springframework.beans.BeanInstantiationException: Failed to instantiate [javax.servlet.Filter]: Factory method 'springSecurityFilterChain' threw exception; nested exception is java.lang.IllegalStateException: Found WebSecurityConfigurerAdapter as well as SecurityFilterChain. Please select just one.
at org.springframework.beans.factory.support.ConstructorResolver.instantiate(ConstructorResolver.java:658) ~[spring-beans-5.3.2.jar:5.3.2]
at org.springframework.beans.factory.support.ConstructorResolver.instantiateUsingFactoryMethod(ConstructorResolver.java:486) ~[spring-beans-5.3.2.jar:5.3.2]
at org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.instantiateUsingFactoryMethod(AbstractAutowireCapableBeanFactory.java:1336) ~[spring-beans-5.3.2.jar:5.3.2]
at org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.createBeanInstance(AbstractAutowireCapableBeanFactory.java:1179) ~[spring-beans-5.3.2.jar:5.3.2]
at org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.doCreateBean(AbstractAutowireCapableBeanFactory.java:571) ~[spring-beans-5.3.2.jar:5.3.2]
at org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.createBean(AbstractAutowireCapableBeanFactory.java:531) ~[spring-beans-5.3.2.jar:5.3.2]
at org.springframework.beans.factory.support.AbstractBeanFactory.lambda$doGetBean$0(AbstractBeanFactory.java:335) ~[spring-beans-5.3.2.jar:5.3.2]
at org.springframework.beans.factory.support.DefaultSingletonBeanRegistry.getSingleton(DefaultSingletonBeanRegistry.java:234) ~[spring-beans-5.3.2.jar:5.3.2]
at org.springframework.beans.factory.support.AbstractBeanFactory.doGetBean(AbstractBeanFactory.java:333) ~[spring-beans-5.3.2.jar:5.3.2]
at org.springframework.beans.factory.support.AbstractBeanFactory.getBean(AbstractBeanFactory.java:208) ~[spring-beans-5.3.2.jar:5.3.2]
at org.springframework.beans.factory.support.AbstractBeanFactory.doGetBean(AbstractBeanFactory.java:322) ~[spring-beans-5.3.2.jar:5.3.2]
at org.springframework.beans.factory.support.AbstractBeanFactory.getBean(AbstractBeanFactory.java:208) ~[spring-beans-5.3.2.jar:5.3.2]
at org.springframework.beans.factory.support.DefaultListableBeanFactory.preInstantiateSingletons(DefaultListableBeanFactory.java:944) ~[spring-beans-5.3.2.jar:5.3.2]
at org.springframework.context.support.AbstractApplicationContext.finishBeanFactoryInitialization(AbstractApplicationContext.java:923) ~[spring-context-5.3.2.jar:5.3.2]
at org.springframework.context.support.AbstractApplicationContext.refresh(AbstractApplicationContext.java:588) ~[spring-context-5.3.2.jar:5.3.2]
at org.springframework.boot.web.servlet.context.ServletWebServerApplicationContext.refresh(ServletWebServerApplicationContext.java:144) ~[spring-boot-2.4.1.jar:2.4.1]
at org.springframework.boot.SpringApplication.refresh(SpringApplication.java:767) [spring-boot-2.4.1.jar:2.4.1]
at org.springframework.boot.SpringApplication.refresh(SpringApplication.java:759) [spring-boot-2.4.1.jar:2.4.1]
at org.springframework.boot.SpringApplication.refreshContext(SpringApplication.java:426) [spring-boot-2.4.1.jar:2.4.1]
at org.springframework.boot.SpringApplication.run(SpringApplication.java:326) [spring-boot-2.4.1.jar:2.4.1]
at org.springframework.boot.SpringApplication.run(SpringApplication.java:1309) [spring-boot-2.4.1.jar:2.4.1]
at org.springframework.boot.SpringApplication.run(SpringApplication.java:1298) [spring-boot-2.4.1.jar:2.4.1]
at de.uniassist.abis.myabackend.MainApplication.main(MainApplication.java:10) [classes/:0.0.1-SNAPSHOT]
at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method) ~[na:1.8.0_282]
at sun.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:62) ~[na:1.8.0_282]
at sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43) ~[na:1.8.0_282]
at java.lang.reflect.Method.invoke(Method.java:498) ~[na:1.8.0_282]
at org.springframework.boot.loader.MainMethodRunner.run(MainMethodRunner.java:49) [application/:na]
at org.springframework.boot.loader.Launcher.launch(Launcher.java:107) [application/:na]
at org.springframework.boot.loader.Launcher.launch(Launcher.java:58) [application/:na]
at org.springframework.boot.loader.JarLauncher.main(JarLauncher.java:88) [application/:na]
Caused by: org.springframework.beans.BeanInstantiationException: Failed to instantiate [javax.servlet.Filter]: Factory method 'springSecurityFilterChain' threw exception; nested exception is java.lang.IllegalStateException: Found WebSecurityConfigurerAdapter as well as SecurityFilterChain. Please select just one.
at org.springframework.beans.factory.support.SimpleInstantiationStrategy.instantiate(SimpleInstantiationStrategy.java:185) ~[spring-beans-5.3.2.jar:5.3.2]
at org.springframework.beans.factory.support.ConstructorResolver.instantiate(ConstructorResolver.java:653) ~[spring-beans-5.3.2.jar:5.3.2]
... 30 common frames omitted
Caused by: java.lang.IllegalStateException: Found WebSecurityConfigurerAdapter as well as SecurityFilterChain. Please select just one.
at org.springframework.util.Assert.state(Assert.java:76) ~[spring-core-5.3.2.jar:5.3.2]
at org.springframework.security.config.annotation.web.configuration.WebSecurityConfiguration.springSecurityFilterChain(WebSecurityConfiguration.java:107) ~[spring-security-config-5.4.2.jar:5.4.2]
at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method) ~[na:1.8.0_282]
at sun.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:62) ~[na:1.8.0_282]
at sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43) ~[na:1.8.0_282]
at java.lang.reflect.Method.invoke(Method.java:498) ~[na:1.8.0_282]
at org.springframework.beans.factory.support.SimpleInstantiationStrategy.instantiate(SimpleInstantiationStrategy.java:154) ~[spring-beans-5.3.2.jar:5.3.2]
... 31 common frames omitted
Setup
My application uses the following WebSecurityConfigurerAdapter
which adds a JwtRequestFilter extends OncePerRequestFilter
:
@Configuration
@EnableGlobalMethodSecurity(prePostEnabled = true)
public class SecurityConfiguration extends WebSecurityConfigurerAdapter {
private final JwtRequestFilter jwtRequestFilter;
public SecurityConfiguration(JwtRequestFilter jwtRequestFilter) {
this.jwtRequestFilter = jwtRequestFilter;
}
@Bean
public PasswordEncoder passwordEncoder() {
return new BCryptPasswordEncoder();
}
@Bean
@Override
public AuthenticationManager authenticationManagerBean() throws Exception {
return super.authenticationManagerBean();
}
@Override
protected void configure(final HttpSecurity httpSecurity) throws Exception {
httpSecurity
.cors().and()
.csrf().csrfTokenRepository(CookieCsrfTokenRepository.withHttpOnlyFalse())
.and()
.addFilterBefore(jwtRequestFilter, UsernamePasswordAuthenticationFilter.class)
.mvcMatcher("/services/**").authorizeRequests()
.mvcMatchers(PUBLIC_RESOURCES).permitAll()
.anyRequest().authenticated()
.and()
.sessionManagement().sessionCreationPolicy(SessionCreationPolicy.STATELESS);
}
}
pom.xml:
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-security</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-devtools</artifactId>
<scope>runtime</scope>
<optional>true</optional>
</dependency>
<build>
<plugins>
<plugin>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-maven-plugin</artifactId>
<configuration>
<layers>
<enabled>true</enabled>
</layers>
<excludeDevtools>false</excludeDevtools>
<excludes>
<exclude>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
</exclude>
</excludes>
</configuration>
</plugin>
</plugins>
</build>
Comment From: wilkinsona
Thanks for the report. This is due to the changes to RemoteDevtoolsSecurityConfiguration
made in https://github.com/spring-projects/spring-boot/commit/0818f27f44317c52f66b9f0e7030fa8fcb7a92f0#diff-377fa85238517c41a5bb705b17f5f6b3b93aee1c5fde0f33fe7c251cae1bffe2 for https://github.com/spring-projects/spring-boot/issues/23421. I don't think we'd considered this side-effect of switching to WebSecurityCustomizer
and SecurityFilterChain
beans, as recommended by the Spring Security team.
You should be able to get things working again by making a similar change in your code to replace your WebSecurityConfigurerAdapter
with a SecurityFilterChain
.
Comment From: mbhave
Sorry for the inconvenience, @straurob. As Andy said, we didn't consider the case where Spring Boot configures a SecurityFilterChain
that doesn't back off in the presence of a user-configured WebSecurityConfigurerAdapter
.
The only way I can think of fixing this is to look for the presence of a WebSecurityConfigurerAdapter
bean and adapt RemoteDevtoolsSecurityConfiguration
accordingly, to configure either a SecurityFilterChain
or WebSecurityConfigurerAdapter
. However, since using a SecurityFilterChain
is the recommended way going forward, and the switch is fairly easy, it might be better to document this limitation instead.
Comment From: straurob
Thanks for the quick replies, @mbhave and @wilkinsona. As far as I have understood, I changed my class to the following. Is this what you have in mind when switching to using SecurityFilterChain
?
If so, then there is the problem that the AuthenticationManager
bean cannot be overridden as the class does not extend from WebSecurityConfigurerAdapter
anymore.
@Configuration
@EnableGlobalMethodSecurity(prePostEnabled = true)
public class SecurityConfiguration {
private final JwtRequestFilter jwtRequestFilter;
public SecurityConfiguration(JwtRequestFilter jwtRequestFilter) {
this.jwtRequestFilter = jwtRequestFilter;
}
@Bean
public PasswordEncoder passwordEncoder() {
return new BCryptPasswordEncoder();
}
@Bean
@Override
public AuthenticationManager authenticationManagerBean() throws Exception {
return super.authenticationManagerBean();
}
@Bean
public SecurityFilterChain securityFilterChain(HttpSecurity httpSecurity) throws Exception {
httpSecurity
.cors().and()
.csrf().csrfTokenRepository(CookieCsrfTokenRepository.withHttpOnlyFalse())
.and()
.addFilterBefore(jwtRequestFilter, UsernamePasswordAuthenticationFilter.class)
.mvcMatcher("/services/**").authorizeRequests()
.mvcMatchers(PUBLIC_RESOURCES).permitAll()
.anyRequest().authenticated()
.and()
.sessionManagement().sessionCreationPolicy(SessionCreationPolicy.STATELESS);
return httpSecurity.build();
}
}
Comment From: wilkinsona
@straurob I believe you can define an AuthenticationManager
bean using the AuthenticationManagerBuilder
. This is essentially what the method override on your WebSecurityConfigurerAdapter
was doing for you. Something like this:
@Bean
public AuthenticationManager authenticationManager(AuthenticationManagerBuilder builder) {
return builder.getOrBuild();
}
Comment From: straurob
Tried that but this gives the following exception when starting the application. Inspecting this shows that builder.getOrBuild()
returns null
.
Caused by: org.springframework.beans.factory.NoSuchBeanDefinitionException: No qualifying bean of type 'org.springframework.security.authentication.AuthenticationManager' available: expected at least 1 bean which qualifies as autowire candidate. Dependency annotations: {}
at org.springframework.beans.factory.support.DefaultListableBeanFactory.raiseNoMatchingBeanFound(DefaultListableBeanFactory.java:1790) ~[spring-beans-5.3.3.jar:5.3.3]
at org.springframework.beans.factory.support.DefaultListableBeanFactory.doResolveDependency(DefaultListableBeanFactory.java:1385) ~[spring-beans-5.3.3.jar:5.3.3]
at org.springframework.beans.factory.support.DefaultListableBeanFactory.resolveDependency(DefaultListableBeanFactory.java:1300) ~[spring-beans-5.3.3.jar:5.3.3]
at org.springframework.beans.factory.support.ConstructorResolver.resolveAutowiredArgument(ConstructorResolver.java:887) ~[spring-beans-5.3.3.jar:5.3.3]
at org.springframework.beans.factory.support.ConstructorResolver.createArgumentArray(ConstructorResolver.java:791) ~[spring-beans-5.3.3.jar:5.3.3]
... 39 common frames omitted
Comment From: wilkinsona
Thanks for giving it a try. The null
indicates that building failed. There's a debug log message that includes the exception or you should be able to see it in the debugger.
I would guess that it's some sort of ordering problem and you may be running into the situation that org.springframework.security.config.annotation.web.configuration.WebSecurityConfigurerAdapter.AuthenticationManagerDelegator addresses. Unfortunately that lazy delegator is package-private so you can't reuse it.
@rwinch Can you please guide us here? What's the recommended way to define an AuthenticationManager
bean when not sub-classing WebSecurityConfigurerAdapter
?
Comment From: rwinch
Exposing an AuthenticationManager
as a Bean. I'd honestly steer away from the AuthenticationManagerBuilder
as it doesn't really provide a lot of value in most cases and causes more problems than it solves.
As @wilkinsona mentioned ordering can cause problems at times. I'd recommend either extracting the AuthenticationManager
bean to a separate configuration and/or using a static method for the Bean definition.
@straurob If you are still struggling after my suggestions, please ping me with an updated sample and I can take a look
Comment From: wilkinsona
Thanks, Rob. Without using AuthenticationManagerBuilder
, what's the recommended way to define an AuthenticationManager
bean that's equivalent to overriding authenticationManagerBean
on WebSecurityConfiguerAdapter
when configure(AuthenticationManagerBuilder auth)
hasn't also been overridden.
Comment From: straurob
I'd recommend either extracting the
AuthenticationManager
bean to a separate configuration and/or using a static method for the Bean definition.@straurob If you are still struggling after my suggestions, please ping me with an updated sample and I can take a look
Thanks, @rwinch. Guess I need to come back to your offer. I'll try to setup a concrete example. Maybe you could provide some kind of "generic example" in the meantime? Maybe I just need a basic idea as a starting point.
Comment From: mbhave
@straurob In case you missed this, we will be fixing this regression in the next Spring Boot 2.4.x patch release. While moving to SecurityFilterChain
is recommended, if that is something you're unable to do at this time, upgrading to the next patch release when it is released should solve your issue.
Comment From: straurob
@mbhave That's great to hear. Looking forward to the patch release.
Comment From: straurob
As @rwinch wrote in a recent comment:
Exposing an AuthenticationManager as a Bean. I'd honestly steer away from the AuthenticationManagerBuilder as it doesn't really provide a lot of value in most cases and causes more problems than it solves.
After having update to 2.4.3, I tried to do it this way. But this doesn't take your hint into account.
@Bean
public AuthenticationManager authenticationManagerBean(AuthenticationManagerBuilder builder) {
return builder.getOrBuild();
}
Then the application won't start and gives the following exception:
No qualifying bean of type 'org.springframework.security.authentication.AuthenticationManager' available
I guess I'm having some trouble to setup the bean correctly.
Comment From: rwinch
Thanks, Rob. Without using
AuthenticationManagerBuilder
, what's the recommended way to define anAuthenticationManager
bean that's equivalent to overridingauthenticationManagerBean
onWebSecurityConfiguerAdapter
whenconfigure(AuthenticationManagerBuilder auth)
hasn't also been overridden.
Just provide a bean without using the builder.