20
votes

What is the recommended way to add Spring Security to a web application that is using Spring's new WebApplicationInitializer interface instead of the web.xml file? I'm looking for the equivalent of:

<filter>
    <filter-name>springSecurityFilterChain</filter-name>
    <filter-class>org.springframework.web.filter.DelegatingFilterProxy</filter-class>
</filter>

UPDATE
The provided answers are reasonable but they both assume that I've got a servletContext instance. I've looked through the hierarchy of WebApplicationInitializers and I don't see any access to the servlet context unless I choose to override one of Spring's initializer methods. AbstractDispatcherServletInitializer.registerServletFilter seems like the sensible choice but it doesn't default to URL pattern mapping and I'd hate to change filter registration for everything if there is a better way.

7
When you create your class that implements WebApplicationInitializer you will override "void onStartup(ServletContext servletContext)" with what was previously in your web.xml. The ServletContext will be passed to your WebApplicationInitializer implementation.ebelisle

7 Answers

27
votes

This is the way that I have done it:

container.addFilter("springSecurityFilterChain", new DelegatingFilterProxy("springSecurityFilterChain"))
                    .addMappingForUrlPatterns(null, false, "/*");

container is an instance of ServletContext

16
votes

The Spring Security Reference answers this question and the solution depends on whether or not you are using Spring Security in conjunction with Spring or Spring MVC.

Using Spring Security without Spring or Spring MVC

If you are not using Spring Security with Spring or Spring MVC (i.e. you do not have an existing WebApplicationInitializer) then you need to provide the following additional class:

import org.springframework.security.web.context.*;

public class SecurityWebApplicationInitializer
    extends AbstractSecurityWebApplicationInitializer {

    public SecurityWebApplicationInitializer() {
        super(SecurityConfig.class);
    }
}

Where SecurityConfig is your Spring Security Java configuration class.

Using Spring Security with Spring or Spring MVC

If you are using Spring Security with Spring or Spring MVC (i.e. you have an existing WebApplicationInitializer) then firstly you need to provide the following additional class:

import org.springframework.security.web.context.*;

public class SecurityWebApplicationInitializer
    extends AbstractSecurityWebApplicationInitializer {
}

Then you need to ensure that your Spring Security Java configuration class, SecurityConfig in this example, is declared in your existing Spring or Spring MVC WebApplicationInitializer. For example:

import org.springframework.web.servlet.support.*;

public class MvcWebApplicationInitializer
    extends AbstractAnnotationConfigDispatcherServletInitializer {

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

    // ... other overrides ...
}
8
votes
Dynamic securityFilter = servletContext.addFilter(AbstractSecurityWebApplicationInitializer.DEFAULT_FILTER_NAME, DelegatingFilterProxy.class);
securityFilter.addMappingForUrlPatterns(EnumSet.allOf(DispatcherType.class), false, "/*");

EnumSet.allOf(DispatcherType.class) to be sure that you add a mapping not only for default DispatcherType.REQUEST, but for DispatcherType.FORWARD, etc...

3
votes

After a bit of work, I've discovered that it's actually quite simple:

public class Initialiser extends AbstractAnnotationConfigDispatcherServletInitializer implements WebApplicationInitializer {

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

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

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

    @Override
    protected Filter[] getServletFilters() {
        return new Filter[] { new DelegatingFilterProxy("springSecurityFilterChain") };
    }
}

The most important thing, though, is that you must have a root context (e.g. RootConfig in this case), and that must contain a reference to all the spring security information.

Thus, my RootConfig class:

@ImportResource("classpath:spring/securityContext.xml")
@ComponentScan({ "com.example.authentication", "com.example.config" })
@Configuration
public class RootConfig {

    @Bean
    public DatabaseService databaseService() {
        return new DefaultDatabaseService();
    }

