Visible Spring Boot race condition causing SpringSecurityFilterChain to be re-written

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.

+7
java spring spring-boot spring-security
source share
2 answers

I think you should have a specific subclass of AbstractSecurityWebApplicationInitializer in your application. Spring Servlet 3.0 Support will find this WebApplicationInitializer implementation and call it when Tomcat launches your application. This causes an attempt to register a Spring Security filter. You also have a WebXml class that extends SpringBootServletInitializer . This is also a WebApplicationInitializer that will be called when Tomcat starts your application. Thanks to Spring's automatic boot configuration support, this also triggers an attempt to register a Spring security filter.

Your WebXml class WebXml not declare order (it does not implement the Spring Ordered interface, and it is not annotated with @Order ). I would suggest the same applies to your subclass of AbstractSecurityWebApplicationInitializer . This means that they both have the same order (default), so Spring can call them in any order. Your application works when your subclass of AbstractSecurityWebApplicationInitializer goes first as Spring Boot is tolerant of an existing filter. If Spring crashes, Downloading starts first because AbstractSecurityWebApplicationInitializer not tolerant.

Having said all this, since you are using Spring Boot, you might not even need your AbstractSecurityWebApplicationInitializer , so the easiest solution is probably to remove it. If you need it, you must assign both it and WebXml order (annotate with @Order or implement Ordered ) to ensure that WebXml will always be called after your subclass of AbstractSecurityWebApplicationInitializer .

+15
source share

After the spring boot documentation, you should disable the default security configuration loaded by the spring boot by adding the @EnableWebMvcSecurity annotation to your application configuration (see 75.2 Changing AuthenticationManager and adding user accounts ), and you need to configure the network security adapter as follows:

 @Bean WebSecurityConfigurerAdapter webSecurityAdapter() { WebSecurityConfigurerAdapter adapter = new WebSecurityConfigurerAdapter() { @Override protected void configure(HttpSecurity http) throws Exception { http.... 
0
source share

All Articles