无法使用Spring Security创建CSRF令牌

时间:2014-05-15 04:44:48

标签: spring-mvc spring-security csrf thymeleaf

我在Spring MVC应用程序中使用Spring Security 3.2.3并获得了一些意想不到的行为。

根据documentation here,应该可以在我的html的元标记中使用${_csrf.token}

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

从中我使用JQuery提取“content”的值,并使用AJAX将其放入Request Header。

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

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

由于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();
      }
}

我知道configure已被调用,因为我在其中放入了一个调试语句,所以我认为CSRF保护确实已启用,因为它应该是默认的。

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

th:object="${context}"

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

<meta name="_csrf" th:content="${_csrf.token}"/>

但是它导致了一个例外情况:无法评估:

  

评估SpringEL表达式的异常:“_ csff.token”

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

6 个答案:

答案 0 :(得分:10)

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

首先,我遵循了Eyal Lupu的精彩博文here中的建议,但由于我的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,从该隐藏字段中读取值并设置请求标头&#34; X-CSRF-Token&#34;属性,以便它通过HTTP发送。由于我们没有使用表单提交,而是使用AJAX POST来调用服务器端的方法,因此无法简单地将令牌保留在隐藏的输入字段中。

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

答案 1 :(得分:3)

我认为,与我相同的source article开始,并且你应该像你一样“添加答案”。我用不同的方式打了它。我让Thymeleaf给了我想要的答案。

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

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

答案 2 :(得分:3)

在将thymeleaf-extras-springsecurity名称空间及其依赖项添加到我的项目之前,我遇到了类似的问题。我从来没有让元标记工作,即使有百里香 - 额外 - 弹簧安全。但我确实使用隐藏输入成功检索了Spring Security的csrf令牌。我有以下说明,对我有用:
在html标记中,添加:
xmlns:sec="http://www.thymeleaf.org/thymeleaf-extras-springsecurity4"

在你的pom.xml中(如果你正在使用Maven)你需要添加依赖项: thymeleaf-extras-springsecurity4
然后在页面正文中添加隐藏的输入以检索csrf标记。
    <input type="hidden" id= "csrf-token" th:name="${_csrf.parameterName}" th:content="${_csrf.token}" />
然后在你的javascript / jquery中使用它,如下所示:
  function f1() { var token1 = $('input#csrf-token').attr("content"); ... $.ajax({ ... type: "POST", beforeSend: function (request) { request.setRequestHeader("X-CSRF-TOKEN", token1); }, ...
这一切都假设您已启用弹簧安全性,并且您尚未关闭csrf保护。

答案 3 :(得分:1)

您的web.xml中的springSecurityFilterChain配置不正确。正确的定义是:

<filter>
    <filter-name>springSecurityFilterChain</filter-name>
    <filter-class>org.springframework.web.filter.DelegatingFilterProxy</filter-class>
</filter>

Spring Security使用一组servlet过滤器来提供它提供的功能(包括CSRF保护)。这些过滤器被定义为Spring bean(即它们由Spring应用程序上下文实例化和管理)。 DelegatingFilterProxy是一种特殊类型的servlet过滤器,它在已注册的 servlet上下文中找到根应用程序上下文,并将每次调用委托给同一个命名bean。

答案 4 :(得分:0)

你的问题是另一个问题,我也偶然发现了这个问题,我花了几个小时才找出原因。您描述的问题的原因是您没有在spring-security.xml

中启用csrf支持

这个小片段需要进入你的security-config.xml:

<!-- Static resources such as CSS and JS files are ignored by Spring Security -->
<security:http pattern="/static/**" security="none" />

<security:http use-expressions="true">

    <!-- Enables Spring Security CSRF protection -->
    <security:csrf/>

    <!-- Configures the form login -->
    <security:form-login
            login-page="/login"
            login-processing-url="/login/authenticate"
            authentication-failure-url="/login?error=bad_credentials"
            username-parameter="username"
            password-parameter="password"/>
    <!-- Configures the logout function -->
    <security:logout
            logout-url="/logout"
            logout-success-url="/login"
            delete-cookies="JESSIONID"/>
    <!-- Anyone can access these urls -->
    <security:intercept-url pattern="/auth/**" access="permitAll"/>
    <security:intercept-url pattern="/login" access="permitAll"/>
    <security:intercept-url pattern="/signin/**" access="permitAll"/>
    <security:intercept-url pattern="/signup/**" access="permitAll"/>
    <security:intercept-url pattern="/user/register/**" access="permitAll"/>

    <!-- The rest of our application is protected. -->
    <security:intercept-url pattern="/**" access="hasRole('ROLE_USER')"/>

    <!-- Adds social authentication filter to the Spring Security filter chain. -->
    <security:custom-filter ref="socialAuthenticationFilter" before="PRE_AUTH_FILTER" />
</security:http>
....
...
..
.

通过正确配置来节省时间......

Cheerio, FLO!

答案 5 :(得分:0)

如果您不需要使用Thymeleaf,我建议如下:

  1. 将其添加到页面顶部:

    <%@ taglib prefix="c" uri="http://java.sun.com/jsp/jstl/core"%>
    
  2. 将此添加到您的登录表单:

    <input type="hidden" name="${_csrf.parameterName}" value="${_csrf.token}" />
    
  3. 将这些依赖项添加到您的pom.xml:

    <dependency>
        <groupId>org.apache.tomcat.embed</groupId>
        <artifactId>tomcat-embed-jasper</artifactId>
        <scope>provided</scope>
    </dependency>
    <dependency>
        <groupId>javax.servlet</groupId>
        <artifactId>jstl</artifactId>
    </dependency>   
    
  4. 经过多次努力,这对我有用。