明显的 Spring Boot 竞争条件导致重复的 springSecurityFilterChain 注册

2023-12-29

我有一个使用 Spring Boot 1.2.0-RELEASE 实现的 REST-full Web 服务,在启动时偶尔会抛出以下异常。

03-Feb-2015 11:42:23.697 SEVERE [localhost-startStop-1] org.apache.catalina.core.ContainerBase.addChildInternal ContainerBase.addChild: start: 
 org.apache.catalina.LifecycleException: Failed to start component [StandardEngine[Catalina].StandardHost[localhost].StandardContext[]]
        at org.apache.catalina.util.LifecycleBase.start(LifecycleBase.java:154)
        at org.apache.catalina.core.ContainerBase.addChildInternal(ContainerBase.java:725)
...
Caused by: java.lang.IllegalStateException: Duplicate Filter registration for 'springSecurityFilterChain'. Check to ensure the Filter is only configured once.
        at org.springframework.security.web.context.AbstractSecurityWebApplicationInitializer.registerFilter(AbstractSecurityWebApplicationInitializer.java:215)
        at org.springframework.security.web.context.AbstractSecurityWebApplicationInitializer.insertSpringSecurityFilterChain(AbstractSecurityWebApplicationInitializer.java:147)
...

当我说“偶尔”时,我的意思是只需重新启动 Tomcat 服务器(版本 8.0.17)就会产生此异常,或者会成功加载而不会出现问题。

这是一个基于 Spring Boot 构建的 Servlet 3.0 应用程序,因此我们没有传统的 web.xml 文件。相反,我们使用 Java 初始化 servlet。

package com.v.dw.webservice;

import org.springframework.boot.builder.SpringApplicationBuilder;
import org.springframework.boot.context.web.SpringBootServletInitializer;

public class WebXml extends SpringBootServletInitializer {

    @Override
    protected SpringApplicationBuilder configure(SpringApplicationBuilder application) {
        return application.sources(ApplicationConfig.class);
    }
}

我们还利用mvn spring-boot:run开发期间的命令,并且以这种方式运行时尚未出现这种竞争条件。我们的配置的“根”和maven使用的main方法在同一个类中:

package com.v.dw.webservice;

import javax.sql.DataSource;

import org.springframework.beans.factory.annotation.Value;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.actuate.autoconfigure.ManagementSecurityAutoConfiguration;
import org.springframework.boot.autoconfigure.EnableAutoConfiguration;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.boot.autoconfigure.jdbc.DataSourceBuilder;
import org.springframework.boot.autoconfigure.security.SecurityAutoConfiguration;
import org.springframework.boot.context.properties.ConfigurationProperties;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Primary;

@SpringBootApplication
@EnableAutoConfiguration(exclude = {ManagementSecurityAutoConfiguration.class, SecurityAutoConfiguration.class})
public class ApplicationConfig {

    public static void main(String[] args) {
        SpringApplication.run(ApplicationConfig.class, args);
    }

    @Value("${info.build.version}")
    private String apiVersion;

    @Bean
    @Primary
    @ConfigurationProperties(prefix="datasource.primary")
    public DataSource primaryDataSource() {
        return DataSourceBuilder.create().build();
    }

}

我尝试简化我们的身份验证逻辑,以使用自定义内存中身份验证提供程序进行测试。据我所知,这是类路径上唯一的自定义身份验证提供程序,并且我们不会在应用程序根包之外导入任何配置类。

不幸的是,Spring 和 Tomcat 提供的日志输出无法帮助提供有关错误的任何上下文,因此我尝试从此处获取 AbstractSecurityWebApplictionInitializer 源:

https://raw.githubusercontent.com/spring-projects/spring-security/rb3.2.5.RELEASE/web/src/main/java/org/springframework/security/web/context/AbstractSecurityWebApplicationInitializer.java https://raw.githubusercontent.com/spring-projects/spring-security/rb3.2.5.RELEASE/web/src/main/java/org/springframework/security/web/context/AbstractSecurityWebApplicationInitializer.java

