SpringBoot 如何防禦 CSRF 攻擊?

作者 |江南一點雨     責編 | 歐陽姝黎

CSRF 就是跨域請求偽造,英文全稱是 Cross Site Request Forgery。

這是一種非常常見的 Web 攻擊方式,其實是很好防禦的,但是由於經常被很多開發者忽略,進而導致很多網站實際上都存在 CSRF 攻擊的安全隱患。

今天就來和大家聊一聊什麼是 CSRF 攻擊以及 CSRF 攻擊該如何防禦。

CSRF原理

想要防禦 CSRF 攻擊,那我們得先搞清楚什麼是 CSRF 攻擊,松哥透過下面一張圖,來和大家梳理 CSRF 攻擊流程:

SpringBoot 如何防禦 CSRF 攻擊?

其實這個流程很簡單:

假設使用者打開了招商銀行網上銀行網站,並且登入。

登入成功後,網上銀行會返回 Cookie 給前端,瀏覽器將 Cookie 儲存下來。

使用者在沒有登出網上銀行的情況下,在瀏覽器裡邊打開了一個新的選項卡,然後又去訪問了一個危險網站。

這個危險網站上有一個超連結,超連結的地址指向了招商銀行網上銀行。

使用者點選了這個超連結,由於這個超連結會自動攜帶上瀏覽器中儲存的 Cookie,所以使用者不知不覺中就訪問了網上銀行,進而可能給自己造成了損失。

CSRF 的流程大致就是這樣,接下來松哥用一個簡單的例子和小夥伴們展示一下 CSRF 到底是怎麼回事。

CSRF實踐

接下來,我建立一個名為 csrf-1 的 Spring Boot 專案,這個專案相當於我們上面所說的網上銀行網站,建立專案時引入 Web 和 Spring Security 依賴,如下:

SpringBoot 如何防禦 CSRF 攻擊?

建立成功後,方便起見,我們直接將 Spring Security 使用者名稱/密碼 配置在 application。properties 檔案中:

然後我們提供兩個測試介面:

假設 /transfer 是一個轉賬介面(這裡是假設,主要是給大家演示 CSRF 攻擊,真實的轉賬介面比這複雜)。

最後我們還需要配置一下 Spring Security,因為 Spring Security 中預設是可以自動防禦 CSRF 攻擊的,所以我們要把這個關閉掉:

配置完成後,我們啟動 csrf-1 專案。

接下來,我們再建立一個 csrf-2 專案,這個專案相當於是一個危險網站,為了方便,這裡建立時我們只需要引入 web 依賴即可。

專案建立成功後,首先修改專案埠:

然後我們在 resources/static 目錄下建立一個 hello。html ,內容如下:

配置完成後,就可以啟動 csrf-2 專案了。

接下來,使用者首先訪問 csrf-1 專案中的介面,在訪問的時候需要登入,使用者就執行了登入操作,訪問完整後,使用者並沒有執行登出操作,然後使用者訪問 csrf-2 中的頁面,看到了超連結,好奇這美女到底長啥樣,一點選,結果錢就被人轉走了。

CSRF防禦

先來說說防禦思路。

CSRF 防禦,一個核心思路就是在前端請求中,新增一個隨機數。

因為在 CSRF 攻擊中,駭客網站其實是不知道使用者的 Cookie 具體是什麼的,他是讓使用者自己傳送請求到網上銀行這個網站的,因為這個過程會自動攜帶上 Cookie 中的資訊。

所以我們的防禦思路是這樣:使用者在訪問網上銀行時,除了攜帶 Cookie 中的資訊之外,還需要攜帶一個隨機數,如果使用者沒有攜帶這個隨機數,則網上銀行網站會拒絕該請求。駭客網站誘導使用者點選超連結時,會自動攜帶上 Cookie 中的資訊,但是卻不會自動攜帶隨機數,這樣就成功的避免掉 CSRF 攻擊了。

Spring Security 中對此提供了很好的支援,我們一起來看下。

3。1 預設方案

Spring Security 中預設實際上就提供了 csrf 防禦,但是需要開發者做的事情比較多。

首先我們來建立一個新的 Spring Boot 工程,建立時引入 Spring Security、Thymeleaf 和 web 依賴。

SpringBoot 如何防禦 CSRF 攻擊?

專案建立成功後,我們還是在 application。properties 中配置使用者名稱/密碼:

接下來,我們提供一個測試介面:

注意,這個測試介面是一個 POST 請求,因為預設情況下,GET、HEAD、TRACE 以及 OPTIONS 是不需要驗證 CSRF 攻擊的。

然後,我們在 resources/templates 目錄下,新建一個 thymeleaf 模版,如下:

