Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

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.

like image 764
Yagnesh Agola Avatar asked Dec 26 '14 07:12

Yagnesh Agola


People also ask

How is CSRF attack prevented with Spring Security?

To protect MVC applications, Spring adds a CSRF token to each generated view. This token must be submitted to the server on every HTTP request that modifies state (PATCH, POST, PUT and DELETE — not GET). This protects our application against CSRF attacks since an attacker can't get this token from their own page.

What is the default session timeout in Spring Security?

Default value is 30 minutes. If you are using spring boot, then as of version 1.3 it will automatically sync the value with the server.

How CSRF token is implemented in Spring?

An easier approach is to use the csrfInput tag from the Spring Security JSP tag library. If you are using Spring MVC <form:form> tag or Thymeleaf 2.1+ and are using @EnableWebSecurity , the CsrfToken is automatically included for you (using the CsrfRequestDataValueProcessor ).

What is CSRF token in Spring Security?

CSRF (Cross Site Request Forgery) is a technique in which an attacker attempts to trick you into performing an action using an existing session of a different website. Spring Security when combined with Thymeleaf templates, automatically inserts a token into all web forms as a hidden field.


2 Answers

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();
  }
}
like image 73
Brice Roncace Avatar answered Oct 11 '22 01:10

Brice Roncace


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>
like image 29
mdrg Avatar answered Oct 11 '22 01:10

mdrg