我修改了registerFilter(...)方法尝试通过添加来生成一些有用的调试输出System.out calls.

private final void registerFilter(ServletContext servletContext, boolean insertBeforeOtherFilters, String filterName, Filter filter) {
    System.out.println(">>>>>> Registering filter '" + filterName + "' with: " + filter.getClass().toString());
    Dynamic registration = servletContext.addFilter(filterName, filter);
    if(registration == null) {
        System.out.println(">>>>>> Existing filter '" + filterName + "' as: " + servletContext.getFilterRegistration(filterName).getClassName());
        throw new IllegalStateException("Duplicate Filter registration for '" + filterName +"'. Check to ensure the Filter is only configured once.");
    }
    registration.setAsyncSupported(isAsyncSecuritySupported());
    EnumSet<DispatcherType> dispatcherTypes = getSecurityDispatcherTypes();
    registration.addMappingForUrlPatterns(dispatcherTypes, !insertBeforeOtherFilters, "/*");
}

当失败时,调试输出仅在异常发生之前生成一次。这表明registerFilter(...)方法仅被调用一次,并且在 Spring 加载过程中相对较晚:

>>>>>> Registering filter 'springSecurityFilterChain' with: class org.springframework.web.filter.DelegatingFilterProxy
>>>>>> Existing filter 'springSecurityFilterChain' as: org.springframework.security.web.FilterChainProxy

当它工作时,调试输出如下所示:

>>>>>> Registering filter 'springSecurityFilterChain' with: class org.springframework.web.filter.DelegatingFilterProxy

  .   ____          _            __ _ _
 /\\ / ___'_ __ _ _(_)_ __  __ _ \ \ \ \
( ( )\___ | '_ | '_| | '_ \/ _` | \ \ \ \
 \\/  ___)| |_)| | | | | || (_| |  ) ) ) )
  '  |____| .__|_| |_|_| |_\__, | / / / /
 =========|_|==============|___/=/_/_/_/
 :: Spring Boot ::        (v1.2.0.RELEASE)

这表明安全配置在加载过程中发生得更早,当它工作时而不是当它失败时。


我认为你必须有一个具体的子类AbstractSecurityWebApplicationInitializer在您的应用程序中。 Spring的Servlet 3.0支持会发现这个WebApplicationInitializer实现并在 Tomcat 启动您的应用程序时调用它。这会触发注册 Spring Security 过滤器的尝试。你也有你的WebXml扩展的类SpringBootServletInitializer。这也是一个WebApplicationInitializer当 Tomcat 启动您的应用程序时,它将被调用。由于 Spring Boot 的自动配置支持,这也会触发注册 Spring Security 过滤器的尝试。

Your WebXml类没有声明顺序(它没有实现 Spring 的Ordered接口并且它没有注释@Order)。我猜你的情况也是如此AbstractSecurityWebApplicationInitializer子类。这意味着它们具有相同的顺序(默认),因此 Spring 可以自由地以任何顺序调用它们。当您的应用程序运行时AbstractSecurityWebApplicationInitializer子类优先,因为 Spring Boot 能够容忍已经存在的过滤器。如果 Spring Boot 首先失败AbstractSecurityWebApplicationInitializer没有那么宽容。

说了这么多,当您使用 Spring Boot 时,您甚至可能不需要AbstractSecurityWebApplicationInitializer所以最简单的解决方案可能就是删除它。如果你do需要它,那么你应该同时分配它和WebXml订单(注释为@Order或实施Ordered) 以便WebXml保证总是在您之后被调用AbstractSecurityWebApplicationInitializer子类。

本文内容由网友自发贡献,版权归原作者所有,本站不承担相应法律责任。如您发现有涉嫌抄袭侵权的内容,请联系:hwhale#tublm.com(使用前将#替换为@)

明显的 Spring Boot 竞争条件导致重复的 springSecurityFilterChain 注册 的相关文章

随机推荐