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

Remote application from devtools does not work with security filter in WebSecurityConfigurerAdapter #25147

Closed
straurob opened this issue Feb 9, 2021 · 13 comments
Assignees
Milestone

Comments

@straurob
Copy link

@straurob straurob commented Feb 9, 2021

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>
@wilkinsona
Copy link
Member

@wilkinsona wilkinsona commented Feb 9, 2021

Thanks for the report. This is due to the changes to RemoteDevtoolsSecurityConfiguration made in 0818f27#diff-377fa85238517c41a5bb705b17f5f6b3b93aee1c5fde0f33fe7c251cae1bffe2 for #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.

@mbhave
Copy link
Contributor

@mbhave mbhave commented Feb 9, 2021

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.

@straurob
Copy link
Author

@straurob straurob commented Feb 9, 2021

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();
    }
}
@wilkinsona
Copy link
Member

@wilkinsona wilkinsona commented Feb 10, 2021

@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();
}
@straurob
Copy link
Author

@straurob straurob commented Feb 10, 2021

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
@wilkinsona
Copy link
Member

@wilkinsona wilkinsona commented Feb 10, 2021

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?

@rwinch
Copy link
Member

@rwinch rwinch commented Feb 10, 2021

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

@wilkinsona
Copy link
Member

@wilkinsona wilkinsona commented Feb 10, 2021

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.

@mbhave mbhave self-assigned this Feb 10, 2021
@straurob
Copy link
Author

@straurob straurob commented Feb 10, 2021

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.

@mbhave
Copy link
Contributor

@mbhave mbhave commented Feb 10, 2021

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

@straurob
Copy link
Author

@straurob straurob commented Feb 10, 2021

@mbhave That's great to hear. Looking forward to the patch release.

@straurob
Copy link
Author

@straurob straurob commented Feb 19, 2021

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.

@rwinch
Copy link
Member

@rwinch rwinch commented Feb 19, 2021

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.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
None yet
Linked pull requests

Successfully merging a pull request may close this issue.

None yet
6 participants