[spring-projects/spring-boot]来自 devtools 的远程应用程序无法与 WebSecurityConfigurerAdapter 中的安全过滤器一起使用

2024-05-09 215 views
6

我想将 Spring Boot 应用程序作为远程应用程序运行,以进行本地开发并部署到 Docker 容器。

启动应用程序时,将引发以下堆栈跟踪。我猜相关消息是:Found WebSecurityConfigurerAdapter as well as SecurityFilterChain. Please select just one.

spring.devtools.remote.secret从配置中删除时,应用程序将启动,但这会禁用远程应用程序功能。

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
设置

我的应用程序使用以下内容WebSecurityConfigurerAdapter添加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>

回答

1

感谢您的报告。这是由于https://github.com/spring-projects/spring-boot/commit/0818f27f44317c52f66b9f0e7030fa8fcb7a92f0#diff-377fa85238517c41a5bb705b17f5f6b3b93aee1c5fde0f33fe7c251cae1RemoteDevtoolsSecurityConfiguration中所做的更改bffe2用于https://github.com/spring-projects/spring-boot/issues /23421 .我认为我们没有考虑到 Spring Security 团队建议的切换到 Bean的副作用。WebSecurityCustomizerSecurityFilterChain

您应该能够通过在代码中进行类似的更改以将您的替换WebSecurityConfigurerAdapterSecurityFilterChain.

7

很抱歉给您带来不便,@straurob。正如 Andy 所说,我们没有考虑 Spring Boot 配置 的情况,SecurityFilterChain该情况在用户配置的WebSecurityConfigurerAdapter.

我能想到解决这个问题的唯一方法是寻找WebSecurityConfigurerAdapterbean 的存在并RemoteDevtoolsSecurityConfiguration相应地进行调整,以配置 aSecurityFilterChainWebSecurityConfigurerAdapter.但是,由于使用 aSecurityFilterChain是推荐的前进方式,并且切换相当容易,因此最好记录此限制。

7

感谢@mbhave 和@wilkinsona 的快速回复。据我所知,我将课程更改为以下内容。这是您在切换到使用时想到的吗SecurityFilterChain

如果是这样,那么就会存在无法重写该 bean 的问题,AuthenticationManager因为该类不再扩展自该类WebSecurityConfigurerAdapter

@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();
    }
}
3

@straurob我相信你可以AuthenticationManager使用AuthenticationManagerBuilder.这本质上就是您的方法重写WebSecurityConfigurerAdapter为您所做的事情。像这样的东西:

@Bean
public AuthenticationManager authenticationManager(AuthenticationManagerBuilder builder) {
    return builder.getOrBuild();
}
4

尝试过,但这在启动应用程序时出现以下异常。检查这表明builder.getOrBuild()返回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
3

感谢您尝试一下。表明null构建失败。有一条包含异常的调试日志消息,或者您应该能够在调试器中看到它。

我猜想这是某种排序问题,您可能会遇到 org.springframework.security.config.annotation.web.configuration.WebSecurityConfigurerAdapter.AuthenticationManagerDelegator 解决的情况。不幸的是,惰性委托者是包私有的,所以你不能重用它。

@rwinch 你能在这里指导我们吗?AuthenticationManager在不进行子类化时定义 bean 的推荐方法是什么WebSecurityConfigurerAdapter

8

将 an 公开AuthenticationManager为 Bean。老实说,我会避开它,AuthenticationManagerBuilder因为它在大多数情况下并没有真正提供很多价值,而且造成的问题比它解决的问题还要多。

正如 @wilkinsona 提到的,排序有时会引起问题。我建议将AuthenticationManagerBean 提取到单独的配置中和/或使用 Bean 定义的静态方法。

@straurob 如果您在我的建议之后仍然遇到困难,请向我发送更新的示例,我可以看一下

3

谢谢,罗布。如果不使用AuthenticationManagerBuilder,定义一个AuthenticationManagerbean 的推荐方法是什么,它相当于authenticationManagerBean在尚未被覆盖WebSecurityConfiguerAdapter时覆盖。configure(AuthenticationManagerBuilder auth)

4

我建议将AuthenticationManagerBean 提取到单独的配置中和/或使用 Bean 定义的静态方法。

@straurob 如果您在我的建议之后仍然遇到困难,请向我发送更新的示例,我可以看一下

谢谢,@rwinch。我想我需要再讨论一下你的提议。我将尝试设置一个具体的例子。也许您可以同时提供某种“通用示例”?也许我只需要一个基本想法作为起点。

3

@straurob 如果您错过了这一点,我们将在下一个 Spring Boot 2.4.x 补丁版本中修复此回归。虽然建议迁移到SecurityFilterChain,但如果您目前无法执行此操作,则在发布下一个补丁版本时升级到该版本应该可以解决您的问题。

1

@mbhave 很高兴听到这个消息。期待补丁发布。

9

正如 @rwinch 在最近的评论中写道:

将 AuthenticationManager 作为 Bean 公开。老实说,我会避开 AuthenticationManagerBuilder,因为它在大多数情况下并没有真正提供很多价值,并且导致的问题比它解决的问题还要多。

更新到2.4.3后,我尝试这样做。但这并没有考虑您的提示。

@Bean
public AuthenticationManager authenticationManagerBean(AuthenticationManagerBuilder builder) {
    return builder.getOrBuild();
}

然后应用程序将无法启动并给出以下异常:

No qualifying bean of type 'org.springframework.security.authentication.AuthenticationManager' available

我想我在正确设置 bean 时遇到了一些麻烦。

9

谢谢,罗布。如果不使用AuthenticationManagerBuilder,定义一个AuthenticationManagerbean 的推荐方法是什么,它相当于authenticationManagerBean在尚未被覆盖WebSecurityConfiguerAdapter时覆盖。configure(AuthenticationManagerBuilder auth)

只需提供一个 bean,而不使用构建器。