从Spring Security 4开始,默认启用CSRF机制,本来这也不算什么大事,但与Spring Boot结合在一起,那么实现起来就比较麻烦了,尤其是采用前后端分离式的开发架构后,配置CSRF机制就更困难了,几乎所有网上的解决办法都无法解决如何获取CSRF编码的难题,首先以表单登陆的错误镇楼:
There was an unexpected error (type=Forbidden, status=403).
Invalid CSRF Token 'null' was found on the request parameter '_csrf' or header 'X-XSRF-TOKEN'.
1. 禁用CSRF机制
禁用CSRF机制能非常完美地兼容Spring Security老版本,但在Spring Boot的实现中,如果继续沿用老版本的配置,大部分的人会碰到以下错误:
An Authentication object was not found in the SecurityContext
这是因为Spring MVC自动代理所有的请求导致的问题,所以解决办法很简单,将多个“http”配置转换为一个“http”配置,并且禁止使用“security=”none””,下面是错误的写法:
<sec:http pattern="/login.html*" security="none"/>
正确的写法如下:
<sec:http use-expressions="false">
<sec:intercept-url pattern="/login.html*" access="IS_AUTHENTICATED_ANONYMOUSLY"/>
</sec:http>
上述两种写法的差异在于第二种会穿越所有的安全过滤器,例如“SecurityContextPersistenceFilter”、“LogoutFilter”,“CsrfFilter”等,而第一种则不会。很显然,对于静态资源,第一种的性能要高很多,但是如果你的静态委托Spring Boot进行管理,那么你只能采用第二种配置方式。
禁用CSRF机制很简单,只需要将其设置为“disabled”即可,如下:
<sec:csrf disabled="true"/>
但是在实战中,我发现很多人在登陆过程中,会提示如下错误:
There was an unexpected error (type=Method Not Allowed, status=405).
Request method 'POST' not supported
经过跟踪调试,发现问题不在于CSRF,而在于“form-login”配置错误,如下:
<sec:form-login login-page="/login.html"
password-parameter="password"
username-parameter="username"
default-target-url="/admin.html"
<!-- 问题就在于此项配置,forward -->
authentication-success-forward-url="/admin.html"
login-processing-url="/j_security_check"/>
问题就在注释的那一行代码,“authentication-success-forward-url”意味着会将登陆请求转发给新地址,这就要求新地址必须支持“POST”,否则将出现405错误,所以解决办法也很简单,要么将那项配置注销,要么将转发的地址必须支持“POST”请求。
2. 启用CSRF机制
启用CSRF机制必须要做到如下几点:
1. 首先命名CSRF Cookie;
2. 从Cookie中获取CSRF Token;
3. 在提交表单时必须添加CSRF Token;
4. 注销系统时必须采用POST方式(必然要添加CSRF Token);
上面四件事少做一件,都会提示如下错误:
There was an unexpected error (type=Forbidden, status=403).
Invalid CSRF Token 'null' was found on the request parameter '_csrf' or header 'X-XSRF-TOKEN'.
2.1 命名CSRF Cookie
命名CSRF Cookie要结合使用如下两项配置,如下:
<sec:csrf token-repository-ref="tokenRepository"/>
<bean id="tokenRepository"
class="org.springframework.security.web.csrf.CookieCsrfTokenRepository">
<property name="cookieHttpOnly" value="false"/>
<property name="cookieName" value="X-XSRF-TOKEN"/>
<property name="headerName" value="X-XSRF-TOKEN"/>
</bean>
2.2 获取CSRF Token
在前后端分离式结构中,每次视图刷新时都会返回新的CSRF Token,但在我们的第一步,已将CSRF Token放置在Cookie中,所以只需要将其从Cookie中取出即可,如下:
// 将Cookie转换为JS Object
function initCookies() {
var cookie = document.cookie,
items = cookie.split(";"),
keys = {};
items.forEach(function(item) {
var kv = item.split('=');
keys[$.trim(kv[0])] = $.trim(kv[1]);
});
return keys;
}
// 获取CSRF Token
var _csrf = initCookies()['X-XSRF-TOKEN'];
2.3 在AJAX请求中添加CSRF Token
有了第二步,现在添加CSRF Token就容易多了,如下:
// 提交数据
$.post(url, {
userId : code,
_csrf : cookies['X-XSRF-TOKEN']
}, function(datas) {
// TODO something
})
2.4 注销系统改为POST方式
参照上述的操作即可,略。
结论
禁用CSRF容易,启用CSRF难,并且差异较大,请密切注意它们对安全过滤器的影响。