    @Bean
    public ExceptionMappingAuthenticationFailureHandler authExceptionMapping() {
        final ExceptionMappingAuthenticationFailureHandler emafh = new ExceptionMappingAuthenticationFailureHandler();
        emafh.setDefaultFailureUrl("/loginFailed");
        final Map<String, String> mappings = new HashMap<>();
        mappings.put(CredentialsExpiredException.class.getCanonicalName(), "/change_password");
        emafh.setExceptionMappings(mappings);
        return emafh;
    }
}

And spring/securityContext.xml:

<?xml version="1.0" encoding="UTF-8"?>
<beans  xmlns="http://www.springframework.org/schema/beans"
        xmlns:security="http://www.springframework.org/schema/security"
        xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
        xmlns:context="http://www.springframework.org/schema/context"
        xsi:noNamespaceSchemaLocation="http://www.springframework.org/schema/beans/spring-beans-3.0.xsd"
        xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans-3.2.xsd
            http://www.springframework.org/schema/security http://www.springframework.org/schema/security/spring-security-3.1.xsd
            http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context-3.2.xsd">

    <security:http security="none" pattern="/favicon.ico"/>

    <!-- Secured pages -->
    <security:http use-expressions="true">
        <security:intercept-url pattern="/login" access="permitAll" />
        <security:intercept-url pattern="/**" access="isAuthenticated()" />
        <security:form-login default-target-url="/index" login-processing-url="/login_form" login-page="/login" authentication-failure-handler-ref="authExceptionMapping" />
    </security:http>

    <security:authentication-manager>
        <security:authentication-provider ref="customAuthProvider" />
    </security:authentication-manager>
</beans>

I could not get it to work if I merged the RootConfig and WebAppConfig classes into just WebAppConfig and had the following:

@Override
protected Class< ? >[] getRootConfigClasses() {
    return null;
}

@Override
protected Class< ? >[] getServletConfigClasses() {
    return new Class[] { WebAppConfig.class };
}
2
votes
public class SIServerSecurityWebApplicationInitializer extends AbstractSecurityWebApplicationInitializer {

    @Override
    protected void beforeSpringSecurityFilterChain(ServletContext servletContext) {
        Dynamic registration = servletContext.addFilter("TenantServletFilter", TenantServletFilter.class);
        EnumSet<DispatcherType> dispatcherTypes = getSecurityDispatcherTypes();
        registration.addMappingForUrlPatterns(EnumSet.of(DispatcherType.REQUEST), false, "/*");
    }

}

This scenario is for executing a filter before executing other filters. If you want to execute a filter after other filers pass true in registration.addMappingForUrlPatterns(EnumSet.of(DispatcherType.REQUEST), true, "/*");. Also check the DispatcherType ASYNC, FORWARD etc.

0
votes

Faced with the same problem. Merge RootConfig and WebAppConfig - not best way - because this i did not try this solution. Tried all other solutions - everty time got "org.apache.catalina.core.StandardContext.startInternal Error filterStart". After some work, got something like this:

    FilterRegistration.Dynamic enc= servletContext.addFilter("encodingFilter",
            new CharacterEncodingFilter());
    encodingFilter .setInitParameter("encoding", "UTF-8");
    encodingFilter .setInitParameter("forceEncoding", "true");
    encodingFilter .addMappingForUrlPatterns(null, true, "/*");

But is not working with DelegatingFilterProxy(). Continue finding for best common solution for all filters.

UPDATE: I did it.

So, the main issue is: if you want add filters using java config, especially if you want to add security filter, such as DelegatingFilterProxy, then you have to create WebAppSecurityConfig:

@Configuration
@EnableWebSecurity
@ImportResource("classpath:security.xml")
public class WebAppSecurityConfig extends WebSecurityConfigurerAdapter {
}

In this case i create WebAppSecurityConfig and make import resource ("security.xml"). This let me to do that in Initializer class:

servletContext.addFilter("securityFilter", new DelegatingFilterProxy("springSecurityFilterChain"))
            .addMappingForUrlPatterns(null, false, "/*");
0
votes

This is related to those interested in Spring Boot with security: You don't need anything, Spring Boot picks up the @components and solves the other issues