Detect Session Timeout in Ajax Request in Spring MVC
The simplest way for doing this is using a filter on URLs of your AJAX requests.
In the example below I'm just sending HTTP 500 response code with a response body indicating the session timeout, but you can easily set the response code and body to what is more suitable for your case..
package com.myapp.security.authentication;import org.springframework.web.filter.GenericFilterBean;import javax.servlet.FilterChain;import javax.servlet.ServletException;import javax.servlet.ServletRequest;import javax.servlet.ServletResponse;import javax.servlet.http.HttpServletRequest;import javax.servlet.http.HttpServletResponse;import java.io.IOException;public class ExpiredSessionFilter extends GenericFilterBean { static final String FILTER_APPLIED = "__spring_security_expired_session_filter_applied"; @Override public void doFilter(ServletRequest req, ServletResponse res, FilterChain chain) throws IOException, ServletException { HttpServletRequest request = (HttpServletRequest) req; HttpServletResponse response = (HttpServletResponse) res; if (request.getAttribute(FILTER_APPLIED) != null) { chain.doFilter(request, response); return; } request.setAttribute(FILTER_APPLIED, Boolean.TRUE); if (request.getRequestedSessionId() != null && !request.isRequestedSessionIdValid()) { response.sendError(HttpServletResponse.SC_INTERNAL_SERVER_ERROR, "SESSION_TIMED_OUT"); return; } chain.doFilter(request, response); }}
Here's an approach that I think is quite simple. It's a combination of approaches that I've observed on this site. I wrote a blog post about it:http://yoyar.com/blog/2012/06/dealing-with-the-spring-security-ajax-session-timeout-problem/
The basic idea is to use an api url prefix (i.e. /api/secured) as suggested above along with an authentication entry point. It's simple and works.
Here's the authentication entry point:
package com.yoyar.yaya.config;import org.springframework.security.core.AuthenticationException;import org.springframework.security.web.authentication.LoginUrlAuthenticationEntryPoint;import javax.servlet.ServletException;import javax.servlet.http.*;import java.io.IOException;public class AjaxAwareAuthenticationEntryPoint extends LoginUrlAuthenticationEntryPoint { public AjaxAwareAuthenticationEntryPoint(String loginUrl) { super(loginUrl); } @Override public void commence( HttpServletRequest request, HttpServletResponse response, AuthenticationException authException) throws IOException, ServletException { boolean isAjax = request.getRequestURI().startsWith("/api/secured"); if (isAjax) { response.sendError(403, "Forbidden"); } else { super.commence(request, response, authException); } }}
And here's what goes in your spring context xml:
<bean id="authenticationEntryPoint" class="com.yoyar.yaya.config.AjaxAwareAuthenticationEntryPoint"> <constructor-arg name="loginUrl" value="/login"/></bean><security:http auto-config="true" use-expressions="true" entry-point-ref="authenticationEntryPoint"> <security:intercept-url pattern="/api/secured/**" access="hasRole('ROLE_USER')"/> <security:intercept-url pattern="/login" access="permitAll"/> <security:intercept-url pattern="/logout" access="permitAll"/> <security:intercept-url pattern="/denied" access="hasRole('ROLE_USER')"/> <security:intercept-url pattern="/" access="permitAll"/> <security:form-login login-page="/login" authentication-failure-url="/loginfailed" default-target-url="/login/success"/> <security:access-denied-handler error-page="/denied"/> <security:logout invalidate-session="true" logout-success-url="/logout/success" logout-url="/logout"/></security:http>
I use the same solution by @Matt in backend. If you're using angularJs on front end, add below interceptor in angular $http to let browser actually do a redirect to login page.
var HttpInterceptorModule = angular.module('httpInterceptor', []).config(function ($httpProvider) { $httpProvider.interceptors.push('myInterceptor'); $httpProvider.defaults.headers.common["X-Requested-With"] = 'XMLHttpRequest'; }) .factory('myInterceptor', function ($q) {return { 'responseError': function(rejection) { // do something on error if(rejection.status == 403 || rejection.status == 401) window.location = "login"; return $q.reject(rejection); } };
});
Note that below line is needed only if you're using AngularJs after version 1.1.1 (angularJS removed header "X-Requested-With" from that version onward)
$httpProvider.defaults.headers.common["X-Requested-With"] = 'XMLHttpRequest';