Spring Security part VI : Session Timeout handling for Ajax calls

Recently when developing the Tatami application for the Twitter-like contest, I faced an annoying issue: how to detect an user session timeout when an Ajax request is triggered from the browser ?

If you’re not familiar yet with Spring Security, you can check my previous articles on this framework.

In this article we’ll see a solution based on Spring Security filter. For those who don’t use Spring Security I’ll show another approach with servlet filter.

Please note that a demo application for this article can be found on GitHub https://github.com/doanduyhai/AjaxSessionExpiration

Edit: the implementation has been changed to simplify Ajax request detection thanks to Marty Jones suggestions.
 

I The problem

Nowadays mobile development is getting more and more important for the business. Many companies provide a mobile interface of their traditional desktop website to address a larger audience.

The corner-stone of mobile architecture are RESTfull webservices. The underlying implementation relies on Ajax calls to achieve content lazy loading.

If your resources is protected by a security framework any un-authenticated request will be rejected and the framework redirects you to a login page. The same mechanism applies if the request is Ajax-based.

However, at the XMLHttpRequest level, it is not possible to detect this redirection. According to the W3C specs, the redirection is taken care at browser level and should be transparent for the user (so transparent for the XMLHttpRequest protocol too). What happens it that the Ajax layer receives an HTTP 200 code after the redirection.

This issue has been widely debated on Stackoverflow at this thead.
Many contributors suggest to add a special attribute in the JSON response to indicates a redirect or to add a special Javascript callback to parse the response string searching for a DOM element that characterizes the login page and so on.

My opinion is that all these solutions are not satisfactory because they require many hacks and tamper with the response string/content itself.
 

II The solution

A Spring Security

1) Algorithm

Since it is clear that it’s not possible to detect an HTTP Redirect at Javascript level, the job should be done at server side.

The idea is to add a special filter in the Spring Security chain to detect a session timeout and an incoming Ajax call then return an custom HTTP error code so the Ajax callback function can detect and process properly.

Below is a pseudo-code of the filter implementation:

  • If not authenticated
    • Redirect to login page
  • If the access to the resource is denied
    • If the session has expired and the request is Ajax-based
      • Return custom HTTP error code
    • Else
      • Redirect to login page
  • Else
    • Redirect to login page
2) Implementation

We should create a custom filter class implementing the org.springframework.web.filter.GenericFilterBean interface.

public class AjaxTimeoutRedirectFilter extends GenericFilterBean
{

	private static final Logger logger = LoggerFactory.getLogger(AjaxTimeoutRedirectFilter.class);

	private ThrowableAnalyzer throwableAnalyzer = new DefaultThrowableAnalyzer();
	private AuthenticationTrustResolver authenticationTrustResolver = new AuthenticationTrustResolverImpl();

	private int customSessionExpiredErrorCode = 901;

	@Override
	public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain) throws IOException, ServletException
	{
		try
		{
			chain.doFilter(request, response);

			logger.debug("Chain processed normally");
		}
		catch (IOException ex)
		{
			throw ex;
		}
		catch (Exception ex)
		{
			Throwable[] causeChain = throwableAnalyzer.determineCauseChain(ex);
			RuntimeException ase = (AuthenticationException) throwableAnalyzer.getFirstThrowableOfType(AuthenticationException.class, causeChain);

			if (ase == null)
			{
				ase = (AccessDeniedException) throwableAnalyzer.getFirstThrowableOfType(AccessDeniedException.class, causeChain);
			}

			if (ase != null)
			{
				if (ase instanceof AuthenticationException)
				{
					throw ase;
				}
				else if (ase instanceof AccessDeniedException)
				{

					if (authenticationTrustResolver.isAnonymous(SecurityContextHolder.getContext().getAuthentication()))
					{
						logger.info("User session expired or not logged in yet");
						String ajaxHeader = ((HttpServletRequest) request).getHeader("X-Requested-With");

						if ("XMLHttpRequest".equals(ajaxHeader))
						{
							logger.info("Ajax call detected, send {} error code", this.customSessionExpiredErrorCode);
							HttpServletResponse resp = (HttpServletResponse) response;
							resp.sendError(this.customSessionExpiredErrorCode);
						}
						else
						{
							logger.info("Redirect to login page");
							throw ase;
						}
					}
					else
					{
						throw ase;
					}
				}
			}

		}
	}

	private static final class DefaultThrowableAnalyzer extends ThrowableAnalyzer
	{
		/**
		 * @see org.springframework.security.web.util.ThrowableAnalyzer#initExtractorMap()
		 */
		protected void initExtractorMap()
		{
			super.initExtractorMap();

			registerExtractor(ServletException.class, new ThrowableCauseExtractor()
			{
				public Throwable extractCause(Throwable throwable)
				{
					ThrowableAnalyzer.verifyThrowableHierarchy(throwable, ServletException.class);
					return ((ServletException) throwable).getRootCause();
				}
			});
		}

	}

	public void setCustomSessionExpiredErrorCode(int customSessionExpiredErrorCode)
	{
		this.customSessionExpiredErrorCode = customSessionExpiredErrorCode;
	}
}

This class is a copy of the org.springframework.security.web.access.ExceptionTranslationFilter class with some modifications to detect session timeout with Ajax-based request.

First we define 1 new attribute:

  • customSessionExpiredErrorCode: custom HTTP error code to return for session timeout with Ajax-based request. The default value is 901

The main job is done at lines 46 & 48. We compare the request header “X-Requested-With“, when it exists, with the value “XMLHttpRequest” to detect whether the current request is Ajax-based or not. Please note that I did the test with IE 9 only, not sure it works for older IE version.

In most cases we simply re-throw the exception except when the call is Ajax-based. In this case we force an HTTP error code and completely bypass the remaining filters in the security filter chain.

 

3) Filter configuration

The idea is to add the above custom filter in the Spring Security filter chain. The order in the filter chain is crucial. Our filter should intercept the session timeout for Ajax calls before the vanilla ExceptionTranslationFilter in order to send the custom HTTP error code.

Configuration for Spring Security namespace users:

...
<beans:bean id="ajaxTimeoutRedirectFilter" class="doan.ajaxsessionexpiration.demo.security.AjaxTimeoutRedirectFilter">
	<beans:property name="customSessionExpiredErrorCode" value="901"/>
</beans:bean>
...
 <http auto-config="true" use-expressions="true">
        <intercept-url pattern="/**" access="isAuthenticated()" />
        <custom-filter ref="ajaxTimeoutRedirectFilter" after="EXCEPTION_TRANSLATION_FILTER"/>
	...
	...
</http>

Please notice that at line 10 we define our custom filter which is placed AFTER the EXCEPTION_TRANSLATION_FILTER in the chain. Being put later in the chain means that our filter can catch security exceptions before the EXCEPTION_TRANSLATION_FILTER which is what we want.

Configuration without Spring Security namespace:

...
<bean id="ajaxTimeoutRedirectFilter" class="doan.ajaxsessionexpiration.demo.security.AjaxTimeoutRedirectFilter">
	<property name="customSessionExpiredErrorCode" value="901"/>
	<property name="restString" value="/rest"/>
	<property name="restStringPatternMode" value="PREFIX"/>
</bean>
...
<bean id="securityFilterChain" class="org.springframework.security.web.FilterChainProxy">
	<sec:filter-chain-map request-matcher="ant">
	    <sec:filter-chain pattern="/**"  filters="
	           securityContextPersistentFilter,
	           logoutFilter,
	           authenticationProcessingFilter,
	           anonymousFilter,
	           exceptionTranslationFilter,
	           ajaxTimeoutRedirectFilter,
	           filterSecurityInterceptor
	           " />
  	</sec:filter-chain-map>
</bean>

B Other security frameworks

If you do not use Spring Security, there is a generic solution though. The idea is to add a special Http filter and check for HTTP session validity. If the session is no longer valid (timeout) and if the request is Ajax-base, you send a custom HTTP error as above.

First you should add a custom filter in the web.xml file:

...
<filter>
	<filter-name>ajaxSessionExpirationFilter</filter-name>
	<filter-class>doan.ajaxsessionexpiration.demo.security.AjaxSessionExpirationFilter</filter-class>
	<init-param>
		<param-name>customSessionExpiredErrorCode</param-name>
		<param-value>901</param-value>
	</init-param>
