04.27 Spring Security CSRF防御之道(2)

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


分享到:


相關文章: