【问题标题】:Can't create CSRF token with Spring Security无法使用 Spring Security 创建 CSRF 令牌
【发布时间】:2014-07-03 09:07:50
【问题描述】:

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

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

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

我使用 JQuery 提取“内容”的值并使用 AJAX 将其放入请求标头中。

尽管出于某种原因,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();
      }
}

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

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

th:object="${context}"

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

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

但是会导致 this 无法评估的异常:

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

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

【问题讨论】:

  • “${_csrf.token}”语法是否可能仅适用于 JSP,而不适用于 .html 文件?
  • 现在的问题是,在运行 Spring Security 时,即使我们已经设置了身份验证方法,它也很烦人地想要对用户进行身份验证,所以我收到了一个未经授权的错误。我需要找出如何绕过 Spring Security 的默认身份验证要求......似乎没有简单的方法可以将其关闭,如果您不包含身份验证配置,整个应用程序将拒绝运行(令人抓狂!)。我只想要这个框架的 CSRF 功能!将继续调查如何绕过...
  • 所以经过反复测试,似乎 Spring Security 确实运行了,但我仍然只得到 ${_csrf.token} 的字符串。我认为这与我在需要 JSP 来评估值时使用 .html 文件有关,但是将其更改为 .jsp 文件会导致“org.thymeleaf.exceptions.TemplateInputException:解析模板时出错 {myapplication},模板可能不存在或可能无法被任何已配置的模板解析器访问”
  • 只是说两个 META 元素中的th:content="${_csrf.whateverProperty}" 表单确实对我有用。弹簧 4.1.x、弹簧安全 4.0.x、Thymeleaf 2.1.x。如果我使用无效的属性名称,我会得到 SpelEvaluationException。在报告原始问题时,您遇到了其他一些配置问题。

标签: spring-mvc spring-security csrf thymeleaf


【解决方案1】:

我终于解决了这个问题,但它基本上需要重写 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,从该隐藏字段读取值并设置请求标头的“X-CSRF-Token”属性,以便通过 HTTP 发送。不可能简单地将令牌留在隐藏的输入字段中,因为我们没有进行表单提交,而是使用 AJAX POST 来调用服务器端的方法。

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

【讨论】:

    【解决方案2】:

    我认为,我从与您相同的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 标头中。

    【讨论】:

      【解决方案3】:

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

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

      【讨论】:

        【解决方案4】:

        您的 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。

        【讨论】:

        • 谢谢。但是,它对行为没有影响。仍然只是看到 csrf 令牌的文字字符串 :(
        • 这里有一个问题:如果我的配置文件是Java类,但其他配置在web.xml中完成,Spring Security会工作吗?即是否只需要全 XML 配置,还是只需要全 Java 配置?
        • 您可以根据需要混合使用 XML 和 Java 配置。当然你需要仔细观察谁在加载什么。如果您尝试将命名空间配置 (&lt;security:http&gt;) 与特殊注释配置 (@EnableWebMvcSecurity) 混合使用,可能会出现问题。
        • 我还无法生成 CSRF 令牌,但我能够让 Spring Security 运行(以前没有)。首先,同时拥有 Java 配置和 XML 配置会导致异常“java.lang.IllegalArgumentException: Expecting to only find a single bean for type interface org.springframework.security.authentication.AuthenticationManager”。其次,对于纯 Java 路由,我只有一个 Java 类(如上)来配置 Spring Security,而实际上你需要 3 个!见此链接:spring.io/blog/2013/07/03/…
        【解决方案5】:

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

        这个小 sn-p 需要进入你的 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>
        ....
        ...
        ..
        .
        

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

        欢呼, 弗洛!

        【讨论】:

          【解决方案6】:

          如果您不需要使用 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>   
            

          经过很多努力,这对我有用。

          【讨论】:

            猜你喜欢
            • 2019-07-20
            • 2020-08-31
            • 2017-09-07
            • 1970-01-01
            • 1970-01-01
            • 2012-03-06
            • 2015-03-06
            • 2014-04-26
            相关资源
            最近更新 更多