</filter>
...
<filter-mapping>
    	<filter-name>ajaxSessionExpirationFilter</filter-name>
    	<url-pattern>/*</url-pattern>
</filter-mapping>

Please note that this filter should be the one to kick in first (the first in the filter chain).

Then the implementation:

public class AjaxSessionExpirationFilter implements Filter
{

	private int customSessionExpiredErrorCode = 901;

    	@Override
	public void init(FilterConfig arg0) throws ServletException
	{
		// Property check here
	}

    	@Override
	public void doFilter(ServletRequest request, ServletResponse response, FilterChain filerChain) throws IOException, ServletException
	{
		HttpSession currentSession = ((HttpServletRequest)request).getSession(false);
		if(currentSession == null)
		{
			String ajaxHeader = ((HttpServletRequest) request).getHeader("X-Requested-With");
			if("XMLHttpRequest".equals(ajaxHeader))
			{
				logger.info("Ajax call detected, send {} error code", this.customSessionExpiredErrorCode);
				HttpServletResponse resp = (HttpServletResponse) response;
				resp.sendError(this.customSessionExpiredErrorCode);
			}
			else
			{
				// Redirect to login page
			}
		}
		else
		{
			// Redirect to login page
		}
	}
}

The session timeout detection is done at line 21. The getSession(false) method of the HttpServletRequest interface will return a HTTP session if one exists (and has not expired yet) but will not create a new one if no session exists or if the current session has expired.

The rest of the processing is similar to the Spring Security version.
 

C Client-side implementation

On the client side we should configure the Ajax callback function so it can handle properly our custom HTTP error code. For this jQuery comes to the rescue:

<script> 
function ajaxSessionTimeout()
{
	// Handle Ajax session timeout here
}

!function( $ )
{
	$.ajaxSetup({
		statusCode: 
		{
			901: ajaxSessionTimeout
		}
	});
}(window.jQuery);
</script>

We use the jQuery.ajaxSetup() function to define a custom behavior when an HTTP 901 status code is encountered. This is a mere callback to a function to handle Ajax request timout.

And that’s all !
 

III Demo application

I’ve created a demo application to illustrate this article. I set the session timeout to 1 minute.

On the main page, we have a popover element which trigger Ajax call to fetch user name from server.

ajaxsessionexpiration-popover

After 1 minute the session expires, the Ajax error callback for Ajax session timeout is called. In this case I display a modal panel informing the user that his session has expired and prompting him to go back to login page to re-authenticate.

About these ads

About DuyHai DOAN
Cassandra Technical Evangelist. LinkedIn profile : http://fr.linkedin.com/pub/duyhai-doan/2/224/848. Follow me on Twitter: @doanduyhai for latest updates on Cassandra

45 Responses to Spring Security part VI : Session Timeout handling for Ajax calls

  1. Not Relevant says:

    I see part IV, I see part VI. Where is part V ?

  2. Marty Jones says:

    I also had this same issue and I went a slightly different direction. I extended the LoginUrlAuthenticationEntryPoint class and overrode the commence() method. In my case I return a 401 return code but you could return whatever you like.

    Here is an example:

    public class CustomLoginUrlAuthenticationEntryPoint extends LoginUrlAuthenticationEntryPoint {

    public void commence(HttpServletRequest request,HttpServletResponse response,
    AuthenticationException authException) throws IOException, ServletException {

    if (HttpRequestUtil.isAjaxRequest(request) && authException != null) {
    response.sendError(HttpServletResponse.SC_UNAUTHORIZED);
    }
    else {
    super.commence(request, response, authException);
    }
    }
    }

  3. Leonid says:

    Thank you for a nice solution.
    A small notice (a just faced it in my project): if you have session-management and invalid-session-url set, the SessionManagementFilter will process the request before it gets to your filter (i.e. say “Session ID is invalid”).

    • DuyHai DOAN says:

      Hello Leonid

      You’re right. My solution is based on a classical authentication filter chain without SessionManagementFilter. I guess that in your case, you need to move the “Ajax Timeout detection” step before the session management filter.

  4. Ajax-Jquey-Spring Security says:

    thanks a lot.

    I used the same approach but every time session time out occurs i am getting 500 as error code instead of 901.

    • DuyHai DOAN says:

      Try to do step by step debug in the AjaxTimeoutRedirectFilter class to see if your code steps into the if(“XMLHttpRequest”.equals(ajaxHeader)) block. You’re probably facing another exception that generates the defaut 500 HTTP error.

  5. smratx says:

    In my project ,I use Struts2 +Spring 3+Hibernate 3+Spring Security 3.After adding the filter you give,the filter can’t detect the authenticationexception.The logger output always display DEBUG AjaxTimeoutRedirectFilter:40 – Chain processed normally.I’m sorry for my pool english.

    • smratx says:

      the error code is 302

      • smratx says:

        How can I catch authenticationexception in struts2 action, and so your custom spring security filter chain can detect it.

      • DuyHai DOAN says:

        If you’re catching an HTTP 302 code, it means that the request is being re-directed already. It also means that the filter is intercepting AFTER the timeout detection by Spring security chain. Can you provide your Spring security filter chain config ?

      • smratx says:
  6. smratx says:

    DuyHai DOAN:Thanks a lot.My configuration is as follow:

    • smratx says:

      <http auto-config=”false” access-denied-page=”/accessDenied.jsp”>
      <session-management invalid-session-url=”/login/login.jsp” session-fixation-protection=”none”>
      <concurrency-control error-if-maximum-exceeded=”true” max-sessions=”10″/>
      </session-management>
      <intercept-url pattern=”/*.*” requires-channel=”https”/>
      <custom-filter ref=”ajaxTimeoutRedirectFilter” after=”EXCEPTION_TRANSLATION_FILTER”/>
      <form-login login-page=”/login/login.jsp”
      authentication-failure-url=”/login/login.jsp?error=true” default-target-url=”/systemInit/init.xhx”/>
      <logout logout-success-url=”/login/login.jsp” />
      </http>

  7. smratx says:

    Thanks a lot . I delete the property invalid-session-url=”/login/login.jsp” from session-management, then the filter worked fine for me.I think the session-management filter is before AjaxTimeoutRedirectFilter,but this can not explain one thing that the 302 status code is only occurred after the first ajax request(The follow-up request returned the customed status code correctly.) I want to know why,who can tell me.I am very sorry once again for my pool english.

    • DuyHai DOAN says:

      smratx

      According to the official Spring Security documentation, the <session-management> tag is meant for session timeout handling.

      Of course if you put <session-management> in your config, it will add a filter in your chain so our ajaxTimeOutFilter is made useless …

      • smratx says:

        Delete the property invalid-session-url=”/login/login.jsp” from session-management, then the filter work fine.

  8. Abdiel says:

    Hello, how can I recover the message sessionScope $ {["SPRING_SECURITY_LAST_EXCEPTION."]} Message, using th:text?

    • DuyHai DOAN says:

      Abdiel

      You can try:

      • th:text=”${#httpSession['SPRING_SECURITY_LAST_EXCEPTION']}”
      • th:text=”${#session['SPRING_SECURITY_LAST_EXCEPTION']}”
      • th:text=”${#session.getAttribute(‘SPRING_SECURITY_LAST_EXCEPTION’)}”
      • Abdiel says:

        Thanks man. I am studying the framework thymeleaf and am finding it very interesting. The code is clean, utlizando thymeleaf!

      • Abdiel says:

        I have another question. How would the behavior with thymeleaf, and am using tiles definition? Would that change the whole structure of the application.

      • DuyHai DOAN says:

        Abdiel, see my answer below

  9. DuyHai DOAN says:

    Abdiel

    One of my collegue asked me the same question earlier

    Basically he is working with plain JSP tags (as you with Apache Tiles) and he wants to migrate to ThymeLeaf without breaking every thing.

    The idea is to use both templating systems during the migration process, each templating engine acts as a ViewResolver from Spring point of view.

    With Spring MVC you can have as many ViewResolvers as you want, specifying their order and defining the page prefix.

    	<bean id="contentNegotiatingResolver" class="org.springframework.web.servlet.view.ContentNegotiatingViewResolver">
    		<property name="order" value="#{T(org.springframework.core.Ordered).HIGHEST_PRECEDENCE}" />
    		<property name="mediaTypes">
    			<map>
    				<entry key="html" value="text/html"/>
    				<entry key="pdf" value="application/pdf"/>
    				<entry key="xsl" value="application/vnd.ms-excel"/>
    				<entry key="xml" value="application/xml"/>
    				<entry key="json" value="application/json"/>
    			</map>
    		</property>
    	</bean>
    
    	<bean id="viewResolver" class="org.springframework.web.servlet.view.UrlBasedViewResolver">
      		<property name="viewClass" value="org.springframework.web.servlet.view.tiles2.TilesView"/>
      		<property name="order" value="1" /> <!-- FIRST RESOLVER -->
      		<property name="suffix" value=".jsp"/> <!-- .jsp extensions for Apache Tiles files -->
    	</bean>
    
    	<bean class="org.thymeleaf.spring3.view.ThymeleafViewResolver"> 
    		<property name="templateEngine" ref="templateEngine" /> 
    		<property name="order" value="2" /> <!-- SECOND RESOLVER -->
    		<property name="suffix" value=".html" /> <!-- .html extension for ThymeLeaf files -->
    		<property name="characterEncoding" value="UTF-8"/>
    	</bean>
    
    • Abdiel says:

      DuyHai Doan, very cool. Would be perfect integration, along with Apache Tiles and Thymeleaf. I thought I might have a conflict using two viewresolvers.

      very thanks

  10. Hannes says:

    Hi,

    when I am making the first call as ajax call and the second as form based call, the second call is still recognised as ajax call.

    Did you see this too?

    Thanks

    • DuyHai DOAN says:

      Hello Hannes

      I did not try your scenario. The simplest thing to do is to set a debug point in the Filter at:

      ...
      String ajaxHeader = ((HttpServletRequest) request).getHeader("X-Requested-With");
      ...
      

      and check to see whether in the form based call, the request header still returns “X-Requested-With”

  11. Abdi says:

    Hi,

    The validation of the request X-Requested-With not. You must set the Content-Type? Because the browser does not appear to respond to the head X-Requested-With.

    thank you

    • DuyHai DOAN says:

      Hello Abdi

      The “X-Requested-With” header is normally set automatically. Maybe it’s something specific to JQuery and not to all Ajax request.

      To be honest, I only use JQuery for AJAX in my projects. I will give a try with manual Ajax request to double-check.

      Thanks for the remarks anyway

      • LouMéou says:

        Hi everybody,
        first of all, thanks you DuyHai for this great post, it helps me a lot.

        About Abdi issue, I don’t know if we faced the same,
        but I had to apply a few changes to ajax request detection function,
        as “X-Requested-With” header attribute was not present in my case.

        Looking at request parameter, I found AJAXREQUEST parameter.
        The root seems to come from richfaces ajax calls, as you ma read here

        https://community.jboss.org/thread/16614.

        Hope it will help.
        Regards,
        Nicolas

    • DuyHai DOAN says:

      I’ve checked and indeed the “X-Requested-With” header is sent by most of Ajax Javascript library. It’s not a standard though… http://en.wikipedia.org/wiki/List_of_HTTP_header_fields#Common_non-standard_request_headers

  12. Pham says:

    Salut anh Hai,
    je suis Vietnamienne comme toi.
    maintenance je suis en train d’integrer spring security 3.0.7 dans mon projet(Spring 3.0+hibernate) j’ai fait bien login avec security . mais j’ai quelques problems de security d’appel AJAX.
    je veux faire security pour tous les URL(appel AJAX) par ex: /http://localhost:8080/myprojet/deleteUser. c-t-d: quand un Utilisateur ai a role Admin il peut appeller ce URL si non il n’ai pas le role ADMIN , il ne peux pas appeller ce URL
    est-ce que vous pouvez me donner quelques idees ou quelque lien qui concerne ce problem?

    desolle si Spam.
    Merci bcp

  13. Ambika says:

    This works absolutely fantastic, Thank you so much for posting it.

  14. Eshana says:

    Removed invalid-session-url and added expired-url. works like charm! :)

    Placement of this filter after exception translation filter is correct :)

    This will not work if we add this filter before session management filter in case we have configured invalid-session-url; as the default filter will forward the request to the url once the dofilter line in our custom filter gets executed.

  15. Matteo says:

    Great post!
    However i found a little problem in your code for my needs. The code in section 2) does not rethrow the exception (if not “ase”) in any case, so i added the line “throw ex;” at line 66

    • DuyHai DOAN says:

      Good catch ! Thanks for your remark

      • Matteo says:

        U’re welcome. Actually i refine my own correction and the right solution was to put an else clause at that line :
        else {
        throw ex;
        }

        however i’m having problem to having it compatible with Java 6 cause it seems to break the
        contract of javax.Filter interface by throwing a generic Exeption ex.
        So i’m trying to work around it as follows:
        else {
        //verify if it works.
        //Java 7 version worked with a simple “throw ex;”
        throw new IOException(ex);
        }

        cause Filter class can throw an IOException.

  16. will824 says:

    Greetings DuyHai DOAN,
    Excellent alternative for capturing Session Timeouts in Ajax. Just wanted to thank you for sharing this information and the great article :)

  17. sympa mais ce soir” il faut “vous décider mon site registre des creations

  18. Jun Wang says:

    This is actually a bit of issue if the application server sits behind reverse proxy (eg Apache). The reverse proxy doesn’t understand error code 901 and will just give a 500 error which doesn’t recognized by the client browser.

    Just wondering if it’s possible to actually use 401 instead of 901?

  19. anandchakru says:

    Thanks a lot. It really helped me save a lot of time. Wish you had given a simple example on the front-end as well.. but no issues, planning to use JQuery-ui’s modal to make the user login again. Will keep you posted on how it turns out.

  20. Fábio says:

    Ótimo post, parabéns.

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

Follow

Get every new post delivered to your Inbox.

Join 46 other followers

%d bloggers like this: