java 当会话在 Spring 安全中超时时,处理请求中发现的无效 CSRF 令牌的最佳方法是什么

声明:本页面是StackOverFlow热门问题的中英对照翻译,遵循CC BY-SA 4.0协议,如果您需要使用它,必须同样遵循CC BY-SA许可,注明原文地址和作者信息,同时你必须将它归于原作者(不是我):StackOverFlow 原文地址: http://stackoverflow.com/questions/32446903/
Warning: these are provided under cc-by-sa 4.0 license. You are free to use/share it, But you must attribute it to the original authors (not me): StackOverFlow

提示:将鼠标放在中文语句上可以显示对应的英文。显示中英文
时间:2020-11-02 20:12:38  来源:igfitidea点击:

What is the best way to handle Invalid CSRF token found in the request when session times out in Spring security

javaspring-securitycsrfsession-timeouthttp-status-code-403

提问by Suleiman Alrosan

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 :

我正在使用 Spring MVC/Security 3.X。问题是,每当会话超时时,我都会在登录页面上收到 403 ,其中 Spring 框架在下面抛出“InvalidCsrfTokenException”:

    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 timeoutis 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:

正如在 spring 文档中提到的,CSRF 超时是一个应该处理的问题。处理这种情况的一种方法是使用自定义 AccessDeniedHandler,我们可以在其中拦截 CSRF 异常。就像是:

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.

问题:处理这种情况的最佳方法是什么,而不必刷新页面(这是糟糕的用户体验)或无休止的会话?提前感谢您的帮助。

采纳答案by Suleiman Alrosan

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

我发现在登录页面会话超时时处理无效 CSRF 令牌的最简单方法是以下之一:

  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:

  1. 再次将请求重定向到登录页面 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. 按照 Neil McGuigan 的建议添加刷新标题:

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

<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.
  1. 此外,您必须为新的 CustomAccessDeniedHandler 创建一个 bean 并注册它。以下示例显示了 Java 配置。

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.

一个更优化的解决方案是 Spring security 在他们的框架中处理这种情况。

回答by Wilson Tamarozzi

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

使用 Spring Security 时,必须发送'_csrf',有以下几种方式:

In Form:

形式

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

<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>

<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

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