无法使用 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 令牌。 <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);
,
...
这一切都假设您启用了 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 配置。当然你需要仔细观察谁在加载什么。如果您尝试将命名空间配置 (<security:http>
) 与特殊注释配置 (@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 创建类型安全的用户角色?
使用 Acegi/Spring Security 创建自定义身份验证