我有一个使用 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)
这表明安全配置在加载过程中发生得更早,当它工作时而不是当它失败时。