2015-02-03 6 views
7

Ho un servizio web REST completo implementato con Spring Boot 1.2.0-RELEASE che occasionalmente genera la seguente eccezione all'avvio.Condizioni di stivale Spring Boot apparenti che causano duplicati springSecurityFilterChain registration

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) 
... 

Quando dico "ogni tanto", voglio dire semplicemente riavviare il server Tomcat (versione 8.0.17) produrrà sia questa eccezione o caricherà con successo senza alcun problema.

Questa è un'applicazione Servlet 3.0 basata su Spring Boot, quindi non abbiamo un file web.xml tradizionale. Invece, inizializziamo il nostro servlet usando 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); 
    } 
} 

Abbiamo anche sfruttare il comando mvn spring-boot:run durante lo sviluppo, e questa condizione gara deve ancora comparire quando viene eseguito in questo modo. La "radice" della nostra configurazione e il metodo principale utilizzato dai Maven sono nella stessa classe:

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

} 

Ho cercato di semplificare la nostra logica di autenticazione da utilizzare un provider di autenticazione personalizzato in memoria per il test. Per quanto posso dire, questo è l'unico provider di autenticazione personalizzato sul classpath e non importiamo alcuna classe di configurazione al di fuori del pacchetto root dell'applicazione.

Purtroppo l'output di registrazione fornito da Spring e Tomcat non aiuta fornisce alcun contesto attorno all'errore, quindi provato afferrando la fonte AbstractSecurityWebApplictionInitializer da qui:

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

E modifica il metodo registerFilter(...) in un tenta di generare qualche utile output di debug aggiungendo le chiamate System.out.

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, "/*"); 
} 

In caso di errore, l'output di debug viene generato solo una volta prima dell'eccezione. Questo indica il metodo registerFilter(...) viene chiamato una sola volta e relativamente tardi nel processo di primavera carico:

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

Quando funziona, l'output di debug è simile al seguente:

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

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

Ciò suggerisce la configurazione di sicurezza sta accadendo molto prima nel processo di caricamento quando funziona contro quando fallisce.

risposta

15

Penso che sia necessario avere una sottoclasse in calcestruzzo di AbstractSecurityWebApplicationInitializer nell'applicazione. Il supporto di Spring Servlet 3.0 troverà questa implementazione WebApplicationInitializer e la chiamerà quando Tomcat avvierà la tua app. Questo fa scattare un tentativo di registrare il filtro di Spring Security. Hai anche la tua classe WebXml che si estende SpringBootServletInitializer. Anche questo è uno WebApplicationInitializer che verrà chiamato quando Tomcat avvia la tua app. Grazie al supporto per la configurazione automatica di Spring Boot, questo fa scattare anche il tentativo di registrare il filtro di Spring Security.

La classe WebXml non dichiara un ordine (non implementa l'interfaccia di Spring Ordered e non è annotata con @Order). Direi che lo stesso vale per la sottoclasse AbstractSecurityWebApplicationInitializer. Ciò significa che entrambi hanno lo stesso ordine (impostazione predefinita), quindi Spring è libera di chiamarli in qualsiasi ordine.L'applicazione funziona quando la sottoclasse AbstractSecurityWebApplicationInitializer viene avviata poiché Spring Boot è tollerante del filtro già presente. Se fallisce quando Spring Boot va prima come AbstractSecurityWebApplicationInitializer non è così tollerante.

Avendo detto tutto questo, dato che stai usando Spring Boot potresti non aver nemmeno bisogno del tuo AbstractSecurityWebApplicationInitializer quindi la soluzione più semplice è probabilmente quella di eliminarlo. Se hai bisogno di , dovresti assegnare entrambi e un ordine (annotare con @Order o implementare Ordered) in modo che WebXml venga sempre chiamato dopo la sottoclasse AbstractSecurityWebApplicationInitializer.

0

seguente documentazione avvio primavera è necessario disattivare la configurazione di sicurezza predefinita caricato da avvio a molla con l'aggiunta di annotazioni @EnableWebMvcSecurity nella configurazione app (vedi 75.2 Change the AuthenticationManager and add user accounts) e di quanto deve configurare una scheda di sicurezza web come questo:

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