SpringSecurity: Fail to delete JSESSIONID - spring

I need to delete the cookie JSESSIONID when the user logs out. To do that I have added the following configuration to my security config:
<http>
<form-login login-page="/login*" authentication-failure-url="/login?try_again" />
<http-basic />
<logout logout-url="/logout" delete-cookies="JSESSIONID" />
<session-management invalid-session-url="/timeout" />
<intercept-url pattern="/login*" access="IS_AUTHENTICATED_ANONYMOUSLY" />
...
</http>
But instead of being deleted, the cookie is just became duplicated:
So it keeps redirecting the browser to the "/timeout" URL.
I tried to trace what's going on using the Developer Tools in Chrome web browser, and I found out that this cookie sets up with this response header:
Set-Cookie:JSESSIONID=CFF85EA743724F23FDA0317A75CFAD44; Path=/website/; HttpOnly
And deletes with this response header:
Set-Cookie:JSESSIONID=""; Expires=Thu, 01-Jan-1970 00:00:10 GMT; Path=/website
I'm not sure, but it seems like the reason is in the "Path" field of these headers: in the first one it points to "/website/", and in the second one it points to "/website".
Is it the reason of the described trouble? If it's not the reason (or not the only reason), what is the other reason(s)? How should I fix this trouble?

You don't need to explicitly delete the JSESSIONID cookie like this. It is not managed by Spring Security as such, but by your servlet container. Spring Security will by default invalidate the http session upon logout, which in turn causes your servlet container to remove the JSESSIONID cookie.

In my case for some reason even though SecurityContextLogoutHandler calls session.invalidate() JSESSIONID wouldn't be cleared. Its value remained the same.
I tried to use delete-cookies="JSESSIONID" the same way the OP tried, and I believe I had the same problem: The path set for the cookie was the context path without a / at the end, so it still wouldn't be cleared (It was giving the order to delete a cookie that didn't exist).
I ended up writing my own ProperCookieClearLogoutHandler, which is identic to CookieClearingLogoutHandler except for the line that sets the context path for the cookie:
package com.testdomain.testpackage;
import java.util.Arrays;
import java.util.List;
import javax.servlet.http.Cookie;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import org.springframework.security.core.Authentication;
import org.springframework.security.web.authentication.logout.LogoutHandler;
import org.springframework.util.Assert;
import org.springframework.util.StringUtils;
public final class ProperCookieClearingLogoutHandler implements LogoutHandler {
private final List<String> cookiesToClear;
public ProperCookieClearingLogoutHandler(String... cookiesToClear) {
Assert.notNull(cookiesToClear, "List of cookies cannot be null");
this.cookiesToClear = Arrays.asList(cookiesToClear);
}
public void logout(HttpServletRequest request, HttpServletResponse response,
Authentication authentication) {
for (String cookieName : cookiesToClear) {
Cookie cookie = new Cookie(cookieName, null);
String cookiePath = request.getContextPath() + "/";
if (!StringUtils.hasLength(cookiePath)) {
cookiePath = "/";
}
cookie.setPath(cookiePath);
cookie.setMaxAge(0);
response.addCookie(cookie);
}
}
}
Then I set the config for the LogoutFilter on spring-security.xml this way;
<bean id="logoutFilter"
class="org.springframework.security.web.authentication.logout.LogoutFilter">
<constructor-arg name="logoutSuccessUrl" value="/views/login/login.xhtml?logout" />
<constructor-arg>
<list>
<bean id="properCookieClearingLogoutHandler"
class="com.imatia.arpad.gplenos.authorization.ProperCookieClearingLogoutHandler">
<constructor-arg name="cookiesToClear">
<list>
<value>JSESSIONID</value>
</list>
</constructor-arg>
</bean>
<bean id="securityContextLogoutHandler"
class="org.springframework.security.web.authentication.logout.SecurityContextLogoutHandler">
</bean>
</list>
</constructor-arg>
<property name="filterProcessesUrl" value="/logout" />
</bean>

This is how I invalidate sessions:
<security:logout invalidate-session="true" logout-success-url="/myapp/auth/login" logout-url="/myapp/auth/logout" />

The default CookieClearingLogoutHandler provided by spring could not clear JSESSIONID due to a difference in cookie path.
You should not change the path of cookie. This would change the cookie identity. If the cookie were set for a path like /foo and you change this to /, then the client won't associate the changed cookie with the original cookie anymore. A cookie is identified by the name and the path.
Therefore you need to implement a custom CookieClearingLogoutHandler as shown in the above solution i.e (ProperCookieClearingLogoutHandler.class) and set it to spring security as shown in below code .Instead of using .deleteCookies("JSESSIONID","USER") which adds CookieClearingLogoutHandler by default.
Spring Security Java config:
#Configuration
#EnableWebSecurity
#ComponentScan(basePackages = "com.dentist.webapp")
public class SpringSecurityConfig extends WebSecurityConfigurerAdapter {
#Autowired
private SessionRegistry sessionRegistry;
#Override
protected void configure(HttpSecurity http) throws Exception {
http.authorizeRequests().antMatchers("/resources/**", "/signup/*", "/about", "/login/*").permitAll().anyRequest()
.authenticated()
.and().formLogin()
.loginPage("/login/form")
.permitAll()
.and().logout()
.invalidateHttpSession(true)
.clearAuthentication(true)
// .deleteCookies("JSESSIONID","USER")
.addLogoutHandler(new ProperCookieClearingLogoutHandler("JSESSIONID","USER"))
.permitAll()
.and().sessionManagement()
.maximumSessions(1)
.maxSessionsPreventsLogin(true)
.expiredUrl("/accessDenied")
.sessionRegistry(sessionRegistry);
}
}

How to delete on log-out is straightforward:
you implement logout and put that code in the method:
HttpSession session = request.getSession(false);
if (session != null) {
session.invalidate();
}
Invalidated session will make the cookie invalid.
But I tried and found, that when I close the browser without logout, the JSESSIONID cookie survives and user able enter the system , when opens the browser.
To eliminate this I created a filter, that invalidates the session on login too, creating a new session, where your login will be performed from start.
#WebFilter(urlPatterns = {"/login/*"}, description = "sessionKiller", filterName="sessionKiller")
public class SessionKillerFilter implements Filter{
#Override
public void init(FilterConfig arg0) throws ServletException {}
#Override
public void doFilter(ServletRequest req, ServletResponse resp, FilterChain chain) throws IOException, ServletException {
//kill older session and create new one on explicit login
//this is to prevent user to login 2-ce
//also this is prevention of re-connect on cookie base, when browser closed and then open
HttpServletRequest request = (HttpServletRequest)req;
HttpSession session = request.getSession(false);
if(session!=null){
session.invalidate();//old session invalidated
}
request.getSession(true);//new session created
chain.doFilter(req, resp);
}
#Override
public void destroy() {}
}

Related

Exclude custom filter for some URL in Spring Boot

I have a spring boot application that uses OAuth authentication. On top of authentication, I need to authorize the user before they can access the system. I have created a custom filter that will authorize the user. I just want to run this filter only after BasicAuthenticationFilter. If BasicAuthenticationFilter does not run then my filter also should not run.
AuthorizationFilter.java
#Component
public class AuthorizationFilter extends OncePerRequestFilter {
#Override
protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain filterChain) throws ServletException, IOException {
boolean isValidUser = true;
// we get the authenticated user from the context
Authentication authentication = SecurityContextHolder.getContext().getAuthentication();
String id = (authentication == null)? "" : authentication.getName();
..
// code to get the user data from database using 'id' and set isValidUser flag
..
if(isValidUser) {
filterChain.doFilter(request, response);
}
else {
...
// handle UNAUTHORIZED
...
}
}
}
SecurityConfiguration.java
#Configuration
public class SecurityConfiguration extends WebSecurityConfigurerAdapter {
#Override
public void configure(final HttpSecurity security) throws Exception {
security.requestMatchers()
.antMatchers("/actuator/health")
.and()
.authorizeRequests()
.antMatchers("/actuator/health").permitAll()
.and()
.csrf().disable();
security.cors();
// Custom filter to validate if user is authorized and active to access the system
security.addFilterAfter(new AuthorizationFilter(), BasicAuthenticationFilter.class);
}
}
Questions:
Even if I have permitted '/actuator/health' endpoint my custom filter still runs for that endpoint. How can I exclude my filter from running on '/actuator/health'?
The perfect solution would be to run my filter only if BasicAuthenticationFilter runs. Is this possible? How?
AFAIK, the best solution for these kinds of issues is to create a new filter chain. In your case, there would be a filter chain for endpoints that need authentication and another filter chain for endpoints that are open (do not need any authentication).
Here is the sample filter chain for unprotected endpoints:
<bean id="openFilterChain" class="org.springframework.security.web.DefaultSecurityFilterChain">
<description>Pass the list of filters that you want to invoke for the given request matcher.</description>
<constructor-arg index="0" ref="infoRequestMatcher"/>
<constructor-arg index="1">
<list>
<ref bean="exceptionTranslationFilter"/>
</list>
</constructor-arg>
</bean>
<bean id="infoRequestMatcher" class="org.springframework.security.web.util.matcher.AntPathRequestMatcher">
<description>
Pass the request matcher that matches all you unprotected endpoints.
</description>
<constructor-arg index="0" value="/actuator/health"/>
</bean>
<bean id="springSecurityFilterChain" class="org.springframework.security.web.FilterChainProxy">
<description>
Register your custom filter chain here.
</description>
<constructor-arg>
<list>
<ref bean="openFilterChain"/>
<ref bean="otherSecurityFilterChain"/>
</list>
</constructor-arg>
</bean>

Session timeout leads to Access Denied in Spring MVC when CSRF integration with Spring Security

I have Integrated CSRF token with Spring Security in my Spring MVC Project. Everything work properly with CSRF token, token will be send from client side to server side.
I have changed my logout process to make it POST method to send CSRF token and its works fine.
I have face problem when session timeout is occurred, it needs to be redirected to spring default logout URL but it gives me Access Denied on that URL.
How to override this behavior.
I have include below line in Security config file
<http>
//Other config parameters
<csrf/>
</http>
Please let me know if anyone needs more information.
The question is a bit old, but answers are always useful.
First, this is a known issue with session-backed CSRF tokens, as described in the docs: CSRF Caveats - Timeouts.
To solve it, use some Javascript to detect imminent timeouts, use a session-independent CSRF token repository or create a custom AccessDeniedHandler route. I chose the latter:
Config XML:
<http>
<!-- ... -->
<access-denied-handler ref="myAccessDeniedHandler"/>
</http>
<bean id="myAccessDeniedHandler" class="package.MyAccessDeniedHandler">
<!-- <constructor-arg ref="myInvalidSessionStrategy" /> -->
</bean>
MyAccessDeniedHandler:
public class MyAccessDeniedHandler implements AccessDeniedHandler {
/* ... */
#Override
public void handle(HttpServletRequest request, HttpServletResponse response, AccessDeniedException exception)
throws IOException, ServletException {
if (exception instanceof MissingCsrfTokenException) {
/* Handle as a session timeout (redirect, etc).
Even better if you inject the InvalidSessionStrategy
used by your SessionManagementFilter, like this:
invalidSessionStrategy.onInvalidSessionDetected(request, response);
*/
} else {
/* Redirect to a error page, send HTTP 403, etc. */
}
}
}
Alternatively, you can define the custom handler as a DelegatingAccessDeniedHandler:
<bean id="myAccessDeniedHandler" class="org.springframework.security.web.access.DelegatingAccessDeniedHandler">
<constructor-arg name="handlers">
<map>
<entry key="org.springframework.security.web.csrf.MissingCsrfTokenException">
<bean class="org.springframework.security.web.session.InvalidSessionAccessDeniedHandler">
<constructor-arg name="invalidSessionStrategy" ref="myInvalidSessionStrategy" />
</bean>
</entry>
</map>
</constructor-arg>
<constructor-arg name="defaultHandler">
<bean class="org.springframework.security.web.access.AccessDeniedHandlerImpl">
<property name="errorPage" value="/my_error_page"/>
</bean>
</constructor-arg>
</bean>
The answer provided by mdrg is right on, and I also implemented a custom AccessDeniedHandler which I submit for your consideration:
import java.io.IOException;
import javax.servlet.ServletException;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import javax.servlet.http.HttpSession;
import org.springframework.security.access.AccessDeniedException;
import org.springframework.security.web.access.AccessDeniedHandlerImpl;
import org.springframework.security.web.csrf.MissingCsrfTokenException;
import org.springframework.security.web.savedrequest.HttpSessionRequestCache;
import org.springframework.security.web.savedrequest.RequestCache;
/**
* Intended to fix the CSRF Timeout Caveat
* (https://docs.spring.io/spring-security/site/docs/current/reference/htmlsingle/#csrf-timeouts).
* When the session expires and a request requiring CSRF is received (POST), the
* missing token exception is handled by caching the current request and
* redirecting the user to the login page after which their original request will
* complete. The intended result is that no loss of data due to the timeout will
* occur.
*/
public class MissingCsrfTokenAccessDeniedHandler extends AccessDeniedHandlerImpl {
private RequestCache requestCache = new HttpSessionRequestCache();
private String loginPage = "/login";
#Override
public void handle(HttpServletRequest req, HttpServletResponse res, AccessDeniedException exception) throws IOException, ServletException {
if (exception instanceof MissingCsrfTokenException && isSessionInvalid(req)) {
requestCache.saveRequest(req, res);
res.sendRedirect(req.getContextPath() + loginPage);
}
super.handle(req, res, exception);
}
private boolean isSessionInvalid(HttpServletRequest req) {
try {
HttpSession session = req.getSession(false);
return session == null || !req.isRequestedSessionIdValid();
}
catch (IllegalStateException ex) {
return true;
}
}
public void setRequestCache(RequestCache requestCache) {
this.requestCache = requestCache;
}
public void setLoginPage(String loginPage) {
this.loginPage = loginPage;
}
}
Wired up via java config:
#EnableWebSecurity
public class SecurityConfig extends WebSecurityConfigurerAdapter {
#Override
protected void configure(HttpSecurity http) throws Exception {
...
http.exceptionHandling().accessDeniedHandler(getAccessDeniedHandler());
...
}
public AccessDeniedHandler getAccessDeniedHandler() {
return new MissingCsrfTokenAccessDeniedHandler();
}
}

Integrating Token based security into existing Spring Security web application

I am designing a RESTful web services that needs to be accessed by user after proper authentication. I have already developed Security for my application using Spring Security 3.0. Now I want to integrate TokenBasedAuthentication. But I stuck here for how do i do this.
My ApplicationContextSecurity.xml:
<global-method-security pre-post-annotations="enabled">
</global-method-security>
<beans:bean id="myAccessDecisionManager"
class="com.app.security.MyAccessDecisionManager">
</beans:bean>
<http auto-config="true" once-per-request="true"
access-decision-manager-ref="myAccessDecisionManager"
access-denied-page="/jsp/errorPage.jsp">
<intercept-url pattern="/*.app" access="ROLE_ANONYMOUS" />
<form-login login-page="/login.app"
login-processing-url="/j_spring_security_check" default-target-url="/login/checking.app"
authentication-failure-url="/login.app?login_error=1" />
<logout logout-url="/j_spring_security_logout"
logout-success-url="/login.app" invalidate-session="true" />
<session-management invalid-session-url="/login.app"
session-fixation-protection="newSession">
<concurrency-control max-sessions="100"
error-if-maximum-exceeded="false" />
</session-management>
</http>
<authentication-manager alias="authenticationManager">
<authentication-provider ref="customAuthenticationProvider"></authentication-provider>
</authentication-manager>
<beans:bean id="customAuthenticationProvider"
class="com.app.security.CustomAuthenticationProvider">
</beans:bean>
My CustomAuthenticationProvider :
public class CustomAuthenticationProvider implements AuthenticationProvider {
#Autowired
private ILoginService loginService;
protected final transient Log log = LogFactory.getLog(getClass());
public Authentication authenticate(Authentication authentication)
throws AuthenticationException {
UsernamePasswordAuthenticationToken usernamePassswordAuthenticationToken = new UsernamePasswordAuthenticationToken(
authentication.getPrincipal(), authentication.getCredentials());
// Doing authentication process here and returning authentication token
return usernamePassswordAuthenticationToken;
}
public boolean supports(Class<? extends Object> authentication) {
return authentication.equals(UsernamePasswordAuthenticationToken.class);
}
}
My requirement is,
When user want to access rest web service at first time he should provide userName/password to the server from header.
Server will accept the request, check the authentication and generate token for future requests for specific period.
Also I need client side code for how to access secured web services.
Thanks.
When user want to access rest web service at first time he should
provide userName/password to the server from header.
Server will accept the request, check the authentication and generate
token for future requests for specific period
You can do this either using HTTP headers or a normal HTTP POST request mapped to a Spring MVC controller (this is how we do it in our apps):
#Controller
public class AuthenticationController {
#Autowired
#Qualifier("authenticationManager")
AuthenticationManager authenticationManager;
#Autowired
SecurityContextRepository securityContextRepository;
#RequestMapping(method = RequestMethod.POST, value = "/authenticate")
public #ResponseBody String authenticate(#RequestParam final String username, #RequestParam final String password, final HttpServletRequest request, final HttpServletResponse response) {
final UsernamePasswordAuthenticationToken authenticationRequest = new UsernamePasswordAuthenticationToken(username, password);
final Authentication authenticationResult = this.authenticationManager.authenticate(authenticationRequest);
final String token = <some randomly generated secure token>;
final Authentication authentication = new MyAuthenticationToken(authenticationResult, token);
SecurityContextHolder.getContext().setAuthentication(authentication);
this.securityContextRepository.saveContext(SecurityContextHolder.getContext(), request, response);
return token;
}
}
Once this is done, the client should send the token in an HTTP header with every subsequent request.
Also I need client side code for how to access secured web services
Not sure what exactly you are looking for here. If your client is a JavaScript library running in a web browser, setting the authentication token as an HTTP header with every request should be straightforward. If your client is a device, the device could store the token in memory and include it as an HTTP header with every request using whatever HTTP client library you are using to invoke the services.

