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


分享到:


相關文章: