Donation

If you found the contents in this blog useful, then please make a donation to keep this blog running. You can make donations via Skrill with email address shazin.sadakath@gmail.com

Wednesday, July 23, 2014

Spring Security Custom FilterChainProxy using Java Configuration

In a previous post I wrote how to custom configure FilterChainProxy using Java Bean XML configuration file. I got some feedback and of the things I was pointed out was that it could also be done using Java configuration instead of XML configuration. But at that time I couldn't do it because we were using XML configuration files for beans. But now as part of my work I needed to work with Spring using Java configuration. So I thought this is the right time to go back and write the whole custom FilterChainProxy configuration in Java configuration.

After some documentation reading, trial and error, stackoverflow I managed to get the following Java configuration to work as exactly as same as my previous post's XML configuration.


import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collection;
import java.util.LinkedHashMap;
import java.util.List;

import javax.inject.Inject;
import javax.servlet.ServletException;

import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.security.access.AccessDecisionVoter;
import org.springframework.security.access.ConfigAttribute;
import org.springframework.security.access.intercept.RunAsManager;
import org.springframework.security.access.intercept.RunAsManagerImpl;
import org.springframework.security.access.vote.AffirmativeBased;
import org.springframework.security.access.vote.RoleVoter;
import org.springframework.security.authentication.AuthenticationManager;
import org.springframework.security.authentication.AuthenticationProvider;
import org.springframework.security.authentication.ProviderManager;
import org.springframework.security.authentication.dao.DaoAuthenticationProvider;
import org.springframework.security.authentication.dao.ReflectionSaltSource;
import org.springframework.security.authentication.dao.SaltSource;
import org.springframework.security.authentication.encoding.Md5PasswordEncoder;
import org.springframework.security.authentication.encoding.PasswordEncoder;
import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity;
import org.springframework.security.core.userdetails.UserDetailsService;
import org.springframework.security.web.DefaultSecurityFilterChain;
import org.springframework.security.web.FilterChainProxy;
import org.springframework.security.web.SecurityFilterChain;
import org.springframework.security.web.access.AccessDeniedHandlerImpl;
import org.springframework.security.web.access.ExceptionTranslationFilter;
import org.springframework.security.web.access.expression.DefaultWebSecurityExpressionHandler;
import org.springframework.security.web.access.expression.ExpressionBasedFilterInvocationSecurityMetadataSource;
import org.springframework.security.web.access.expression.WebExpressionVoter;
import org.springframework.security.web.access.intercept.FilterInvocationSecurityMetadataSource;
import org.springframework.security.web.access.intercept.FilterSecurityInterceptor;
import org.springframework.security.web.authentication.LoginUrlAuthenticationEntryPoint;
import org.springframework.security.web.authentication.SimpleUrlAuthenticationFailureHandler;
import org.springframework.security.web.authentication.SimpleUrlAuthenticationSuccessHandler;
import org.springframework.security.web.authentication.UsernamePasswordAuthenticationFilter;
import org.springframework.security.web.authentication.logout.CookieClearingLogoutHandler;
import org.springframework.security.web.authentication.logout.LogoutFilter;
import org.springframework.security.web.authentication.logout.LogoutHandler;
import org.springframework.security.web.authentication.logout.SecurityContextLogoutHandler;
import org.springframework.security.web.context.HttpSessionSecurityContextRepository;
import org.springframework.security.web.context.SecurityContextPersistenceFilter;
import org.springframework.security.web.util.matcher.AntPathRequestMatcher;
import org.springframework.security.web.util.matcher.RequestMatcher;
import org.springframework.web.filter.RequestContextFilter;

import com.lkbotics.visualizer.util.CustomUsernamePasswordAuthenticationFilter;

@Configuration
@EnableWebSecurity
public class SecurityConfig {

    @Inject
    UserDetailsService userService;

    @Bean
    public AuthenticationManager authenticationManager() throws Exception {
        AuthenticationManager authenticationManager = new ProviderManager(
                Arrays.asList(authenticationProvider()));
        return authenticationManager;
    }

    @Bean
    public AuthenticationProvider authenticationProvider() throws Exception {
        DaoAuthenticationProvider authenticationProvider = new DaoAuthenticationProvider();
        authenticationProvider.setUserDetailsService(userService);
        authenticationProvider.setSaltSource(saltSource());
        authenticationProvider.setPasswordEncoder(passwordEncoder());
        authenticationProvider.afterPropertiesSet();
        return authenticationProvider;
    }

    @Bean
    public SaltSource saltSource() throws Exception {
        ReflectionSaltSource saltSource = new ReflectionSaltSource();
        saltSource.setUserPropertyToUse("salt");
        saltSource.afterPropertiesSet();
        return saltSource;
    }

    @Bean
    public PasswordEncoder passwordEncoder() {
        return new Md5PasswordEncoder();
    }

    @Bean
    public FilterChainProxy springSecurityFilterChain()
            throws ServletException, Exception {
        List<SecurityFilterChain> securityFilterChains = new ArrayList<SecurityFilterChain>();
        securityFilterChains.add(new DefaultSecurityFilterChain(
                new AntPathRequestMatcher("/login**")));
        securityFilterChains.add(new DefaultSecurityFilterChain(
                new AntPathRequestMatcher("/resources/**")));
        securityFilterChains.add(new DefaultSecurityFilterChain(
                new AntPathRequestMatcher("/**"),
                securityContextPersistenceFilter(), 
                logoutFilter(),
                usernamePasswordAuthenticationFilter(),
                exceptionTranslationFilter(),
                filterSecurityInterceptor()));
        return new FilterChainProxy(securityFilterChains);
    }

    @Bean
    public SecurityContextPersistenceFilter securityContextPersistenceFilter() {
        return new SecurityContextPersistenceFilter(
                new HttpSessionSecurityContextRepository());
    }

    @Bean
    public ExceptionTranslationFilter exceptionTranslationFilter() {
        ExceptionTranslationFilter exceptionTranslationFilter = new ExceptionTranslationFilter(
                new LoginUrlAuthenticationEntryPoint("/login"));
        AccessDeniedHandlerImpl accessDeniedHandlerImpl = new AccessDeniedHandlerImpl();
        accessDeniedHandlerImpl.setErrorPage("/exception");
        exceptionTranslationFilter
                .setAccessDeniedHandler(accessDeniedHandlerImpl);
        exceptionTranslationFilter.afterPropertiesSet();
        return exceptionTranslationFilter;
    }

    @Bean
    public UsernamePasswordAuthenticationFilter usernamePasswordAuthenticationFilter()
            throws Exception {
        UsernamePasswordAuthenticationFilter usernamePasswordAuthenticationFilter = new UsernamePasswordAuthenticationFilter();
        usernamePasswordAuthenticationFilter
                .setAuthenticationManager(authenticationManager());
        usernamePasswordAuthenticationFilter.setAllowSessionCreation(true);
        SimpleUrlAuthenticationSuccessHandler successHandler = new SimpleUrlAuthenticationSuccessHandler(
                "/");
        successHandler.setAlwaysUseDefaultTargetUrl(true);
        usernamePasswordAuthenticationFilter
                .setAuthenticationSuccessHandler(successHandler);
        usernamePasswordAuthenticationFilter
                .setAuthenticationFailureHandler(new SimpleUrlAuthenticationFailureHandler(
                        "/login?error=true"));
        usernamePasswordAuthenticationFilter.afterPropertiesSet();

        return usernamePasswordAuthenticationFilter;

    }

    @Bean
    public FilterSecurityInterceptor filterSecurityInterceptor()
            throws Exception {
        FilterSecurityInterceptor filterSecurityInterceptor = new FilterSecurityInterceptor();
        filterSecurityInterceptor
                .setAuthenticationManager(authenticationManager());
        filterSecurityInterceptor
                .setAccessDecisionManager(accessDecisionManager());
        filterSecurityInterceptor.setRunAsManager(runAsManager());
        LinkedHashMap<RequestMatcher, Collection<ConfigAttribute>> requestMap = new LinkedHashMap<RequestMatcher, Collection<ConfigAttribute>>();
        List<ConfigAttribute> configs = new ArrayList<ConfigAttribute>();
        configs.add(new org.springframework.security.access.SecurityConfig(
                "isAuthenticated()"));
        requestMap.put(new AntPathRequestMatcher("/**"), configs);
        FilterInvocationSecurityMetadataSource filterInvocationSecurityMetadataSource = new ExpressionBasedFilterInvocationSecurityMetadataSource(
                requestMap, new DefaultWebSecurityExpressionHandler());
        filterSecurityInterceptor
                .setSecurityMetadataSource(filterInvocationSecurityMetadataSource);
        filterSecurityInterceptor.afterPropertiesSet();

        return filterSecurityInterceptor;
    }

    public AffirmativeBased accessDecisionManager() throws Exception {
        List<AccessDecisionVoter> voters = new ArrayList<AccessDecisionVoter>();
        voters.add(new WebExpressionVoter());
        voters.add(new RoleVoter());
        AffirmativeBased affirmativeBased = new AffirmativeBased(voters);
        affirmativeBased.setAllowIfAllAbstainDecisions(false);
        affirmativeBased.afterPropertiesSet();

        return affirmativeBased;
    }

