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碼將 的文章