无法使用 Spring Security 创建 CSRF 令牌

Posted

技术标签:

【中文标题】无法使用 Spring Security 创建 CSRF 令牌【英文标题】:Can't create CSRF token with Spring Security 【发布时间】: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。在报告原始问题时,您遇到了其他一些配置问题。 【参考方案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 中苦苦挣扎的人,以下是我的步骤(这将其缩减了很多,但这些是解决它的高级概念):

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

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

    扩展 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,我建议如下:

    将此添加到页面顶部:

    <%@ taglib prefix="c" uri="http://java.sun.com/jsp/jstl/core"%>
    

    将此添加到您的登录表单:

    <input type="hidden" name="$_csrf.parameterName" value="$_csrf.token" />
    

    将这些依赖项添加到您的 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>   
    

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

【讨论】:

以上是关于无法使用 Spring Security 创建 CSRF 令牌的主要内容,如果未能解决你的问题,请参考以下文章

无法使用Spring Security弹出用户凭据请求?

自定义登录表单无法使用 Spring Security

如何为 Spring Security 创建类型安全的用户角色?

使用 Acegi/Spring Security 创建自定义身份验证

无法在 Spring Security 中用自定义替换标准登录表单

Spring Security 3.1.4:由于anonymousUser身份验证无法访问目标页面