    @Bean
    public RunAsManager runAsManager() throws Exception {
        RunAsManagerImpl runAsManager = new RunAsManagerImpl();
        runAsManager.setKey("V_RUN_AS");
        runAsManager.afterPropertiesSet();
        return runAsManager;
    }

    @Bean
    public LogoutFilter logoutFilter() throws ServletException {
        List<LogoutHandler> handlers = new ArrayList<LogoutHandler>();
        handlers.add(new CookieClearingLogoutHandler("JSESSIONID"));
        handlers.add(new SecurityContextLogoutHandler());
        LogoutFilter logoutFilter = new LogoutFilter("/login",
                handlers.toArray(new LogoutHandler[] {}));
        logoutFilter.afterPropertiesSet();
        return logoutFilter;
    }
    
    
}

Furthermore you will need the following configuration in web.xml to support the Java configuration.

<context-param>
    <param-name>contextClass</param-name>
    <param-value>org.springframework.web.context.support.AnnotationConfigWebApplicationContext</param-value>
  </context-param>
  <context-param>
      <param-name>contextConfigLocation</param-name>
      <param-value><PACKAGE>.SecurityConfig</param-value>
  </context-param>
  <listener>
    <listener-class>org.springframework.web.context.ContextLoaderListener</listener-class>
  </listener>
  <filter>
      <filter-name>springSecurityFilterChain</filter-name>
      <filter-class>org.springframework.web.filter.DelegatingFilterProxy</filter-class>
  </filter>
  <filter-mapping>
      <filter-name>springSecurityFilterChain</filter-name>
      <url-pattern>/*</url-pattern>
  </filter-mapping>
  <servlet>

The same rules applies where DelegatingFilterProxy in web.xml must be named springSecurityFilterChain because our custom FilterChainProxy in SecurityConfig is named springSecurityFilterChain.

Trackbacks/Pings

  1. Spring Blog - http://spring.io/blog/2014/07/29/this-week-in-spring-spring-xd-edition-july-29th-2014

2 comments:

Marco said...

Hi! I'm reading your post searching a way to using my iplementation of LoginUrlAuthenticationEntryPoint in ExceptionTranslationFilter. Here is why I'm trying it: http://stackoverflow.com/questions/26543044/spring-security-sc-unauthorized-on-ajax-call

In short I've understand that I need to configure spring security defining a springsecurityFilterChain manually instead of using configure (HttpSecurity) facility in order to obtain my requirement. So I'm trying to translate this code:

@Override
protected void configure(HttpSecurity http) throws Exception {
http
.csrf().disable()
.authorizeRequests()
.antMatchers("/resources/**").permitAll()
.antMatchers("/registrazione**").permitAll()
.antMatchers("/monitoring**").hasRole("ADMIN")
.anyRequest().authenticated()
.and()
.httpBasic()
.authenticationEntryPoint(getCustomEntryPoint())
.and()
.formLogin()
.loginPage("/login").permitAll()
.failureUrl("/login?login_error=t").permitAll()
.loginProcessingUrl("/resources/j_spring_security_check").permitAll()
.usernameParameter("j_username")
.passwordParameter("j_password")
.and()
.logout()
.logoutUrl("/resources/j_spring_security_logout").permitAll()
.and()
.rememberMe().tokenValiditySeconds(1209600).key("remember-me")
.and()
.sessionManagement()
.maximumSessions(1);
}

into

@Bean(name="springSecurityFilterChain")
public FilterChainProxy springSecurityFilterChain() throws ServletException, Exception{
List securityFilterChains = new ArrayList();

securityFilterChains.add(new DefaultSecurityFilterChain(new AntPathRequestMatcher("/login**")));
securityFilterChains.add(new DefaultSecurityFilterChain(new AntPathRequestMatcher("/resources/**")));
securityFilterChains.add(new DefaultSecurityFilterChain(new AntPathRequestMatcher("/registrazione**")));


securityFilterChains.add(new DefaultSecurityFilterChain(
new AntPathRequestMatcher("/**"),
securityContextPersistenceFilter(),
logoutFilter(),
usernamePasswordAuthenticationFilter(),
exceptionTranslationFilter(),
filterSecurityInterceptor()));



return new FilterChainProxy(securityFilterChains);
}


Now login page is displayed when accessing webapp, but I don't know how to render in springSecurityFilterChain() that line of code:

.loginProcessingUrl("/resources/j_spring_security_check")


So by now I cannot login :)

Thank you for any help!

Han Li said...

Thanks for Sharing!
Very helpful!
I do not think you have to use xml+java config. with spring boot, eliminating web.xml should be quite straightforward.