4.使用Spring Security CSRF
Spring Security 采取那些必要的措施防御CSRF攻击? 步骤如下
1、使用适当的HTTP请求动作
2、配置CSRF防御信息
3、包含CSRFToken信息
使用适当的HTTP 请求动作
第一步是确保页面使用适当的HTTP请求动作,具体的说,在Spring SecurityCSRF防御使用之前,我们需要确保我们的应用使用PATCH,PUT,DELETE动作发起请求,这不是Spring Security支持的局限性,而是正确的CSRF防御必要的要求。
配置CSRF防御信息
如果你使用了XML配置,我们需要使用<csrf>标签:
<http> ... <csrf> /<http>
CSRF防御可以使用基本的JAVA配置实现,如果你不希望使用CSRF防御,对应的java配置如下:
@EnableWebSecurity@Configurationpublic class WebSecurityConfig extends WebSecurityConfigurerAdapter { @Override protected void configure(HttpSecurity http) throws Exception { http .csrf().disable() ...; }}
包含CSRF Token信息
Form提交
如果页面使用了Spring MVC的
如果使用spring MVC
最后一个步骤就是确保你已经将CSRF Token信息包含进PATCH,POST,PUT和DELETE方法中。我们可以使用请求中的”_csrf”属性,获取当前的CsrfToken信息。JSP页面实例如下:
AJAX请求
如果你使用JSON数据,我们不能通过添加HTTP参数的形式提交CSRF Token信息,取而代之,我们可以将Token信息放入HTTP头部信息中。一个典型的模式就是将CSRF Token信息添加到META标签中。
... ...
然后将Token信息包含到所有的AJAX请求中,如果你使用了Jquery,可以使用下面的方式:
$(function () { var token = $("meta[name='_csrf']").attr("content"); var header = $("meta[name='_csrf_header']").attr("content"); $(document).ajaxSend(function(e, xhr, options) { xhr.setRequestHeader(header, token); });});
5.CSRF注意事项
当使用防御CSRF时需要注意的一些事项:
超时问题
CSRFToken信息在Cookie中的有效期
有人可能会问你为什么CsrfToken没有存储到Cookie中,这是因为有一个已知的漏洞:Header头部信息可以被其他主机修改。另一个缺陷就是如果已经移除了header的状态信息(有效期),并且 有些事情已经妥协,你将失去强制终止Token的能力。(Another disadvantage is that by removing the state (i.e. the timeout) you lose the ability to forcibly terminate the token if something got compromised.)
一个问题就是CSRF Token随机值存在于HttpSession中,知道HttpSession失效。你配置的AccessDeniedHandler将抛出一个InvalidCsrfTokenException异常。如果你使用默认的AccessDeniedHandler,浏览器会出现403 错误,并跳转到错误页面。
解决用户Session超时问题,我们可以使用一段JavaScript代码让用户知道他们的session即将失效。用户可以通过点击按钮,重新获取Session。
要不然,我们可以自定义一个AccessDeniedHandler处理InvalidCsrfTokenException
例如,<access-denied-handler>自定义处理AccessDeniedHandler涉及到XML文件配置(<access-denied-handler>)和Java代码:/<access-denied-handler>/<access-denied-handler>
package org.springframework.security.config.annotation.web.configurers;
import java.io.IOException;
import javax.servlet.ServletException;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.security.access.AccessDeniedException;
import org.springframework.security.config.annotation.BaseSpringSpec;
import org.springframework.security.config.annotation.web.builders.HttpSecurity;
import org.springframework.security.config.annotation.web.configuration.BaseWebConfig;
import org.springframework.security.web.access.AccessDeniedHandler;
import org.springframework.security.web.access.ExceptionTranslationFilter;
/**
* Tests to verify that all the functionality of <access-denied-handler> attributes is present/<access-denied-handler>
*
* @author Rob Winch
*
*/
public class NamespaceHttpAccessDeniedHandlerTests extends BaseSpringSpec {
def "http/access-denied-handler@error-page"() {
when:
loadConfig(AccessDeniedPageConfig)
then:
findFilter(ExceptionTranslationFilter).accessDeniedHandler.errorPage == "/AccessDeniedPageConfig"
}
@Configuration
static class AccessDeniedPageConfig extends BaseWebConfig {
protected void configure(HttpSecurity http) {
http.
exceptionHandling()
.accessDeniedPage("/AccessDeniedPageConfig")
}
}
def "http/access-denied-handler@ref"() {
when:
loadConfig(AccessDeniedHandlerRefConfig)
then:
findFilter(ExceptionTranslationFilter).accessDeniedHandler.class == AccessDeniedHandlerRefConfig.CustomAccessDeniedHandler
}
@Configuration
static class AccessDeniedHandlerRefConfig extends BaseWebConfig {
protected void configure(HttpSecurity http) {
CustomAccessDeniedHandler accessDeniedHandler = new CustomAccessDeniedHandler()
http.
exceptionHandling()
.accessDeniedHandler(accessDeniedHandler)
}
static class CustomAccessDeniedHandler implements AccessDeniedHandler {
public void handle(HttpServletRequest request,
HttpServletResponse response,
AccessDeniedException accessDeniedException)
throws IOException, ServletException {
}
}
}
}
登录问题:
为了防御伪造的请求日志,form请求日志也必须要防御CSRF攻击。因为CSRFToken信息存放在HttpSession中,所以登录之后HttpSession立即就会被创建。在REST 全/无状态架构中,会出现错误信息,原因是要实现切实的防御措施,CSRFToken状态信息是必须要有的。没有CSRFToken信息或者一个CSRFToken失效,我们几乎不能做任何事情。实际上,CSRF Token是很小的信息,但是却又这不可忽视的影响。
退出问题:
添加CSRF防御,意味着LogoutFilter过滤器只能接受HTTP POST请求。并且退出操作也需要一个Token信息,防止恶意用户强制用户退出。
一个方法就是使用form表单退出。如果你真的喜欢使用一个链接,你可以使用JavaScript提交一个POST请求。因为带JavaScript的浏览器可能会被禁止,你可以链接到一个退出确认页面提交POST请求。
HiddenHttpMethodFilter
HiddenHttpMethodFilter应该放在Spring Security filter之前,事实上这是可以的,但是当防止CSRF攻击,它可能产生其他的影响。
我们可以注意到HiddenHttpMethodFilter仅仅继承了HTTP POST请求方法,所以这确实不太可能产生任何现实的问题。然而这仍旧是最好的方法。
重写默认值
Spring Security目标就是提供一个默认值防止用户被CSRF攻击。这并不意味着用户被强迫接受所有的默认值。
例如我们提供一个自定义的CsrfTokenRepository继承保存CSRFToken的方法。
你也可以自定义一个RequestMatcher 实现CSRF防御,简单的说如果Spring Security's CSRF 防御不能完全实现你想要的结果,所以你可以自定义这些行为。
6、结论
你现在应该对Spring Security防御CSRF攻击有了一定的认识。
文章翻译自http://spring.io/blog/2013/08/21/spring-security-3-2-0-rc1-highlights-csrf-protection/和http://docs.spring.io/spring-security/site/docs/current/reference/htmlsingle/#csrf-attacks
閱讀更多 IT碼將 的文章