Ajax GET 请求后表单提交失败(无效的 CSRF 令牌)。怎么修?

Posted

技术标签:

【中文标题】Ajax GET 请求后表单提交失败(无效的 CSRF 令牌)。怎么修?【英文标题】:Form submission fails (invalid CSRF token) after Ajax GET request. How to fix? 【发布时间】:2020-07-18 13:16:51 【问题描述】:

我有一个提交 POST 请求的“客户详细信息”表单。这是在 Spring Boot 应用程序中,并作为 Thymeleaf 模板实现。我刚刚在表单中添加了一点 AJAX 功能——当用户单击某个按钮时,它使用jQuery.ajax() 根据客户地址查找地理代码。 AJAX 调用是一个 GET 请求。

如果我输入客户表单并单击“提交”而不触摸 Ajax 按钮,它会正常提交。但是如果我先使用 Ajax 按钮,然后尝试提交表单,它会被 CsrfFilter 停止并显示消息“Invalid CSRF token found for http://...”。

我假设 Ajax 调用以某种方式使 CSRF 令牌过期,因此需要一个新令牌,但我该如何防止呢?

有一些类似this one 之类的问题,但在这种情况下,他们正在寻找一种将 CSRF 令牌添加到 Ajax 请求的方法。此外,我正在使用 Thymeleaf 视图,因此我将 CSRF 令牌自动插入到我的表单中——我不必手动添加它。

【问题讨论】:

【参考方案1】:

问题是我已经实现了一个自定义过滤器来删除每个请求的安全上下文。这是我制作应用程序无状态的方式,正如我记录的in this software engineering SE answer。这会删除(或重置?)服务器端 CSRF 令牌,因此它与表单中的令牌不匹配,但是,自定义过滤器在CsrfFilter 之后触发 所以我从来没有遇到过问题之前的表单提交。

在这种情况下,虽然我有在为客户端构建表单之后但在提交之前处理的 Ajax 请求,所以我的自定义过滤器的不幸副作用导致表单提交失败。

如果 URL 位于特定目录路径下,我通过修改自定义过滤器跳过删除上下文来纠正它:

public class SecurityContextDeletingFilter extends GenericFilterBean 

    RequestMatcher exceptedPaths = new AntPathRequestMatcher("/form-ajax/**");

    @Override
    public void doFilter(ServletRequest servletRequest, ServletResponse servletResponse, FilterChain filterChain) throws IOException, ServletException 
        final HttpServletRequest request = (HttpServletRequest) servletRequest;
        final HttpSession session = request.getSession();

        if( exceptedPaths.matches(request)) 
            // Deleting the security context deletes the CSRF token in the session. We want to make sure this
            // does NOT occur if the user is in a form and is using Ajax features like the geocode lookup,
            // because they'll need the server to recognize the token on eventual form submission.
            logger.trace("Skipping this filter for a /form-ajax/ endpoint.");
         else 
            if( session.getAttribute(HttpSessionSecurityContextRepository.SPRING_SECURITY_CONTEXT_KEY) != null ) 
                session.removeAttribute(HttpSessionSecurityContextRepository.SPRING_SECURITY_CONTEXT_KEY);
            
        

        filterChain.doFilter(servletRequest,servletResponse);
    

【讨论】:

无国籍是什么意思?稍后在过滤器链中将session.removeAttribute(HttpSessionSecurityContextRepository.SPRING_SECURITY_CONTEXT_KEY); 作为 SessionManagementFilter 毫无意义将检查 SecurityContext 是否包含经过身份验证的用户,如果是,它将在会话中将身份验证存储在 SPRING_SECURITY_CONTEXT_KEY 键下。因此,基本上您在自定义过滤器中将其删除,但稍后 SessionManagementFilter 会将其放回相同的请求中。阅读 15.1 docs.spring.io/spring-security/site/docs/3.2.0.CI-SNAPSHOT/… 如果您阅读链接的帖子,我会根据自定义 JWT cookie(不是 JSESSIONID cookie)对每个请求的用户进行身份验证。当用户单击“退出”时,我删除了 JWT cookie,但之前,Spring 通过 JSESSIONID cookie 保持用户身份验证,因此他们无法注销。 SecurityContextDeletingFilter 解决了这个问题。

以上是关于Ajax GET 请求后表单提交失败(无效的 CSRF 令牌)。怎么修?的主要内容,如果未能解决你的问题,请参考以下文章

jQuery Ajax 前端和后端数据交互的问题

jQuery ajax post提交本页面处理,为啥提交后URL还携带参数

通过html表单方式提交数据(可以指定get和post)和ajax方式请求的原理是一样的吗?

jQuery-Mobile:ajax请求在changePage失败后停止工作

根据ajax响应提交表单

CodeIgniter:提交无效表单后如何保留值?