Spring Security Custom FilterChainProxy using Java Annotation Configuration

Posted on Updated on

Spring 3

Hi Friends, Today we came with some interesting topics about Spring Security custom filterChainProxy with Java annotation configuration. As most of you know that after spring 4.0.X, we could do Spring configuration with annotation no more usages of XML configuration.

As my last project work, I wanted to use Java annotation configuration that completely relied on annotation so I started reading blogs and Spring official documentation then now I am able to make it possible using Annotation Configuration.

Here, below snippet code example I also provided XML configuration which is a mirror of Annotations base Configuration file. In this snippet code example, three login windows are required for USER, RECRUITER and ADMIN login so accordingly it is configured.

Custom Implemented Classes:-

  • LoginSuccessDispatcherHandler : – This class dispatch the page after successful authentication to target page i.e if LoginType is ROLE_USER then redirect to user dashboard or if LoginType is ROLE_RECRUITE then redirect to recruiter dashboard or  if LoginType is ROLE_ADMIN then redirect to admin dashboard.
  • UserAuthenticationProvider :- This class implemented from AbstractUserDetailsAuthenticationProvider  that validate token and return a valid User Object.

Annotation base configuration here:-


package com.itexperts.spring.config;

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

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.SecurityConfig;
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.AuthenticatedVoter;
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.ReflectionSaltSource;
import org.springframework.security.authentication.dao.SaltSource;
import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity;
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.AbstractAuthenticationProcessingFilter;
import org.springframework.security.web.authentication.AuthenticationSuccessHandler;
import org.springframework.security.web.authentication.LoginUrlAuthenticationEntryPoint;
import org.springframework.security.web.authentication.SimpleUrlAuthenticationFailureHandler;
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 com.itexperts.spring.bean.UserAuth;
import com.itexperts.spring.security.LoginSuccessDispatcherHandler;
import com.itexperts.spring.security.UserAuthenticationProvider;

@Configuration
@EnableWebSecurity
public class AppSecurityConfig  {

	@Bean
	public AuthenticationManager getAuthenticationManager() {
		AuthenticationManager authenticationManager = new ProviderManager(
				Arrays.asList(getAuthenticationProvider()));
		return authenticationManager;
	}

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

@Bean(name="springSecurityFilterChain")
public FilterChainProxy getFilterChainProxy() throws ServletException, Exception {
	List listOfFilterChains = new ArrayList();
	listOfFilterChains.add(new DefaultSecurityFilterChain(
			new AntPathRequestMatcher("/home**")));
	listOfFilterChains.add(new DefaultSecurityFilterChain(
			new AntPathRequestMatcher("/resources/**")));
	listOfFilterChains.add(new DefaultSecurityFilterChain(
			new AntPathRequestMatcher("/**"),
			securityContextPersistenceFilter(), logoutFilter(),
			usernamePasswordAuthenticationFilter(),
			exceptionTranslationFilter(), filterSecurityInterceptor()));
	return new FilterChainProxy(listOfFilterChains);
}

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

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


@Bean
public AbstractAuthenticationProcessingFilter usernamePasswordAuthenticationFilter()
		throws Exception {
	AbstractAuthenticationProcessingFilter usernamePasswordAuthenticationFilter = new UsernamePasswordAuthenticationFilter();
	usernamePasswordAuthenticationFilter
			.setAuthenticationManager(getAuthenticationManager());
	// super(new AntPathRequestMatcher("/login", "POST"));->
	//usernamePasswordAuthenticationFilter.setFilterProcessesUrl("/login");
	usernamePasswordAuthenticationFilter.setAllowSessionCreation(true);

	AuthenticationSuccessHandler successHandler = new LoginSuccessDispatcherHandler();
	usernamePasswordAuthenticationFilter
			.setAuthenticationSuccessHandler(successHandler);
	usernamePasswordAuthenticationFilter
			.setAuthenticationFailureHandler(new SimpleUrlAuthenticationFailureHandler(
					"/home?error=true"));
	usernamePasswordAuthenticationFilter.afterPropertiesSet();

	return usernamePasswordAuthenticationFilter;

}

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

 @Bean
 public FilterSecurityInterceptor filterSecurityInterceptor()
            throws Exception {
        FilterSecurityInterceptor filterSecurityInterceptor = new FilterSecurityInterceptor();
        filterSecurityInterceptor
                .setAuthenticationManager(getAuthenticationManager());
        filterSecurityInterceptor
                .setAccessDecisionManager(accessDecisionManager());
        filterSecurityInterceptor.setRunAsManager(runAsManager());
        LinkedHashMap<RequestMatcher, Collection> requestMap = new LinkedHashMap<RequestMatcher, Collection>();
      
        requestMap.put(new AntPathRequestMatcher("/user/**"), 
        		SecurityConfig.createList("hasRole('"+UserAuth.ROLE_CANDIDATE.name()+"')"));
        requestMap.put(new AntPathRequestMatcher("/recruiter/**"), 
        		SecurityConfig.createList("hasRole('"+UserAuth.ROLE_RECRUITER.name()+"')"));
        requestMap.put(new AntPathRequestMatcher("/admin/**"), 
        		SecurityConfig.createList("hasRole('"+UserAuth.ROLE_ADMIN.name()+"')"));
        FilterInvocationSecurityMetadataSource filterInvocationSecurityMetadataSource = new ExpressionBasedFilterInvocationSecurityMetadataSource(
                requestMap, new DefaultWebSecurityExpressionHandler());
        filterSecurityInterceptor
                .setSecurityMetadataSource(filterInvocationSecurityMetadataSource);
        filterSecurityInterceptor.afterPropertiesSet();

        return filterSecurityInterceptor;
    }
 
 public AffirmativeBased accessDecisionManager() throws Exception {
        List<AccessDecisionVoter<? extends Object>> voters = new ArrayList<AccessDecisionVoter<? extends Object>>();
        voters.add(new WebExpressionVoter());
        RoleVoter voter = new RoleVoter();
        voter.setRolePrefix("ROLE_");
        voters.add(voter);
        voters.add(new AuthenticatedVoter());
        AffirmativeBased affirmativeBased = new AffirmativeBased(voters);
        affirmativeBased.setAllowIfAllAbstainDecisions(false);
        affirmativeBased.afterPropertiesSet();

        return affirmativeBased;
    }

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

XML configuration here :-


<?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"
 xsi:schemaLocation="http://www.springframework.org/schema/beans
 http://www.springframework.org/schema/beans/spring-beans-3.0.xsd
 http://www.springframework.org/schema/security
 http://www.springframework.org/schema/security/spring-security-3.1.xsd">

 <alias name="filterChainProxy" alias="springSecurityFilterChain"/>
 
 <bean id="filterChainProxy" class="org.springframework.security.web.FilterChainProxy">
 
 <security:filter-chain-map path-type="ant">
 
 
 <security:filter-chain pattern="/home/**" filters="none" />
 <security:filter-chain pattern="/resources/**" filters="none" />
 
 <security:filter-chain pattern="/**" filters="concurrentSessionFilter,securityContextPersistenceFilter,logoutFilter,authenticationFilter,sessionManagementFilter,exceptionTranslationFilter,filterSecurityInterceptor" />
 </security:filter-chain-map>
 </bean>
 
 <bean id="authenticationManager" class="org.springframework.security.authentication.ProviderManager">
 <property name="providers">
 <list>
 <ref local="userAuthenticationProvider" />
 </list>
 </property>
 </bean>
 
 <bean id="userAuthenticationProvider" class="com.itexperts.spring.security.UserAuthenticationProvider">
 </bean>

 <bean id="securityContextPersistenceFilter" class="org.springframework.security.web.context.SecurityContextPersistenceFilter">
 <property name="securityContextRepository" ref="securityContextRepository" />
 </bean>
 
 <bean id="securityContextRepository" class="org.springframework.security.web.context.HttpSessionSecurityContextRepository" />

 <bean id="concurrentSessionFilter" class="org.springframework.security.web.session.ConcurrentSessionFilter">
 <property name="expiredUrl" value="/home?errorCode=24" />
 <property name="sessionRegistry" ref="sessionRegistry" />
 </bean>

 

 <bean id="sessionRegistry" class="org.springframework.security.core.session.SessionRegistryImpl" />
 
 <bean id="logoutFilter" class="org.springframework.security.web.authentication.logout.LogoutFilter">
 <constructor-arg value="/logout" />
 <!-- URL redirected to after logout -->
 <constructor-arg>
 <list>
 <bean
 class="org.springframework.security.web.authentication.logout.SecurityContextLogoutHandler">
 </bean>
 </list>
 </constructor-arg>
 </bean>

 <bean id="authenticationFilter" class="org.springframework.security.web.authentication.UsernamePasswordAuthenticationFilter">
 <property name="authenticationManager" ref="authenticationManager" />
 <!--<property name="filterProcessesUrl" value="/login" />-->
 <property name="authenticationFailureHandler" ref="authenticationFailureHandler"/>
 <property name="authenticationSuccessHandler" ref="authenticationSuccessHandler"/>
 <property name="sessionAuthenticationStrategy" ref="sas" />
 </bean>

 <bean id="exceptionTranslationFilter" class="org.springframework.security.web.access.ExceptionTranslationFilter">
 <property name="authenticationEntryPoint" ref="authenticationEntryPoint" />
 </bean>

 
 <bean id="authenticationEntryPoint" class="com.search.job.authentication.manager.AjaxAwareAuthenticationEntryPoint">
 <property name="loginFormUrl" value="/home" />
 </bean>
 
 <bean id="accessDeniedHandler" class="org.springframework.security.web.access.AccessDeniedHandlerImpl">
 <property name="errorPage" value="/exception" />
 </bean>

 <bean id="authenticationSuccessHandler" class="com.itexperts.spring.security.LoginSuccessDispatcherHandler">
 </bean>

 <bean id="sas" class="org.springframework.security.web.authentication.session.ConcurrentSessionControlStrategy">
 <constructor-arg name="sessionRegistry" ref="sessionRegistry" />
 <property name="maximumSessions" value="1" />
 </bean>
 

 <bean id="filterSecurityInterceptor"
 class="org.springframework.security.web.access.intercept.FilterSecurityInterceptor">
 <property name="authenticationManager">
 <ref bean="authenticationManager" />
 </property>
 <property name="accessDecisionManager">
 <bean class="org.springframework.security.access.vote.AffirmativeBased">
 <property name="decisionVoters">
 <list>
 <bean class="org.springframework.security.access.vote.RoleVoter">
 <property name="rolePrefix" value="ROLE_" />
 </bean>
 <bean
 class="org.springframework.security.access.vote.AuthenticatedVoter">
 </bean>

 </list>
 </property>
 </bean>
 </property>
 <property name="securityMetadataSource">
 <security:filter-security-metadata-source>
 <security:intercept-url pattern="/profile/**" access="ROLE_CANDIADTE"></security:intercept-url>
 <security:intercept-url pattern="/recruiter/**" access="ROLE_RECRUITER"></security:intercept-url>
 <security:intercept-url pattern="/admin/**" access="ROLE_ADMIN"></security:intercept-url>
 </security:filter-security-metadata-source>
 </property>
 </bean>
 
 <bean id="sessionManagementFilter" class="org.springframework.security.web.session.SessionManagementFilter">
 <constructor-arg name="securityContextRepository" ref="httpSessionSecurityContextRepository" />
 <property name="sessionAuthenticationStrategy" ref="sas" />
 </bean>
 <bean id="httpSessionSecurityContextRepository" class="org.springframework.security.web.context.HttpSessionSecurityContextRepository"/> 
</beans>


Finally, we need to register all Servlets and Filter and other in web.xml, Since we are using Annotation base configuration so that we do it following way:-


package com.itexperts.spring.dispatcher;

import javax.servlet.ServletContext;
import javax.servlet.ServletException;

import org.springframework.web.filter.DelegatingFilterProxy;
import org.springframework.web.servlet.support.AbstractAnnotationConfigDispatcherServletInitializer;

import com.itexperts.spring.config.AppConfig;
import com.itexperts.spring.config.AppSecurityConfig;
import com.itexperts.spring.config.ServletContextConfig;

public class DispatcherServletInitializer extends AbstractAnnotationConfigDispatcherServletInitializer {

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

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

	@Override
	protected String[] getServletMappings() {
		return new String[] { "/" };
	}
	
	@Override
    public void onStartup(ServletContext servletContext) 
            throws ServletException {
        servletContext
            .addFilter("springSecurityFilterChain", 
                       new DelegatingFilterProxy("springSecurityFilterChain"))
            .addMappingForUrlPatterns(null, false, "/*");

        super.onStartup(servletContext);
    }

}

LoginSuccessDispatcherHandler.Java


package com.itexperts.spring.security;

import java.io.IOException;
import java.util.Collection;

import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import javax.servlet.http.HttpSession;

import org.springframework.security.core.Authentication;
import org.springframework.security.core.GrantedAuthority;
import org.springframework.security.web.DefaultRedirectStrategy;
import org.springframework.security.web.RedirectStrategy;
import org.springframework.security.web.WebAttributes;
import org.springframework.security.web.authentication.AuthenticationSuccessHandler;

import com.itexperts.spring.bean.UserAuth;

public class LoginSuccessDispatcherHandler implements
		AuthenticationSuccessHandler {

	private RedirectStrategy redirectStrategy = new DefaultRedirectStrategy();

	@Override
	public void onAuthenticationSuccess(HttpServletRequest request,
			HttpServletResponse response, Authentication authentication)
			throws IOException {
		handle(request, response, authentication);
		clearAuthenticationAttributes(request);
	}

	protected void handle(HttpServletRequest request,
			HttpServletResponse response, Authentication authentication)
			throws IOException {
		String targetUrl = determineTargetUrl(authentication);

		if (response.isCommitted()) {
			return;
		}

		redirectStrategy.sendRedirect(request, response, targetUrl);
	}

	protected String determineTargetUrl(Authentication authentication) {

		Collection<? extends GrantedAuthority> authorities = authentication
				.getAuthorities();
		String targetURL = "";
		for (GrantedAuthority auth : authorities) {
			if (auth.getAuthority().equals(UserAuth.ROLE_CANDIDATE.name())) {
				targetURL = "/user/candidate";
				break;
			} else if (auth.getAuthority().equals(
					UserAuth.ROLE_RECRUITER.name())) {
				targetURL = "/recruiter";
				break;
			} else if (auth.getAuthority().equals(UserAuth.ROLE_ADMIN.name())) {
				targetURL = "/admin";
				break;
			} else {
				break;
			}
		}
		if ("".equals(targetURL)) {
			throw new IllegalStateException();
		}
		return targetURL;

	}

	protected void clearAuthenticationAttributes(HttpServletRequest request) {
		HttpSession session = request.getSession(false);
		if (session == null) {
			return;
		}
		session.removeAttribute(WebAttributes.AUTHENTICATION_EXCEPTION);
	}

	public void setRedirectStrategy(RedirectStrategy redirectStrategy) {
		this.redirectStrategy = redirectStrategy;
	}

	protected RedirectStrategy getRedirectStrategy() {
		return redirectStrategy;
	}

}


UserAuthenticationProvider.Java


package com.itexperts.spring.security;

import java.util.ArrayList;
import java.util.Collection;

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.security.authentication.BadCredentialsException;
import org.springframework.security.authentication.UsernamePasswordAuthenticationToken;
import org.springframework.security.authentication.dao.AbstractUserDetailsAuthenticationProvider;
import org.springframework.security.core.AuthenticationException;
import org.springframework.security.core.GrantedAuthority;
import org.springframework.security.core.authority.SimpleGrantedAuthority;
import org.springframework.security.core.userdetails.User;
import org.springframework.security.core.userdetails.UserDetails;

import com.itexperts.spring.bean.LoginModel;
import com.itexperts.spring.repo.UserRepository;

public class UserAuthenticationProvider extends AbstractUserDetailsAuthenticationProvider {

	@Autowired
	private UserRepository userRepository;
	
	@Override
	protected void additionalAuthenticationChecks(UserDetails userDetails,
			UsernamePasswordAuthenticationToken token)
			throws AuthenticationException {
	}

	@Override
	protected UserDetails retrieveUser(String str,
			UsernamePasswordAuthenticationToken token)
			throws AuthenticationException {
		LoginModel loginModel = userRepository.login(token.getPrincipal(),token.getCredentials());
		if( loginModel == null ){
			throw new BadCredentialsException("Invalid Username and Password");
		}
		Collection grantedAuthorities = new ArrayList();
		GrantedAuthority roleType = new SimpleGrantedAuthority(loginModel.getUsertype());
		grantedAuthorities.add(roleType);
		return new User(loginModel.getUsername(), loginModel.getPassword(),true, true, true, true, grantedAuthorities);
	}

}


Advertisements

Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out / Change )

Twitter picture

You are commenting using your Twitter account. Log Out / Change )

Facebook photo

You are commenting using your Facebook account. Log Out / Change )

Google+ photo

You are commenting using your Google+ account. Log Out / Change )

Connecting to %s