我在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”
我认为这里的关键可能是弄清楚如何在我看来正确评估表达式。
答案 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的人来说,这里是我的步骤(这是相当多的,但这些是解决它的高级概念):
实现Spring接口RequestDataValueProcessor并在Spring Security的XML配置中注册它,这样你就可以覆盖方法getExtraHiddenFields,它允许你在HTML中插入一个隐藏的输入字段(当然还有令牌) 。令牌本身是使用Java.Util UUID生成的。
使用JQuery,从该隐藏字段中读取值并设置请求标头&#34; X-CSRF-Token&#34;属性,以便它通过HTTP发送。由于我们没有使用表单提交,而是使用AJAX POST来调用服务器端的方法,因此无法简单地将令牌保留在隐藏的输入字段中。
扩展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,我建议如下:
将其添加到页面顶部:
<%@ 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>
经过多次努力,这对我有用。