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
- If the session has expired and the request is Ajax-based
- 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.
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.
I see part IV, I see part VI. Where is part V ?
Hello dear “Not Relevant” reader đ
Part V can be found here: http://doanduyhai.wordpress.com/category/spring/security/
Indeed this article belongs to a serie dedicated to Spring Security.
You can navigate to a particular serie with the menu (Spring/Security)
Hope that it helped
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);
}
}
}
Thanks for sharing Marty!
Your solution is great! But I cannot find the HttpRequestUtil package anywhere. Instead I found an implementation for AjaxUtils here:
https://github.com/SpringSource/spring-mvc-showcase/blob/master/src/main/java/org/springframework/mvc/extensions/ajax/AjaxUtils.java
They’re comparing request header against the string “XMLHttpRequest”. I don’t know if it would work for old IE versions since Microsoft uses ActiveXObject instead.
Anyway great comment!
Sorry about that. Here is my HttpRequestUtil class:
public static boolean isAjaxRequest(HttpServletRequest request) {
return “XMLHttpRequest”.equalsIgnoreCase(request.getHeader(“X-Requested-With”));
}
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”).
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.
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.
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.
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.
the error code is 302
How can I catch authenticationexception in struts2 action, and so your custom spring security filter chain can detect it.
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 ?
DuyHai DOAN:Thanks a lot.My configuration is as follow:
<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>
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.
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 …
Delete the property invalid-session-url=â/login/login.jspâ from session-management, then the filter work fine.
Hello, how can I recover the message sessionScope $ {[“SPRING_SECURITY_LAST_EXCEPTION.”]} Message, using th:text?
Abdiel
You can try:
Thanks man. I am studying the framework thymeleaf and am finding it very interesting. The code is clean, utlizando thymeleaf!
I have another question. How would the behavior with thymeleaf, and am using tiles definition? Would that change the whole structure of the application.
Abdiel, see my answer below
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.
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
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
Hello Hannes
I did not try your scenario. The simplest thing to do is to set a debug point in the Filter at:
and check to see whether in the form based call, the request header still returns “X-Requested-With”
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
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
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
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
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
Salut Pham
Tu peux regarder ce post en dĂ©tail, il explique comment configurer les restrictions d’URL en fonction des roles: http://doanduyhai.wordpress.com/2012/02/19/spring-security-part-iv-exceptiontranslationfilter-filtersecurityinterceptor
Pour ton cas, je pense que tu devrais définit un pattern comme ceci:
J’imagine que dans l’URL http://localhost:8080/myProject/deleteUser, myProject est ton context Path. Du coup il suffit de protĂ©ger la partie /deleteUser comme configurĂ© ci-dessus.
This works absolutely fantastic, Thank you so much for posting it.
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.
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
Good catch ! Thanks for your remark
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.
Greetings DuyHai DOAN,
Excellent alternative for capturing Session Timeouts in Ajax. Just wanted to thank you for sharing this information and the great article đ
sympa mais ce soir” il faut “vous dĂ©cider mon site registre des creations
http://stackoverflow.com/questions/11242174/handle-session-expired-event-in-spring-based-web-application
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?
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.
Ătimo post, parabĂ©ns.
Pingback: How to: How to manage a redirect request after a jQuery Ajax call | SevenNet
Pingback: Solution: How to manage a redirect request after a jQuery Ajax call #dev #it #computers | Technical information for you
Pingback: Fixed How to manage a redirect request after a jQuery Ajax call #dev #it #asnwer | Good Answer