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

2

10

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.

Footmark answered 26/12, 2014 at 7:33 Comment(0)
M
16

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>
Merman answered 20/2, 2015 at 18:17 Comment(0)
Z
3

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();
  }
}
Zollie answered 20/11, 2017 at 19:9 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.