个人博客原文链接:http://www.ltang.me/2015/10/27/spring-security-csrf-study/
已经有现成的springMVC项目,已经在web.xml设置好了filter,所有请求在分发到controller之前先被spring security处理。
项目中已经使用spring security完成关于登陆和用户认证、chookie、logout等功能,现在做的是开启csrf防御功能。
- 在
applicationContext-security.xml
配置文件中,将<csrf disabled="true"/>
修改为<csrf/>
,开启csrf防御功能。
登陆接口改造
登录页面不修改,点击登录时,请求会被spring security拦截,返回403错误,提示没有token,请求不会被后台servlet处理;
在登录的form上添加隐藏输入:
<input type="hidden" name="${_csrf.parameterName}" value="${_csrf.token}"/>
, 此时点击登录,则请求会被后台处理,提示用户名密码错误或者登录成功等。
注册接口改造
注册页面,使用ajax提交post请求,开启csrf防御后,若不修改,则ajax提交失败,返回403错误:
Failed to load resource: the server responded with a status of 403 (Forbidden)
在注册页面
register.jsp
的header
中添加:<meta name="_csrf" content="${_csrf.token}"/> <meta name="_csrf_header" content="${_csrf.headerName}"/>``` 在`register.js`中添加代码:
var ttoken = (“meta[name=′csrf′]”).attr(“content”);vartheader= (“meta[name=’_csrf_header’]”).attr(“content”);
$(document).ajaxSend(function(e, xhr, options){
xhr.setRequestHeader(theader, ttoken);
});
“`
此时再点击下一步,将不会返回403错误,而是返回后台处理请求后的结果。
如上,spring securtiy会拦截所有post请求,并校验token值,若没有token值或不正确,则返回403错误,后台不会处理请求。
退出登陆接口改造
必须要更正一下,之前我认为退出登录接口不是必须使用post方式,可以使用get方式,只是建议使用post方式:
同理,退出登陆接口也要从原本的get方式改为post方式,否则不会被spring security csrf处理。
官方文档的说法是,不想用post方式也可以,但是强烈建议用post方式…
从某种程度上来说也没错,因为确实可以通过额外的工作使得logout支持get方式,如下:
@EnableWebSecurity
public class WebSecurityConfig extends
WebSecurityConfigurerAdapter {
@Override
protected void configure(HttpSecurity http) throws Exception {
http
.logout()
.logoutRequestMatcher(new AntPathRequestMatcher("/logout"));
}
}
后来在项目中,默认情况下,使用get方式logout,才发现返回404错误,仔细查看文档,发现:
Adding CSRF will update the LogoutFilter to only use HTTP POST. This ensures that log out requires a CSRF token and that a malicious user cannot forcibly log out your users.
所以,最后还是把所有登出接口都改为post方式请求了。
修改前:
<a href="${basePath}/j_spring_security_logout" style="margin-right: 8px;" rel="nofollow">退出</a>
修改后:
<a href='javascript:document.logoutForm.submit();' style="margin-right: 8px;" rel="nofollow">退出</a>
<form name="logoutForm" class="form" action="${basePath}
/j_spring_security_logout" method="post" style="display: none">
<input type="hidden" name="${_csrf.parameterName}" value="${_csrf.token}"/>
</form>
文件上传接口
文件上传肯定使用post方式了,按照文档说法, 有两种改造方式:
- 在
springSecurityFilterChain
filter 之前添加MultipartFilter
filter, 如下:
<filter>
<filter-name>MultipartFilter</filter-name>
<filter-class>org.springframework.web.multipart.support.MultipartFilter</filter-class>
</filter>
<filter>
<filter-name>springSecurityFilterChain</filter-name>
<filter-class>org.springframework.web.filter.DelegatingFilterProxy</filter-class>
</filter>
<filter-mapping>
<filter-name>MultipartFilter</filter-name>
<url-pattern>/*</url-pattern>
</filter-mapping>
<filter-mapping>
<filter-name>springSecurityFilterChain</filter-name>
<url-pattern>/*</url-pattern>
</filter-mapping>
这样上传文件时就不会因为没有传token值而被拦截返回403错误,但是谁都可以上传临时文件到你服务器了。不过官网文档说鼓励这种做法因为临时文件的上传对大多数服务器没有严重影响?
在url中添加token值,不过这样可能导致token泄露..不过没关系啦每次token都不一样:
<form action="./upload?${_csrf.parameterName}=${_csrf.token}" method="post" enctype="multipart/form-data">
项目中使用的是ajaxFileUpload方式上传文件,所以修改后代码如下:
var csrfParameter = $("meta[name='_csrf_parameter']").attr("content"); var csrfToken = $("meta[name='_csrf']").attr("content"); var url = basePath + '/account/rechargeApply.html?' + csrfParameter + '=' + csrfToken; $.ajaxFileUpload({ url:url, ...
总结&参考
为了防止CSRF攻击,接口请求尽量使用post方式,开启spring security csrf后,能够防御大部分CSRF攻击。
参考链接: