14

I"m using Spring MVC/Security 3.X. The issue is that I'm getting 403 at the login page whenever the session timeout, where underneath "InvalidCsrfTokenException" is being thrown by Spring framework :

    threw exception [org.springframework.security.web.csrf.InvalidCsrfTokenException: Invalid CSRF Token '7b4aefe9-6685-4c70-adf1-0d633680523a' was found on the request parameter '_csrf' or header 'X-CSRF-TOKEN'.] with root cause
org.springframework.security.web.csrf.InvalidCsrfTokenException: Invalid CSRF Token '7b4aefe9-6685-4c70-adf1-0d633680523a' was found on the request parameter '_csrf' or header 'X-CSRF-TOKEN'.
    at org.springframework.security.web.csrf.CsrfFilter.doFilterInternal(CsrfFilter.java:100)
    at org.springframework.web.filter.OncePerRequestFilter.doFilter(OncePerRequestFilter.java:107)
    at org.springframework.security.web.FilterChainProxy$VirtualFilterChain.doFilter(FilterChainProxy.java:342)
    at org.springframework.security.web.header.HeaderWriterFilter.doFilterInternal(HeaderWriterFilter.java:57)
    at org.springframework.web.filter.OncePerRequestFilter.doFilter(OncePerRequestFilter.java:107)
    at org.springframework.security.web.FilterChainProxy$VirtualFilterChain.doFilter(FilterChainProxy.java:342)
    at org.springframework.security.web.context.SecurityContextPersistenceFilter.doFilter(SecurityContextPersistenceFilter.java:87)
    at org.springframework.security.web.FilterChainProxy$VirtualFilterChain.doFilter(FilterChainProxy.java:342)
    at org.springframework.security.web.context.request.async.WebAsyncManagerIntegrationFilter.doFilterInternal(WebAsyncManagerIntegrationFilter.java:50)
    at org.springframework.web.filter.OncePerRequestFilter.doFilter(OncePerRequestFilter.java:107)
    at org.springframework.security.web.FilterChainProxy$VirtualFilterChain.doFilter(FilterChainProxy.java:342)
    at org.springframework.security.web.FilterChainProxy.doFilterInternal(FilterChainProxy.java:192)
    at org.springframework.security.web.FilterChainProxy.doFilter(FilterChainProxy.java:160)
    at org.springframework.web.filter.DelegatingFilterProxy.invokeDelegate(DelegatingFilterProxy.java:343)
    at org.springframework.web.filter.DelegatingFilterProxy.doFilter(DelegatingFilterProxy.java:260)
    at org.apache.catalina.core.ApplicationFilterChain.internalDoFilter(ApplicationFilterChain.java:241)
    at org.apache.catalina.core.ApplicationFilterChain.doFilter(ApplicationFilterChain.java:208)
    at org.springframework.web.multipart.support.MultipartFilter.doFilterInternal(MultipartFilter.java:119)
    at org.springframework.web.filter.OncePerRequestFilter.doFilter(OncePerRequestFilter.java:107)
    at org.apache.catalina.core.ApplicationFilterChain.internalDoFilter(ApplicationFilterChain.java:241)
    at org.apache.catalina.core.ApplicationFilterChain.doFilter(ApplicationFilterChain.java:208)
    at org.apache.catalina.core.StandardWrapperValve.invoke(StandardWrapperValve.java:220)
    at org.apache.catalina.core.StandardContextValve.invoke(StandardContextValve.java:122)
    at org.apache.catalina.authenticator.AuthenticatorBase.invoke(AuthenticatorBase.java:501)
    at org.apache.catalina.core.StandardHostValve.invoke(StandardHostValve.java:171)
    at org.apache.catalina.valves.ErrorReportValve.invoke(ErrorReportValve.java:103)
    at org.apache.catalina.valves.AccessLogValve.invoke(AccessLogValve.java:950)
    at org.apache.catalina.core.StandardEngineValve.invoke(StandardEngineValve.java:116)
    at org.apache.catalina.connector.CoyoteAdapter.service(CoyoteAdapter.java:408)
    at org.apache.coyote.http11.AbstractHttp11Processor.process(AbstractHttp11Processor.java:1070)
    at org.apache.coyote.AbstractProtocol$AbstractConnectionHandler.process(AbstractProtocol.java:611)
    at org.apache.tomcat.util.net.JIoEndpoint$SocketProcessor.run(JIoEndpoint.java:314)
    at java.util.concurrent.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:1145)
    at java.util.concurrent.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:615)
    at org.apache.tomcat.util.threads.TaskThread$WrappingRunnable.run(TaskThread.java:61)
    at java.lang.Thread.run(Thread.java:745)

As It's mentioned in spring documentation the CSRF timeout is an issue that should be handled. One way to handle this scenario is to have custom AccessDeniedHandler where we an intercept CSRF exception. Something like:

static class CustomAccessDeniedHandler extends AccessDeniedHandlerImpl{

    @Override
    public void handle(HttpServletRequest request,
            HttpServletResponse response,
            AccessDeniedException accessDeniedException)
            throws IOException, ServletException {
        if (accessDeniedException instanceof MissingCsrfTokenException
                || accessDeniedException instanceof InvalidCsrfTokenException) {

            //What goes in here???

        }

        super.handle(request, response, accessDeniedException);

    }
}

Question: What is the best way to handle this situation without having to refresh the page( which is bad user experience) or have an endless session? Thanks for your help in advance.

Suleiman Alrosan
  • 337
  • 2
  • 4
  • 14
  • Are you doing an AJAX call? – giubueno Sep 07 '15 at 22:52
  • I would recommend sending a Refresh http response header that will refresh the page right after session expires, effectively reloading it and starting a new session. You could also run some javascript on your pages to warn a user when their session is about to expire – Neil McGuigan Sep 08 '15 at 05:25
  • Yes I'm using AJAX call . As I mentioned above, I would like to avoid renewable session that is endless. So having refresh head does not meet my requirements. Having JS to warn the user that the session is about to expire is a good idea but for the login page. – Suleiman Alrosan Sep 08 '15 at 13:52
  • You could also disable CSRF protection for the login page. CSRF should not be an issue on the login page. See for example http://stackoverflow.com/a/29147610/1852723 how to exclude certain URLs – Drunix Sep 09 '15 at 15:04

3 Answers3

9

The easiest way I found to handle invalidate CSRF token when session times out at the login page is one of the followings:

  1. Redirect the request again to the login page again vi CustomAccessDeniedHandler:

    static class CustomAccessDeniedHandler extends AccessDeniedHandlerImpl{
    
    
    
    
        @Override
        public void handle(HttpServletRequest request,
                HttpServletResponse response,
    
      AccessDeniedException accessDeniedException)
        throws IOException, ServletException {
    if (accessDeniedException instanceof MissingCsrfTokenException
            || accessDeniedException instanceof InvalidCsrfTokenException) {
    
        if(request.getRequestURI().contains("login")){
            response.sendRedirect(request.getContextPath()+"/login");                                        
        }
    }
    
    super.handle(request, response, accessDeniedException);
    
    
    
     }
    }
    
  2. Add refresh header as Neil McGuigan suggested:

<meta http-equiv="refresh" content="${pageContext.session.maxInactiveInterval}">

  1. Furthermore you must create a bean for the new CustomAccessDeniedHandler and register it. The following example shows this for Java config.

In any config class:

@Bean
public AccessDeniedHandler accessDeniedHandler() {
    return new CustomAccessDeniedHandler();
}

In your security config modify the configure method as follows:

@Override
protected void configure(final HttpSecurity http) throws Exception {
    http
      // ...
      .and()
      .exceptionHandling().accessDeniedHandler(accessDeniedHandler());
}

Also see here.

a more Optimum solution will be for Spring security to handle this situation in their framework.

stefanhgm
  • 55
  • 8
Suleiman Alrosan
  • 337
  • 2
  • 4
  • 14
-1

When using Spring Security, you must send the '_csrf', there are the following ways:

In Form:

<input type="hidden" name="${_csrf.parameterName}" value="${_csrf.token}"/>

In Ajax:

<head> <meta name="_csrf" content="${_csrf.token}"/> <!-- default header name is X-CSRF-TOKEN --> <meta name="_csrf_header" content="${_csrf.headerName}"/> <!-- ... --> </head>

$(function () {
  var token = $("meta[name='_csrf']").attr("content");
  var header = $("meta[name='_csrf_header']").attr("content");
  $(document).ajaxSend(function(e, xhr, options) {
    xhr.setRequestHeader(header, token);
  });
});

Source: http://docs.spring.io/autorepo/docs/spring-security/3.2.0.CI-SNAPSHOT/reference/html/csrf.html

morgano
  • 17,210
  • 10
  • 45
  • 56
Wilson Tamarozzi
  • 606
  • 1
  • 8
  • 10
  • I'm already doing that.The issue that I'm having is that CSRF token is getting invalid whenever the session times out, which result into unpleasant behavior in LOGIN page. – Suleiman Alrosan Sep 08 '15 at 13:59
-1

I using construct: csrf().disable(). After that the error message will be disapeared and the app works properly (under Spring Boot 2.3, JSF 2.4, JDK 14):

@Configuration
@EnableWebSecurity(debug = false)
public class SecurityConfiguration extends WebSecurityConfigurerAdapter {

    @Override
    protected void configure(HttpSecurity httpSecurity) throws Exception {

        httpSecurity.httpBasic().disable().csrf().disable();
    }

}
Iakov Senatov
  • 151
  • 1
  • 5
  • That disables CSRF protection, and is a bad idea, unless you're really sure that you don't need CSRF protection. – Dario Seidl Oct 22 '21 at 14:44