从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
- 2
1. 禁用CSRF机制
禁用CSRF机制能非常完美地兼容Spring Security老版本,但在Spring Boot的实现中,如果继续沿用老版本的配置,大部分的人会碰到以下错误:
An Authentication object was not found in the SecurityContext
- 1
这是因为Spring MVC自动代理所有的请求导致的问题,所以解决办法很简单,将多个“http”配置转换为一个“http”配置,并且禁止使用“security=”none””,下面是错误的写法:
<sec:http pattern="/login.html*" security="none"/>
- 1
正确的写法如下:
<sec:http use-expressions="false"> <sec:intercept-url pattern="/login.html*" access="IS_AUTHENTICATED_ANONYMOUSLY"/></sec:http>
- 1
- 2
- 3
上述两种写法的差异在于第二种会穿越所有的安全过滤器,例如“SecurityContextPersistenceFilter”、“LogoutFilter”,“CsrfFilter”等,而第一种则不会。很显然,对于静态资源,第一种的性能要高很多,但是如果你的静态委托Spring Boot进行管理,那么你只能采用第二种配置方式。
禁用CSRF机制很简单,只需要将其设置为“disabled”即可,如下:
<sec:csrf disabled="true"/>
- 1
但是在实战中,我发现很多人在登陆过程中,会提示如下错误:
There was an unexpected error (type=Method Not Allowed, status=405).Request method 'POST' not supported
- 1
- 2
经过跟踪调试,发现问题不在于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"/>
- 1
- 2
- 3
- 4
- 5
- 6
- 7
问题就在注释的那一行代码,“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'.
- 1
- 2
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>
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
2.2 获取CSRF Token
在前后端分离式结构中,每次视图刷新时都会返回新的CSRF Token,但在我们的第一步,已将CSRF Token放置在Cookie中,所以只需要将其从Cookie中取出即可,如下:
// 将Cookie转换为JS Objectfunction 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 Tokenvar _csrf = initCookies()['X-XSRF-TOKEN'];
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
2.3 在AJAX请求中添加CSRF Token
有了第二步,现在添加CSRF Token就容易多了,如下:
// 提交数据$.post(url, { userId : code, _csrf : cookies['X-XSRF-TOKEN']}, function(datas) { // TODO something})
- 1
- 2
- 3
- 4
- 5
- 6
- 7
2.4 注销系统改为POST方式
参照上述的操作即可,略。
结论
禁用CSRF容易,启用CSRF难,并且差异较大,请密切注意它们对安全过滤器的影响。