I have a REST web service implemented using Spring Boot 1.2.0-RELEASE, which sometimes raises the following exception on startup.
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) ...
When I say "occasionally", I mean that just restarting the Tomcat server (version 8.0.17) will either result in this exception, or it will boot successfully without problems.
This is a Servlet 3.0 application created on Spring Boot, so we do not have a traditional web.xml file. Instead, we initialize our servlet using Java.
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); } }
We also use the mvn spring-boot:run command during development, and this race condition should still appear at startup this way. The "root" of our configuration and the main method used by maven belong to the same class:
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(); } }
I tried to simplify our authentication logic to use an in-memory authentication provider for authentication. As far as I can tell, this is the only user authentication provider in the classpath, and we do not import any configuration classes outside the root package of the application.
Unfortunately, the log output provided by Spring and Tomcat does not help provide any error context, so I tried to grab the AbstractSecurityWebApplictionInitializer source here:
https://raw.githubusercontent.com/spring-projects/spring-security/rb3.2.5.RELEASE/web/src/main/java/org/springframework/security/web/context/AbstractSecurityWebApplicationInitializer.java
And I modified the registerFilter(...) method, trying to generate useful debug output by adding 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, "/*"); }
When it fails, debug output is generated only once before the exception. This means that the registerFilter(...) method is called only once and relatively late in the Spring boot process:
>>>>>> Registering filter 'springSecurityFilterChain' with: class org.springframework.web.filter.DelegatingFilterProxy >>>>>> Existing filter 'springSecurityFilterChain' as: org.springframework.security.web.FilterChainProxy
When it works, the debug output is as follows:
>>>>>> Registering filter 'springSecurityFilterChain' with: class org.springframework.web.filter.DelegatingFilterProxy . ____ _ __ _ _ /\\ / ___'_ __ _ _(_)_ __ __ _ \ \ \ \ ( ( )\___ | '_ | '_| | '_ \/ _` | \ \ \ \ \\/ ___)| |_)| | | | | || (_| | ) ) ) ) ' |____| .__|_| |_|_| |_\__, | / / / / =========|_|==============|___/=/_/_/_/ :: Spring Boot :: (v1.2.0.RELEASE)
This suggests that the security configuration occurs much earlier during the boot process, when it works, and in the event of a failure.