Spring-Security+Freemarker 開啟跨域請求偽造防護功能


     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到服務端。希望對你有所幫助,歡迎留言交流。

 


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM