无法使用Spring Security创建CSRF令牌

sta*_*uxe 12 spring-mvc csrf spring-security thymeleaf

我在我的Spring MVC应用程序中使用Spring Security 3.2.3并获得一些意外行为.

根据这里文档,应该可以${_csrf.token}在我的html的meta标签中使用:

<meta name="_csrf" content="${_csrf.token}" />
<!-- default header name is X-CSRF-TOKEN -->
<meta name="_csrf_header" content="${_csrf.headerName}" />
Run Code Online (Sandbox Code Playgroud)

从中我使用JQuery提取"内容"的值,并使用AJAX将其放入Request Header.

出于某种原因,Spring Security不会将其"转换"为实际令牌,它只是作为文字字符串"$ {_ csrf.token}"发送到标头中.

${_csrf.token}根据文档尝试在隐藏输入中使用的替代路径,然后我尝试通过检查输入的值来检查令牌评估的内容,但它仍然只是纯文本"$ {_ csrf.token}".

由于Spring Security似乎没有生效,我是否缺少某种配置?我目前正在使用准系统Spring Security Java配置(而不是xml),如下所示:

import org.springframework.context.annotation.*;
import org.springframework.security.config.annotation.web.builders.HttpSecurity;
import org.springframework.security.config.annotation.web.configuration.*;

@Configuration
@EnableWebSecurity
public class WebSecurityConfig extends WebSecurityConfigurerAdapter {

    @Override
    protected void configure(HttpSecurity http) throws Exception {
        http
          .csrf();
      }
}
Run Code Online (Sandbox Code Playgroud)

我知道配置被调用,因为我在其中放入了一个调试语句,因此我假设CSRF保护确实已启用,因为它应该是默认的.

我意识到语法"$ {}"是JSP表达式语言,我目前正成功地用它来评估Thymeleaf对象的上下文,例如:

th:object="${context}"
Run Code Online (Sandbox Code Playgroud)

所以我尝试在元标记的"内容"前添加"th:",如下所示:

<meta name="_csrf" th:content="${_csrf.token}"/>
Run Code Online (Sandbox Code Playgroud)

但它导致一个例外,无法评估:

评估SpringEL表达式的异常:"_csrf.token"

我认为这里的关键可能是弄清楚如何在我看来正确评估表达式.

sta*_*uxe 9

我终于解决了这个问题,但它基本上需要重写Spring Security.这里充满了荣耀.

首先,我遵循的Eyal卢浦的精彩的博客贴子的建议在这里,但我不得不把它调整到我的情况,因为我的AJAX的要求.

至于Thymeleaf的情况,关键的花絮被隐藏在Thymeleaf论坛的档案中 - 臭名昭着的第7期.

https://github.com/thymeleaf/thymeleaf-spring/issues/7#issuecomment-27643488

Thymeleaf自己的创始人的最后评论说:

th:action...检测何时将此属性应用于标记 - 无论如何应该是唯一的地方 - 并且在这种情况下调用RequestDataValueProcessor.getExtraHiddenFields(...)并在结束标记之前添加返回的隐藏字段.

这是我需要让令牌工作的关键短语.不幸的是,为什么th:action还要开始getExtraHiddenFields,这是完全不明显的,但无论如何,它确实如此,这才是最重要的.

所以对于那些在Thymeleaf + Spring Security CSRF + AJAX POST中苦苦挣扎的人来说,这里有我的步骤(这是相当多的,但这些是解决它的高级概念):

  1. 实现Spring接口RequestDataValueProcessor并在Spring Security的XML配置中注册它,这样你就可以覆盖方法getExtraHiddenFields,它允许你在HTML中插入一个隐藏的输入字段(当然还有令牌).令牌本身是使用Java.Util UUID生成的.

  2. 使用JQuery,从该隐藏字段中读取值并设置Request Header的"X-CSRF-Token"属性,以便通过HTTP发送它.不可能简单地将令牌留在隐藏的输入字段中,因为我们没有做表单提交,而是使用AJAX POST来调用服务器端的方法.

  3. 扩展Spring的HandlerInterceptorAdapter并将其注册为拦截器,以便每次完成POST方法时,都会调用服务器端的"preHandle"方法,以便它可以将请求令牌(从上一步骤中的HTTP头提取)与会话的令牌(应该是相同的!).执行此检查后,它可以允许请求通过或返回错误.


小智 5

我认为我从与您相同的源文章开始,并且与您一样“您应该能够”添加答案。我以不同的方式与之抗争。我让 Thymeleaf 给了我想要的答案。

<meta name="_csrf" th:content="${_csrf.token}"/>
<!-- default header name is X-CSRF-TOKEN -->
<meta name="_csrf_header" th:content="${_csrf.headerName}"/>
Run Code Online (Sandbox Code Playgroud)

Thymeleaf 将属性“content”与请求的 Spring EL 内容放在一起。然后我使用提供的 JavaScript/JQuery 将元标记中的信息直接提取到 CSRF 标头中。