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 an AuthenticationManager bean that's equivalent to overriding authenticationManagerBean on WebSecurityConfiguerAdapter when configure(AuthenticationManagerBuilder auth) hasn't also been overridden.

Just provide a bean without using the builder.