注意,在傳送 POST 請求的時候,還額外攜帶了一個隱藏域,隱藏域的 key 是 $,value 則是 $。

這兩個值服務端會自動帶過來,我們只需要在前端渲染出來即可。

接下來給前端 hello。html 頁面新增一個控制器,如下:

新增完成後,啟動專案,我們訪問 hello 頁面,在訪問時候,需要先登入,登入成功之後,我們可以看到登入請求中也多了一個引數,如下:

可以看到,這裡也多了 _csrf 引數。

這裡我們用了 Spring Security 的預設登入頁面,如果大家使用自定義登入頁面,可以參考上面 hello。html 的寫法,透過一個隱藏域傳遞 _csrf 引數。

訪問到 hello 頁面之後,再去點選按鈕,就可以訪問到 hello 介面了。

小夥伴們可以自行嘗試在 hello。html 頁面中,去掉 _csrf 引數,看看訪問 hello 介面的效果。

這是 Spring Security 中預設的方案,透過 Model 將相關的資料帶到前端來。

如果你的專案是前後端不分專案,這種方案就可以了,如果你的專案是前後端分離專案,這種方案很明顯不夠用。

3。2 前後端分離方案

如果是前後端分離專案,Spring Security 也提供瞭解決方案。

這次不是將 _csrf 放在 Model 中返回前端了,而是放在 Cookie 中返回前端,配置方式如下:

有小夥伴可能會說放在 Cookie 中不是又被駭客網站盜用了嗎?其實不會的,大家注意如下兩個問題:

駭客網站根本不知道你的 Cookie 裡邊存的啥,他也不需要知道,因為 CSRF 攻擊是瀏覽器自動攜帶上 Cookie 中的資料的。

我們將服務端生成的隨機數放在 Cookie 中,前端需要從 Cookie 中自己提取出來 _csrf 引數,然後拼接成引數傳遞給後端,單純的將 Cookie 中的資料傳到服務端是沒用的。

理解透了上面兩點,你就會發現 _csrf 放在 Cookie 中是沒有問題的,但是大家注意,配置的時候我們透過 withHttpOnlyFalse 方法獲取了 CookieCsrfTokenRepository 的例項,該方法會設定 Cookie 中的 HttpOnly 屬性為 false,也就是允許前端透過 js 操作 Cookie(否則你就沒有辦法獲取到 _csrf)。

配置完成後,重啟專案,此時我們就發現返回的 Cookie 中多了一項:

接下來,我們透過自定義登入頁面,來看看前端要如何操作。

首先我們在 resources/static 目錄下新建一個 html 頁面叫做 login。html:

這段 html 我給大家解釋下:

首先引入 jquery 和 jquery。cookie ,方便我們一會操作 Cookie。

定義三個 input,前兩個是使用者名稱和密碼,第三個是登入按鈕。

點選登入按鈕之後,我們先從 Cookie 中提取出 XSRF-TOKEN,這也就是我們要上傳的 csrf 引數。

透過一個 POST 請求執行登入操作,注意攜帶上 _csrf 引數。

服務端我們也稍作修改,如下:

一方面這裡給 js 檔案放行。

另一方面配置一下登入頁面,以及登入成功的回撥,這裡簡單期間,登入成功的回撥我就給一個字串就可以了。大家感興趣的話,可以檢視本系列前面文章,有登入成功後回撥的詳細解釋。

OK,所有事情做完之後,我們訪問 login。html 頁面,輸入使用者名稱密碼進行登入,結果如下:

可以看到,我們的 _csrf 配置已經生效了。

小夥伴們可以自行嘗試從登入引數中去掉 _csrf,然後再看看效果。

小結

好了,今天主要和小夥伴們介紹了 csrf 攻擊以及如何防禦的問題。大家看到,csrf 攻擊主要是藉助了瀏覽器預設傳送 Cookie 的這一機制,所以如果你的前端是 App、小程式之類的應用,不涉及瀏覽器應用的話,其實可以忽略這個問題,如果你的前端包含瀏覽器應用的話,這個問題就要認真考慮了。

好了 ,本文就說到這裡,本文相關案例我已經上傳到 GitHub ,大家可以自行下載:https://github。com/lenve/spring-security-samples

生於2001年的《程式設計師》曾陪伴了無數開發者成長,影響了一代又一代的中國技術人。時隔20年,《新程式設計師》帶著全球技術大師深邃思考、優秀開發者技術創造等深度內容回來了!同時將全方位為所有開發者呈現國內外核心技術生態體系全景圖。掃描下方小程式碼即可立即訂閱!

SpringBoot 如何防禦 CSRF 攻擊?