【问题标题】:What is the best way to handle Invalid CSRF token found in the request when session times out in Spring security在 Spring 安全中会话超时时处理请求中发现的无效 CSRF 令牌的最佳方法是什么
【发布时间】:2015-12-03 11:53:33
【问题描述】:

我正在使用 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)

正如 Spring 文档中提到的,CSRF timeout 是一个应该处理的问题。处理这种情况的一种方法是使用自定义 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);

    }
}

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

【问题讨论】:

  • 你在做 AJAX 调用吗?
  • 我建议发送一个 Refresh http 响应标头,该标头将在会话到期后立即刷新页面,从而有效地重新加载并启动新会话。您还可以在您的页面上运行一些 javascript,以在用户会话即将到期时警告用户
  • 是的,我正在使用 AJAX 调用。正如我上面提到的,我想避免无休止的可更新会话。所以刷新头不符合我的要求。让 JS 警告用户会话即将到期是一个好主意,但对于登录页面。
  • 您也可以禁用登录页面的 CSRF 保护。 CSRF 不应该是登录页面上的问题。参见例如 stackoverflow.com/a/29147610/1852723 如何排除某些 URL

标签: java spring-security csrf session-timeout http-status-code-403


【解决方案1】:

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

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

  1. 此外,您必须为新的 CustomAccessDeniedHandler 创建一个 bean 并注册它。以下示例显示了 Java 配置的这一点。

在任何配置类中:

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

在您的安全配置中修改配置方法如下:

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

另见here。

一个更优化的解决方案是让 Spring 安全性在其框架中处理这种情况。

【讨论】:

  • 这不是更容易暴力破解吗?基本上,您正在节省获取令牌的一步
【解决方案2】:

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

在表格中:

&lt;input type="hidden" name="${_csrf.parameterName}" value="${_csrf.token}"/&gt;

在 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);
  });
});

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

【讨论】:

  • 我已经这样做了。我遇到的问题是 CSRF 令牌在会话超时时变得无效,这会导致登录页面中出现令人不快的行为。
【解决方案3】:

我使用构造:csrf().disable()。之后错误消息将消失,应用程序正常运行(在 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();
    }

}

【讨论】:

  • 这会禁用 CSRF 保护,这是个坏主意,除非你真的确定不需要 CSRF 保护。
猜你喜欢
  • 2012-03-30
  • 1970-01-01
  • 1970-01-01
  • 2011-03-19
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多