remember-me and authentication-success-handler

i have strange issue of for login sucess and redirect to page.
below is my spring security configuration.
<http auto-config="true" use-expressions="true">
<intercept-url pattern="/login.hst**" access="anonymous or authenticated" />
<intercept-url pattern="/**/*.hst" access="authenticated" />
<form-login login-page="/login.hst"
authentication-failure-url="/login.hst?error=true"
authentication-success-handler-ref="loginSucessHandler" />
<logout invalidate-session="true" logout-success-url="/home.hst"
logout-url="/logout.hst" />
<remember-me key="jbcpHaverERP" authentication-success-handler-ref="loginSucessHandler"/>
<session-management>
<concurrency-control max-sessions="1" />
</session-management>
</http>
LoginSuessHandler class:
#Service
public class LoginSucessHandler extends
SavedRequestAwareAuthenticationSuccessHandler {
#Override
public void onAuthenticationSuccess(HttpServletRequest request,
HttpServletResponse response, Authentication authentication)
throws ServletException, IOException {
...
super.setUseReferer(true);
super.onAuthenticationSuccess(request, response, authentication);
}
}
now problem of redirect to requested page on success. if i directly refer to any secure url spring redirects me to login page and on successful login to original requested link.
but this is not working in case if user had earlier selected remember-me and then closing browser and now requesting direct URL, he is being properly authenticated but instead of redirecting him to requested page spring redirects to /. i have checked log and some spring source code and found it is not able to determine target url.
i have tried to set refer but referer value is null. but one strange thing i have noticed that in spring security configuration if i remove authentication-success-handler from remember-me configuration then it works.
<remember-me key="jbcpHaverERP" authentication-success-handler-ref="loginSucessHandler"/>
not able to figure out issue. is authentication-success-handler implementation requied to be different for form login and remember-me?
Remember-me differs from form-login in that authentication occurs during the actual request the user makes. For form-login, the user must first be redirected to the login page, submit the login form and after that they are redirected to the original target (which is usually cached in the session). So form-login requires a redirect, whereas remember-me doesn't. With a remember-me request, the user can be authenticated, and the request allowed to proceed without any intervention.
The primary purpose of an AuthenticationSuccessHandler is to control the navigation flow after authentication, so you wouldn't normally use one with remember-me at all. Using SavedRequestAwareAuthenticationSuccessHandler isn't a good idea, as there won't be a saved request available. If there is no saved request, then by default it will perform a redirect to "/" as you have observed.
If all you want is to add some functionality during a remember-me login, then you can implement the AuthenticationSuccessHandler interface directly without performing a redirect or a forward. As I explained above, you can't use the same implementation for form-login, since the current request is the submission of the login form (usually to the URL j_spring_security_check), and not a request to a URL within your application. So you need a redirect for form-login.
You would rather use ApplicationListener and look for the event InteractiveAuthenticationSuccessEvent.
InteractiveAuthenticationSuccessEvent has a property generatedBy which will be the filter, ie UsernamePasswordAuthenticationFilter (form logins) and RememberMeAuthenticationFilter (remeber me logins)
#Component
class AuthenticationApplicationListener implements ApplicationListener<InteractiveAuthenticationSuccessEvent> {
#Override
void onApplicationEvent(InteractiveAuthenticationSuccessEvent event) {
//do something
}
}
using a custom implementation of AuthenticationSuccessHandler on rememberMe will cause problems. Take a look at the flow in RememberMeAuthenticationFilter. if the successHandler is used, the filter chain is bypassed
Using an AuthenticationSuccessHandler does not work. As stated in another answer, the spring security filter chain will be bypassed!
What works, is to use an ApplicationListener - as another answer also proposes. But to find out, if your user is authenticated by remember me, the idea to use InteractiveAuthenticationSuccessEvent.getGeneratedBy() is not working: getGeneratedBy returns Class<T>, that means a generic. Therefore at runtime you cannot find out, if T is a RememberMeAuthenticationFilter.
What worked fine for me: Use InteractiveAuthenticationSuccessEvent.getAuthentication().
Here an example (by the way: #EventListener is used since Spring Security 4.2 - if you use an earlier version, do the following via implementing ApplicationListener<InteractiveAuthenticationSuccessEvent>):
#Component
public class AuthenticationApplicationListener {
#EventListener
public void handleInteractiveAuthenticationSuccess(InteractiveAuthenticationSuccessEvent event) {
if (RememberMeAuthenticationToken.class.isAssignableFrom(event.getAuthentication().getClass())) {
.... do some stuff
}
}
}
You should implement different authentication-success-handler for login form and for remember-me.
If you want to perform redirect in remeber-me handler you can use SimpleUrlAuthenticationSuccessHandler and set DefaultTargetUrl.
public class RememberMeAuthenticationSuccessHandler extends SimpleUrlAuthenticationSuccessHandler {
#Override
public void onAuthenticationSuccess(HttpServletRequest request, HttpServletResponse response,
Authentication authentication) throws IOException, ServletException {
// ...
super.setAlwaysUseDefaultTargetUrl(true);
super.setDefaultTargetUrl(request.getRequestURL().toString());
super.onAuthenticationSuccess(request, response, authentication);
}

Detect Session Timeout in Ajax Request in Spring MVC

I can't see seem to find a good example/answer on how to send back some data from an ajax request when a session has timed out. It sends back the login page HTML and I want to either send json or a status code I can intercept.
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';
Seeing as all of the present answers are a few years old now, I'll share my solution which I currently have working in a Spring Boot REST application:
#Configuration
#EnableWebSecurity
public class UISecurityConfig extends WebSecurityConfigurerAdapter {
#Override
protected void configure(HttpSecurity http) throws Exception {
...
http.exceptionHandling.authenticationEntryPoint(authenticationEntryPoint());
...
}
private AuthenticationEntryPoint authenticationEntryPoint() {
// As a REST service there is no 'authentication entry point' like MVC which can redirect to a login page
// Instead just reply with 401 - Unauthorized
return (request, response, authException) -> response.sendError(HttpServletResponse.SC_UNAUTHORIZED, authException.getMessage());
}
}
The basic premise here is that I override the authentication entry point which by default was issuing a redirect to my non-existent login page. It now responds by sending a 401. Spring also implicitly creates an standard error response JSON object that it returns as well.

Resources