4

When I create a WebSecurityConfigurerAdapter it is getting registered twice. I've created a configuration exactly the same as the Hello Web Security Java Configuration example from the reference docs.

The configuration is working as expected, though beans and the filter chain gets registered twice as shown in the tomcat output.

00:32:01 INFO : Root WebApplicationContext: initialization started
00:32:01 INFO : Refreshing Root WebApplicationContext: startup date [Thu Oct 09 00:32:01 EST 2014]; root of context hierarchy
00:32:01 INFO : Registering annotated classes: [class com.acme.app.config.SecurityConfig]
______________
00:32:02 INFO : Creating filter chain: org.springframework.security.web.util.matcher.AnyRequestMatcher@1, [org.springframework.security.web.context.request.async.WebAsyncManagerIntegrationFilter@26eed435, org.springframework.security.web.context.SecurityContextPersistenceFilter@cd9947, org.springframework.security.web.header.HeaderWriterFilter@354e8394, org.springframework.security.web.csrf.CsrfFilter@54fb62e3, org.springframework.security.web.authentication.logout.LogoutFilter@1df9cd96, org.springframework.security.web.authentication.UsernamePasswordAuthenticationFilter@4da923c6, org.springframework.security.web.authentication.ui.DefaultLoginPageGeneratingFilter@1ee94efd, org.springframework.security.web.authentication.www.BasicAuthenticationFilter@7d4b5722, org.springframework.security.web.savedrequest.RequestCacheAwareFilter@26dde6a8, org.springframework.security.web.servletapi.SecurityContextHolderAwareRequestFilter@7c58b660, org.springframework.security.web.authentication.AnonymousAuthenticationFilter@6fd12384, org.springframework.security.web.session.SessionManagementFilter@24d4cc75, org.springframework.security.web.access.ExceptionTranslationFilter@1709e92c, org.springframework.security.web.access.intercept.FilterSecurityInterceptor@2c46f137]
00:32:02 INFO : Root WebApplicationContext: initialization completed in 565 ms
00:32:02 INFO : FrameworkServlet 'dispatcher': initialization started
00:32:02 INFO : Refreshing WebApplicationContext for namespace 'dispatcher-servlet': startup date [Thu Oct 09 00:32:02 EST 2014]; parent: Root WebApplicationContext
00:32:02 INFO : Registering annotated classes: [class com.acme.app.config.WebConfig]
______________
00:32:02 INFO : Creating filter chain: org.springframework.security.web.util.matcher.AnyRequestMatcher@1, [org.springframework.security.web.context.request.async.WebAsyncManagerIntegrationFilter@52b26dcb, org.springframework.security.web.context.SecurityContextPersistenceFilter@10f4b535, org.springframework.security.web.header.HeaderWriterFilter@561ad054, org.springframework.security.web.csrf.CsrfFilter@4b6e4b62, org.springframework.security.web.authentication.logout.LogoutFilter@1d424a71, org.springframework.security.web.authentication.UsernamePasswordAuthenticationFilter@2dad3bf4, org.springframework.security.web.authentication.ui.DefaultLoginPageGeneratingFilter@282a0f5c, org.springframework.security.web.authentication.www.BasicAuthenticationFilter@52eeebb7, org.springframework.security.web.savedrequest.RequestCacheAwareFilter@47d4a88e, org.springframework.security.web.servletapi.SecurityContextHolderAwareRequestFilter@474c5850, org.springframework.security.web.authentication.AnonymousAuthenticationFilter@6fdc40f8, org.springframework.security.web.session.SessionManagementFilter@2978de23, org.springframework.security.web.access.ExceptionTranslationFilter@5bf190dd, org.springframework.security.web.access.intercept.FilterSecurityInterceptor@22b3fc11]

AppInitializer.java

public class AppInitializer
        extends AbstractAnnotationConfigDispatcherServletInitializer {

    @Override
    protected Class<?>[] getRootConfigClasses() {
        return new Class[]{SecurityConfig.class};
    }

    @Override
    protected Class<?>[] getServletConfigClasses() {
        return new Class[]{WebConfig.class};
    }

    @Override
    protected String[] getServletMappings() {
        return new String[]{"/"};
    }

}

SecurityConfig.java

@Configuration
@EnableWebMvcSecurity
public class SecurityConfig extends WebSecurityConfigurerAdapter {

    @Autowired
    public void configureGlobal(AuthenticationManagerBuilder auth) throws Exception {
        System.out.println("______________");
        auth
                .inMemoryAuthentication()
                .withUser("user").password("password").roles("USER");
    }

}

SecurityWebApplicationInitializer.java

public class SecurityWebApplicationInitializer
        extends AbstractSecurityWebApplicationInitializer {

}

WebConfig.java

@Configuration
@EnableWebMvc
@ComponentScan(basePackages = "com.acme.app")
public class WebConfig extends WebMvcConfigurerAdapter {

    @Override
    public void addResourceHandlers(ResourceHandlerRegistry registry) {
        registry.addResourceHandler("/static/**")
                .addResourceLocations("/static/");
    }

    @Bean
    public CookieLocaleResolver getLocaleResolver() {
        CookieLocaleResolver bean = new CookieLocaleResolver();
        bean.setCookieName("clientlanguage");
        bean.setCookieMaxAge(100000);
        return bean;
    }

    @Override
    public void addInterceptors(InterceptorRegistry registry) {
        LocaleChangeInterceptor lci = new LocaleChangeInterceptor();
        lci.setParamName("lang");
        registry.addInterceptor(lci);
    }

    @Bean
    public TilesConfigurer getTilesConfigurer() {
        CustomTilesInitializer ti = new CustomTilesInitializer();
        TilesConfigurer res = new TilesConfigurer();
        res.setCompleteAutoload(true);
        res.setDefinitions("/WEB-INF/**/tiles.xml");
        return res;
    }

    @Override
    public void configureViewResolvers(ViewResolverRegistry registry) {
        registry.tiles();
    }

    @Override
    public void addViewControllers(ViewControllerRegistry registry) {
        registry.addViewController("/").setViewName("home");
        registry.addRedirectViewController("/home", "/");
        registry.addViewController("/login").setViewName("login");
        registry.addViewController("/aboutme").setViewName("aboutme");
    }

}

UPDATE

As @lkrnac has pointed out with his answer that the config is being loaded twice during the WebConfig @ComponentScan annotation. I had figured that spring would be smart enough to not load a config that it had previously loaded. I could locate the classes outside of the config namespace though that seems like a nuisance. Instead I have chosen to provide an exclude filter as in the following:

@ComponentScan(basePackages = "com.acme.app",
               excludeFilters = {
                   @Filter(type = ASSIGNABLE_TYPE,
                           value = {
                               WebConfig.class,
                               SecurityConfig.class
                           })
               })

This way I can freely still create new config classes for my web context without having to worry about them being included or not.

Community
  • 1
  • 1
Brett Ryan
  • 26,937
  • 30
  • 128
  • 163

2 Answers2

4

Your Security config is in package com.acme.app.config.SecurityConfig. This package is scanned in WebConfig.

So I suspect that context is loaded twice:

  1. In App Initializer
  2. By Component scan in WebConfig

Try to locate SecurityConfig outside of com.acme.app package

luboskrnac
  • 23,973
  • 10
  • 81
  • 92
  • Indeed you are correct, I had just also discovered the same thing. I figured spring would be smart enough to not load a config it had already loaded. – Brett Ryan Oct 08 '14 at 14:05
  • The solution hides the problem. A `@Configuration` class that extends `WebSecurityConfigurerAdapter` and uses `@EnableWebMvcSecurity` is not a root application context and hence returning it from `getRootConfigClasses()` is bad design. Root context should not contain any web related beans – that’s what’s the `getServletConfigClasses()` is specifically meant for. – Abhijit Sarkar Jun 17 '15 at 05:07
  • Hmm, interesting that Spring Security Docs are using this "bad design". Example here: http://docs.spring.io/spring-security/site/docs/current/reference/htmlsingle/#abstractsecuritywebapplicationinitializer-with-spring-mvc – luboskrnac Jun 22 '15 at 10:54
1

Anonymous user on my blog pointed out to this excluding alternative:

@ComponentScan(basePackages = "com.acme.app",
               excludeFilters = {
                  @Filter(type = FilterType.ANNOTATION, value = Configuration.class)
               })
luboskrnac
  • 23,973
  • 10
  • 81
  • 92
  • 1
    That will end up excluding all new `Configuration` classes, which you likely do not want. – Brett Ryan Jan 13 '15 at 02:49
  • I know, but can be handy as alternative. E.g. in my current app, in order to do integration testing of partial Configurations, I had configuration with component scan per package and imported them all into main one explicitly. So no global component scan. – luboskrnac Jan 13 '15 at 10:13