CSRF簡介——摘抄自《Spring實戰(第4版)》
我們可以回憶一下,當一個POST請求提交到“/spittles”上時,SpittleController將會為用戶創建一個新的Spittle對象。但是,如果這個POST請求來源於其他站點的話,會怎么樣呢?如果在其他站點提交如下表單,這個POST請求會造成什么樣的結果呢?假設你禁不住獲得一輛新汽車的誘惑,點擊了按鈕——那么你將會提交表單到如下地址http://www.spittr.com/spittles。如果你已經登錄到了spittr.com,那么這就會廣播一條消息,讓每個人都知道你做了一件蠢事。這是跨站請求偽造(cross-site request forgery,CSRF)的一個簡單樣例。簡單來講,如果一個站點欺騙用戶提交請求到其他服務器的話,就會發生CSRF攻擊,這可能會帶來消極的后果。盡管提交“I’m stupid!”這樣的信息到微博站點算不上什么CSRF攻擊的最糟糕場景,但是你可以很容易想到更為嚴重的攻擊情景,它可能會對你的銀行賬號執行難以預期的操作。
從Spring Security 3.2開始,默認就會啟用CSRF防護。實際上,除非你采取行為處理CSRF防護或者將這個功能禁用,否則的話,在應用中提交表單時,你可能會遇到問題。
Spring Security通過一個同步token的方式來實現CSRF防護的功能。它將會攔截狀態變化的請求(例如,非GET、HEAD、OPTIONS和TRACE的請求)並檢查CSRF token。如果請求中不包含CSRF token的話,或者token不能與服務器端的token相匹配,請求將會失敗,並拋出CsrfException異常。這意味着在你的應用中,所有的表單必須在一個“_csrf”域中提交token,而且這個token必須要與服務器端計算並存儲的token一致,這樣的話當表單提交的時候,才能進行匹配。
好消息是,Spring Security已經簡化了將token放到請求的屬性中這一任務。
一、Freemarker模板集成Spring Security-CSRF防止功能
1.Spring Security配置防止CSRF為開啟狀態,默認情況下該功能是開啟的,調用disable()方法可以禁止。
@Configuration @EnableWebSecurity public class SecurityConfig extends WebSecurityConfigurerAdapter { /** * HTTP請求處理 */ @Override protected void configure(HttpSecurity http) throws Exception { String doUrl = "/**/*.do"; http .formLogin().loginPage("/user/login.do") .defaultSuccessUrl("/free/list.do")//啟用FORM登錄 .and().logout().logoutRequestMatcher(new AntPathRequestMatcher("/logout","GET")) .and().authorizeRequests().antMatchers("/user/login.do").permitAll()//登錄頁允許所有人訪問 .and().portMapper().http(8898).mapsTo(8443) //添加端口映射,做測試用 .and().authorizeRequests().antMatchers(doUrl).authenticated() .and().requiresChannel().antMatchers("/free/**",doUrl).requiresSecure() .and().requiresChannel().antMatchers(doUrl).requiresInsecure() .and().httpBasic(); //.and().csrf().disable(); //禁用CSRF }
2.Freemarker模板綁定_csrf 值
<#include "/templates/_base.ftl"/> <@layout;section> <#if section="title"> 用戶登錄 <#elseif section="css"> <#elseif section="content"> <div class="width:400px;height:300px;"> <h3>用戶登錄</h3> <form name="f" action="/stest/user/login.do" method="POST"> <input name="${_csrf.parameterName}" type="hidden" value="${_csrf.token}"> <table> <tbody> <tr><td>User:</td><td><input type="text" name="username" value=""></td></tr> <tr><td>Password:</td><td><input type="password" name="password"></td></tr> <tr><td colspan="2"><input name="submit" type="submit" value="Login"></td></tr> </tbody> </table> <p>${msg!}</p> </form> </div> <#elseif section="scripts"> </#if> </@layout>
疑問解析:屬性_csrf是怎么產生的呢? 根據調試發現,這個屬性是在org.springframework.security.web.csrf.CsrfFilter過濾器中賦值到request的屬性集合中的。因此可以在視圖綁定過程中,直接獲取。源碼截圖如下

                3.查看登錄頁面輸出的csrf-Token值,已經成功賦值。如果我們隨便修改一下這個token值,然后post請求到服務端,會發現報403錯誤,請求被阻止。說明配置已經成功生效,防止了請求的偽造。
    
     
    
二、當開啟CSRF后,原來以Get方式,調用/logout,退出登錄狀態的功能失效了,跳轉后報404錯誤。
1、查看源碼,發現框架方法里做了備注。大概意思就是開啟CSRF后,logout方法需要以post方式提交。或者調用logoutRequestMatcher方法,顯示設置/logout請求為GET方法
          
2、用logoutRequestMatcher設置logout為get請求來解決404報錯問題。POST方式比較簡單這里就不在贅述。當然,實際項目中,最好選擇以post方式退出系統。
http .formLogin().loginPage("/user/login.do") .defaultSuccessUrl("/free/list.do")//啟用FORM登錄 .and().logout().logoutRequestMatcher(new AntPathRequestMatcher("/logout","GET"))
至此CSRF防止方案示例就講完了。在項目中,對於post請求都需要注意提交_csrf到服務端。希望對你有所幫助,歡迎留言交流。
