SpringSecurity: Authenticating & Authorizing AJAX Requests (Server-Side Implementation)

In my previous post, we discussed a bit of theory related to authentication and authorization of AJAX requests. We also defined the desired behavior that we would like to achieve as a result of this process. In this post, I will share the server-side code that you will need to achieve that behavior.

ExceptionTranslationFilter

ExceptionTranslationFilter is one of the most important filters in Spring Security filter chain. It catches AccessDeniedException and AuthenticationException thrown by other filters in the chain, and takes actions accordingly. Our target is to intercept control from this filter at different points and tweak it’s behavior if we detect an AJAX request.

Tweaking Authentication Failure Behavior

ExceptionTranslationFilter has a protected method sendStartAuthentication which is invoked whenever an AuthenticationException is caught. This method is responsible for caching current request and redirecting user to a login page. In order to tweak authentication failure behavior, we need to extend ExceptionTranslationFilter with a new class and override sendStartAuthentication. In our implementation of sendStartAuthentication, we will detect if current request is AJAX. If true, we will create a JSON (or XML, if you like) response otherwise we will invoke implementation of super class.

If you have been configuring Spring Security using security namespace heavily, you might find overriding ExceptionTranslationFilter bit tricky, because you cannot override it when using <http> element of the namespace. It implies that you will need to instantiate and wire-up all the plethora of security beans yourself which <http> had been seamlessly doing for you.

Tweaking Authorization Failure Behavior

ExceptionTranslationFilter keeps reference of an instance of AccessDeniedHandler and invokes its handle method whenever an authorization failure occurs. If you do not provide your own implementation to ExceptionTranslationFilter, it uses AccessDeniedHandlerImpl as default implementation of the interface. Once again, we will extend AccessDeniedHandlerImpl and a new class and override its handle method. In our implementation of this method, we will detect if current request is AJAX. If so, we will send a JSON response back to the client, otherwise we will pass control to implementation of super class.

Code & Configuration

Here’s how our extension of ExceptionTranslationFilter would look like:

public class MyExceptionTranslationFilter extends ExceptionTranslationFilter {
	@Override
	protected void sendStartAuthentication(HttpServletRequest req,
			HttpServletResponse resp, FilterChain chain,
			AuthenticationException reason) throws ServletException,
			IOException {
		boolean isAjax = "XMLHttpRequest".equals(req.getHeader("X-Requested-With"));
		
		if (isAjax) {			
			String jsonObject = "{\"message\":\"Please login first.\","+
					"\"access-denied\":true,\"cause\":\"AUTHENTICATION_FAILURE\"}";
			String contentType = "application/json";
			resp.setContentType(contentType);
			PrintWriter out = resp.getWriter();
			out.print(jsonObject);
			out.flush();
			out.close();
			return;
		}
		
		super.sendStartAuthentication(req, resp, chain, reason);
	}
}

And here is how we would extend AccessDeniedHandlerImpl:

public class MyAccessDeniedHandlerImpl extends AccessDeniedHandlerImpl {
	@Override
	public void handle(HttpServletRequest req,
			HttpServletResponse resp, AccessDeniedException reason) throws ServletException,
			IOException {
		boolean isAjax = "XMLHttpRequest".equals(req.getHeader("X-Requested-With"));
		
		if (isAjax) {			
			String jsonObject = "{\"message\":\"You are not privileged to request this resource.\","+
					"\"access-denied\":true,\"cause\":\"AUTHORIZATION_FAILURE\"}";
			String contentType = "application/json";
			resp.setContentType(contentType);
			PrintWriter out = resp.getWriter();
			out.print(jsonObject);
			out.flush();
			out.close();
			return;
		}
		
		super.handle(req, resp, reason);
	}
}

Finally, our configuration would look like this:

<!-- alias should be same as filter name you have defined in web.xml for Spring Security -->
<beans:alias name="filterChainProxy" alias="springSecurityFilterChain"/>

<!-- filterChainProxy actually creates a security filter chain which had been automatically done for us by &lt;http&gt;. -->
<beans:bean id="filterChainProxy" class="org.springframework.security.web.FilterChainProxy">
    <filter-chain-map path-type="ant">
        <!-- We don't need to apply security filters on our css, javascript and image files. -->
        <filter-chain pattern="/css/**" filters="none" />
	<filter-chain pattern="/js/**" filters="none" />
	<filter-chain pattern="/images/**" filters="none" />
        <!-- For rest of the URLs, we specify a filter set. Keep in mind that order of filters is important. You can add more filters or remove any existing from the chain depending upon your requirements. -->
	<filter-chain pattern="/**" filters="securityContextFilter, logoutFilter, formLoginFilter, requestCacheFilter,
               servletApiFilter, rememberMeAuthenticationFilter, anonFilter, sessionMgmtFilter, exceptionTranslator, filterSecurityInterceptor" />
    </filter-chain-map>
</beans:bean>

<!-- This class holds UserDetails object once a user is authenticated. -->	
<beans:bean id="securityContextRepository" class="org.springframework.security.web.context.HttpSessionSecurityContextRepository" />

<!-- This filter adds UserDetails object to securityContextRepository once a user is authenticated. -->
<beans:bean id="securityContextFilter" class="org.springframework.security.web.context.SecurityContextPersistenceFilter" >
    <beans:property name="securityContextRepository" ref="securityContextRepository" />
</beans:bean>

<!-- As name suggests, this filter handles a logout event initiated by user.-->	
<beans:bean id="logoutFilter" class="org.springframework.security.web.authentication.logout.LogoutFilter">
    <!-- The page that you would like to redirect after logout is complete. -->
    <beans:constructor-arg value="/" />
    <!-- A list of LogoutHandler(s) that you would like to invoke when logout is triggered.-->
    <beans:constructor-arg>
        <beans:list>
            <!-- A reference to rememberMeServices (which implements LogoutHandler) is required to clear remember me cookie. -->
       	    <beans:ref local="rememberMeServices"/>
            <!-- SecurityContextLogoutHandler would clear UserDetails object from securityContextRepository. -->
	    <beans:bean class="org.springframework.security.web.authentication.logout.SecurityContextLogoutHandler" />
	</beans:list>
    </beans:constructor-arg>
</beans:bean>

<!-- Allows username/password based authentication. -->	
<beans:bean id="formLoginFilter" class="org.springframework.security.web.authentication.UsernamePasswordAuthenticationFilter">
    <!-- A reference to rememberMeServices is required to add a remember me cookie on login. -->
    <beans:property name="rememberMeServices" ref="rememberMeServices"/>
    <!-- Authenticates login request. -->
    <beans:property name="authenticationManager" ref="authenticationManager" />
    <!-- Reference to an implementation of AuthenticationSuccessHandler, whose onAuthenticationSuccess method will be invoked if login attempt was successful. -->
    <beans:property name="authenticationSuccessHandler">
        <beans:bean class="org.springframework.security.web.authentication.SavedRequestAwareAuthenticationSuccessHandler">
            <beans:property name="defaultTargetUrl" value="/categories" />
        </beans:bean>
    </beans:property>
    <beans:property name="sessionAuthenticationStrategy">
        <beans:bean class="org.springframework.security.web.authentication.session.SessionFixationProtectionStrategy" />
    </beans:property>
</beans:bean>
	
<beans:bean id="requestCacheFilter" class="org.springframework.security.web.savedrequest.RequestCacheAwareFilter" />
 
<beans:bean id="servletApiFilter" class="org.springframework.security.web.servletapi.SecurityContextHolderAwareRequestFilter" />
	 
<beans:bean id="anonFilter" class="org.springframework.security.web.authentication.AnonymousAuthenticationFilter" >
    <beans:property name="key" value="SomeUniqueKeyForThisApplication" />
    <beans:property name="userAttribute" value="anonymousUser,ROLE_ANONYMOUS" />
</beans:bean>
	 
<beans:bean id="sessionMgmtFilter" class="org.springframework.security.web.session.SessionManagementFilter" >
    <beans:constructor-arg ref="securityContextRepository" />
</beans:bean>

<beans:bean id="rememberMeServices" class="org.springframework.security.web.authentication.rememberme.TokenBasedRememberMeServices">
    <beans:property name="key" value="HEAR_ME_ROAR"/>
    <beans:property name="userDetailsService" ref="userManager"/>
</beans:bean>
	
<beans:bean id="rememberMeAuthenticationFilter" class="org.springframework.security.web.authentication.rememberme.RememberMeAuthenticationFilter">
    <beans:property name="rememberMeServices" ref="rememberMeServices"/>
    <beans:property name="authenticationManager" ref="authenticationManager"/>
</beans:bean>
	
<beans:bean id="rememberMeAuthenticationProvider" class="org.springframework.security.authentication.RememberMeAuthenticationProvider">
    <beans:property name="key" value="HEAR_ME_ROAR"/>
</beans:bean>
	 
<beans:bean id="filterSecurityInterceptor" class="org.springframework.security.web.access.intercept.FilterSecurityInterceptor">
    <beans:property name="securityMetadataSource">
        <filter-security-metadata-source>
            <intercept-url pattern="/" access="IS_AUTHENTICATED_ANONYMOUSLY" />	            
            <intercept-url pattern="/transactions**" access="ROLE_USER"/>
            <intercept-url pattern="/categories**" access="ROLE_USER"/>
	    <intercept-url pattern="/change-password**" access="ROLE_USER"/>
	    <intercept-url pattern="/invite-friend**" access="ROLE_USER"/>	
	    <intercept-url pattern="/ajax/json/**" access="ROLE_USER"/>			
	</filter-security-metadata-source>
    </beans:property>
    <beans:property name="authenticationManager" ref="authenticationManager" />
    <beans:property name="accessDecisionManager" ref="accessDecisionManager" />
</beans:bean>
	
<beans:bean id="accessDecisionManager" class="org.springframework.security.access.vote.AffirmativeBased">
    <beans:property name="decisionVoters">
        <beans:list>
            <beans:bean class="org.springframework.security.access.vote.RoleVoter"/>
            <beans:bean class="org.springframework.security.access.vote.AuthenticatedVoter"/>
        </beans:list>
    </beans:property>
</beans:bean>
	
<beans:bean id="webPrivilegeEvaluator" class="org.springframework.security.web.access.DefaultWebInvocationPrivilegeEvaluator">
    <beans:constructor-arg ref="filterSecurityInterceptor" />
</beans:bean>
	
<authentication-manager alias="authenticationManager">
    <authentication-provider user-service-ref="userManager">
	<password-encoder hash="md5">
            <salt-source user-property="dateCreated"/>
	</password-encoder>
    </authentication-provider>
    <authentication-provider ref="rememberMeAuthenticationProvider"/>
</authentication-manager>
	
<beans:bean id="exceptionTranslator" class="com.tostring.sample.security.MyExceptionTranslationFilter">
    <beans:property name="authenticationEntryPoint">
        <beans:bean class="org.springframework.security.web.authentication.LoginUrlAuthenticationEntryPoint">
            <beans:property name="loginFormUrl" value="/"/>
        </beans:bean>
    </beans:property>
    <beans:property name="accessDeniedHandler">
        <beans:bean class="com.tostring.sample.security.MyAccessDeniedHandler"/>
    </beans:property>
</beans:bean>

When you have completed server-side implementation, please proceed on to client-side implementation.

Published by

Umar Ashfaq

Umar Ashfaq is a full-stack web developer. His core strength is building neat UIs with JavaScript on web but he also enjoys server side Java, NodeJS and Objective C. Follow him on twitter @umarashfaq87

8 thoughts on “SpringSecurity: Authenticating & Authorizing AJAX Requests (Server-Side Implementation)”

  1. Hi Afshaq
    Thanks for the post on the filter chain handling the json request on session timout. It was very useful.

    –Lakshmi Narasimhan

  2. Thank you for the solution. It seems better comparing to extending LoginUrlAuthenticationEntryPoint class. Still I have made it bit simpler. Instead of rewriting the chain I have added a custom chain filter which comes after EXCEPTION_TRANSLATION_FILTER, extends ExceptionTranslationFilter and “super.doFilter” only for AJAX cases. This is my approach – http://gedrox.blogspot.com/2013/03/blog-post.html.

  3. Hi,

    Can you show how to configure the ExceptionTranslationFilter using Java Config.

    I tried to create but my filter is not invoked

    part of my code

    @Override
    protected void configure(HttpSecurity http) throws Exception {
    LOGGER.debug(“Configuring Spring Security HttpSecurity…”);
    http.authorizeRequests().antMatchers(“/login.jsp”).permitAll();
    http.authorizeRequests().antMatchers(“/login”).permitAll();
    http.authorizeRequests().antMatchers(“/logout.html”).permitAll(); http.authorizeRequests().anyRequest().authenticated().and().formLogin().loginProcessingUrl(“/login”).loginPage(“/login.jsp”)
    .defaultSuccessUrl(“/app.html”, true).and().logout().logoutUrl(“/logout”).logoutSuccessUrl(“/logout.html”).and().csrf().disable()
    .addFilter(exceptionTranslationFilter());

    }

    @Bean
    public ExceptionTranslationFilter exceptionTranslationFilter() {
    RestExceptionTranslationFilter exceptionTranslationFilter = new RestExceptionTranslationFilter(new RestAuthenticationEntryPoint());
    RestAccessDeniedHandler accessDeniedHandlerImpl = new RestAccessDeniedHandler();
    exceptionTranslationFilter.setAccessDeniedHandler(accessDeniedHandlerImpl);
    exceptionTranslationFilter.afterPropertiesSet();
    return exceptionTranslationFilter;
    }

Leave a Reply

Your email address will not be published. Required fields are marked *