鬆哥手把手教你在 SpringBoot 中防護 CSRF 攻擊!so easy!

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

這是一種很是常見的 Web 攻擊方式,實際上是很好防護的,可是因爲常常被不少開發者忽略,進而致使不少網站實際上都存在 CSRF 攻擊的安全隱患。前端

今天鬆哥就來和你們聊一聊什麼是 CSRF 攻擊以及 CSRF 攻擊該如何防護。java

本文是本系列第 18 篇,閱讀本系列前面文章有助於更好的理解本文:jquery

  1. 挖一個大坑,Spring Security 開搞!
  2. 鬆哥手把手帶你入門 Spring Security,別再問密碼怎麼解密了
  3. 手把手教你定製 Spring Security 中的表單登陸
  4. Spring Security 作先後端分離,咱就別作頁面跳轉了!通通 JSON 交互
  5. Spring Security 中的受權操做原來這麼簡單
  6. Spring Security 如何將用戶數據存入數據庫?
  7. Spring Security+Spring Data Jpa 強強聯手,安全管理只有更簡單!
  8. Spring Boot + Spring Security 實現自動登陸功能
  9. Spring Boot 自動登陸,安全風險要怎麼控制?
  10. 在微服務項目中,Spring Security 比 Shiro 強在哪?
  11. SpringSecurity 自定義認證邏輯的兩種方式(高級玩法)
  12. Spring Security 中如何快速查看登陸用戶 IP 地址等信息?
  13. Spring Security 自動踢掉前一個登陸用戶,一個配置搞定!
  14. Spring Boot + Vue 先後端分離項目,如何踢掉已登陸用戶?
  15. Spring Security 自帶防火牆!你都不知道本身的系統有多安全!
  16. 什麼是會話固定攻擊?Spring Boot 中要如何防護會話固定攻擊?
  17. 集羣化部署,Spring Security 要如何處理 session 共享?

1.CSRF原理

想要防護 CSRF 攻擊,那咱們得先搞清楚什麼是 CSRF 攻擊,鬆哥經過下面一張圖,來和你們梳理 CSRF 攻擊流程:git

其實這個流程很簡單:github

  1. 假設用戶打開了招商銀行網上銀行網站,而且登陸。
  2. 登陸成功後,網上銀行會返回 Cookie 給前端,瀏覽器將 Cookie 保存下來。
  3. 用戶在沒有登出網上銀行的狀況下,在瀏覽器裏邊打開了一個新的選項卡,而後又去訪問了一個危險網站。
  4. 這個危險網站上有一個超連接,超連接的地址指向了招商銀行網上銀行。
  5. 用戶點擊了這個超連接,因爲這個超連接會自動攜帶上瀏覽器中保存的 Cookie,因此用戶不知不覺中就訪問了網上銀行,進而可能給本身形成了損失。

CSRF 的流程大體就是這樣,接下來鬆哥用一個簡單的例子和小夥伴們展現一下 CSRF 究竟是怎麼回事。web

2.CSRF實踐

接下來,我建立一個名爲 csrf-1 的 Spring Boot 項目,這個項目至關於咱們上面所說的網上銀行網站,建立項目時引入 Web 和 Spring Security 依賴,以下:spring

建立成功後,方便起見,咱們直接將 Spring Security 用戶名/密碼 配置在 application.properties 文件中:數據庫

spring.security.user.name=javaboy
spring.security.user.password=123

而後咱們提供兩個測試接口:小程序

@RestController
public class HelloController {
    @PostMapping("/transfer")
    public void transferMoney(String name, Integer money) {
        System.out.println("name = " + name);
        System.out.println("money = " + money);
    }
    @GetMapping("/hello")
    public String hello() {
        return "hello";
    }
}

假設 /transfer 是一個轉帳接口(這裏是假設,主要是給你們演示 CSRF 攻擊,真實的轉帳接口比這複雜)。

最後咱們還須要配置一下 Spring Security,由於 Spring Security 中默認是能夠自動防護 CSRF 攻擊的,因此咱們要把這個關閉掉:

@Configuration
public class SecurityConfig extends WebSecurityConfigurerAdapter {
    @Override
    protected void configure(HttpSecurity http) throws Exception {
        http.authorizeRequests().anyRequest().authenticated()
                .and()
                .formLogin()
                .and()
                .csrf()
                .disable();
    }
}

配置完成後,咱們啓動 csrf-1 項目。

接下來,咱們再建立一個 csrf-2 項目,這個項目至關因而一個危險網站,爲了方便,這裏建立時咱們只須要引入 web 依賴便可。

項目建立成功後,首先修改項目端口:

server.port=8081

而後咱們在 resources/static 目錄下建立一個 hello.html ,內容以下:

<body>
<form action="http://localhost:8080/transfer" method="post">
    <input type="hidden" value="javaboy" name="name">
    <input type="hidden" value="10000" name="money">
    <input type="submit" value="點擊查看美女圖片">
</form>
</body>

這裏有一個超連接,超連接的文本是點擊查看美女圖片,當你點擊了超連接以後,會自動請求 http://localhost:8080/transfer 接口,同時隱藏域還攜帶了兩個參數。

配置完成後,就能夠啓動 csrf-2 項目了。

接下來,用戶首先訪問 csrf-1 項目中的接口,在訪問的時候須要登陸,用戶就執行了登陸操做,訪問完整後,用戶並無執行登出操做,而後用戶訪問 csrf-2 中的頁面,看到了超連接,好奇這美女到底長啥樣,一點擊,結果錢就被人轉走了。

3.CSRF防護

先來講說防護思路。

CSRF 防護,一個核心思路就是在前端請求中,添加一個隨機數。

由於在 CSRF 攻擊中,黑客網站實際上是不知道用戶的 Cookie 具體是什麼的,他是讓用戶本身發送請求到網上銀行這個網站的,由於這個過程會自動攜帶上 Cookie 中的信息。

因此咱們的防護思路是這樣:用戶在訪問網上銀行時,除了攜帶 Cookie 中的信息以外,還須要攜帶一個隨機數,若是用戶沒有攜帶這個隨機數,則網上銀行網站會拒絕該請求。黑客網站誘導用戶點擊超連接時,會自動攜帶上 Cookie 中的信息,可是卻不會自動攜帶隨機數,這樣就成功的避免掉 CSRF 攻擊了。

Spring Security 中對此提供了很好的支持,咱們一塊兒來看下。

3.1 默認方案

Spring Security 中默認實際上就提供了 csrf 防護,可是須要開發者作的事情比較多。

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

項目建立成功後,咱們仍是在 application.properties 中配置用戶名/密碼:

spring.security.user.name=javaboy
spring.security.user.password=123

接下來,咱們提供一個測試接口:

@Controller
public class HelloController {
    @PostMapping("/hello")
    @ResponseBody
    public String hello() {
        return "hello";
    }
}

注意,這個測試接口是一個 POST 請求,由於默認狀況下,GET、HEAD、TRACE 以及 OPTIONS 是不須要驗證 CSRF 攻擊的。

而後,咱們在 resources/templates 目錄下,新建一個 thymeleaf 模版,以下:

<body>
<form action="/hello" method="post">
    <input type="hidden" th:value="${_csrf.token}" th:name="${_csrf.parameterName}">
    <input type="submit" value="hello">
</form>
</body>

注意,在發送 POST 請求的時候,還額外攜帶了一個隱藏域,隱藏域的 key 是 ${_csrf.parameterName},value 則是 ${_csrf.token}

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

接下來給前端 hello.html 頁面添加一個控制器,以下:

@GetMapping("/hello")
public String hello2() {
    return "hello";
}

添加完成後,啓動項目,咱們訪問 hello 頁面,在訪問時候,須要先登陸,登陸成功以後,咱們能夠看到登陸請求中也多了一個參數,以下:

能夠看到,這裏也多了 _csrf 參數。

這裏咱們用了 Spring Security 的默認登陸頁面,若是你們使用自定義登陸頁面,能夠參考上面 hello.html 的寫法,經過一個隱藏域傳遞 _csrf 參數。

訪問到 hello 頁面以後,再去點擊按鈕,就能夠訪問到 hello 接口了。

小夥伴們能夠自行嘗試在 hello.html 頁面中,去掉 _csrf 參數,看看訪問 hello 接口的效果。

這是 Spring Security 中默認的方案,經過 Model 將相關的數據帶到前端來。

若是你的項目是先後端不分項目,這種方案就能夠了,若是你的項目是先後端分離項目,這種方案很明顯不夠用。

3.2 先後端分離方案

若是是先後端分離項目,Spring Security 也提供瞭解決方案。

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

@Configuration
public class SecurityConfig extends WebSecurityConfigurerAdapter {
    @Override
    protected void configure(HttpSecurity http) throws Exception {
        http.authorizeRequests().anyRequest().authenticated()
                .and()
                .formLogin()
                .and()
                .csrf().csrfTokenRepository(CookieCsrfTokenRepository.withHttpOnlyFalse());
    }
}

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

  1. 黑客網站根本不知道你的 Cookie 裏邊存的啥,他也不須要知道,由於 CSRF 攻擊是瀏覽器自動攜帶上 Cookie 中的數據的。
  2. 咱們將服務端生成的隨機數放在 Cookie 中,前端須要從 Cookie 中本身提取出來 _csrf 參數,而後拼接成參數傳遞給後端,單純的將 Cookie 中的數據傳到服務端是沒用的。

理解透了上面兩點,你就會發現 _csrf 放在 Cookie 中是沒有問題的,可是你們注意,配置的時候咱們經過 withHttpOnlyFalse 方法獲取了 CookieCsrfTokenRepository 的實例,該方法會設置 Cookie 中的 HttpOnly 屬性爲 false,也就是容許前端經過 js 操做 Cookie(不然你就沒有辦法獲取到 _csrf)。

配置完成後,重啓項目,此時咱們就發現返回的 Cookie 中多了一項:

接下來,咱們經過自定義登陸頁面,來看看前端要如何操做。

首先咱們在 resources/static 目錄下新建一個 html 頁面叫作 login.html:

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>Title</title>
    <script src="js/jquery.min.js"></script>
    <script src="js/jquery.cookie.js"></script>
</head>
<body>
<div>
    <input type="text" id="username">
    <input type="password" id="password">
    <input type="button" value="登陸" id="loginBtn">
</div>
<script>
    $("#loginBtn").click(function () {
        let _csrf = $.cookie('XSRF-TOKEN');
        $.post('/login.html',{username:$("#username").val(),password:$("#password").val(),_csrf:_csrf},function (data) {
            alert(data);
        })
    })
</script>
</body>
</html>

這段 html 我給你們解釋下:

  1. 首先引入 jquery 和 jquery.cookie ,方便咱們一會操做 Cookie。
  2. 定義三個 input,前兩個是用戶名和密碼,第三個是登陸按鈕。
  3. 點擊登陸按鈕以後,咱們先從 Cookie 中提取出 XSRF-TOKEN,這也就是咱們要上傳的 csrf 參數。
  4. 經過一個 POST 請求執行登陸操做,注意攜帶上 _csrf 參數。

服務端咱們也稍做修改,以下:

@Configuration
public class SecurityConfig extends WebSecurityConfigurerAdapter {
    @Override
    public void configure(WebSecurity web) throws Exception {
        web.ignoring().antMatchers("/js/**");
    }

    @Override
    protected void configure(HttpSecurity http) throws Exception {
        http.authorizeRequests().anyRequest().authenticated()
                .and()
                .formLogin()
                .loginPage("/login.html")
                .successHandler((req,resp,authentication)->{
                    resp.getWriter().write("success");
                })
                .permitAll()
                .and()
                .csrf().csrfTokenRepository(CookieCsrfTokenRepository.withHttpOnlyFalse());
    }
}

一方面這裏給 js 文件放行。

另外一方面配置一下登陸頁面,以及登陸成功的回調,這裏簡單期間,登陸成功的回調我就給一個字符串就能夠了。你們感興趣的話,能夠查看本系列前面文章,有登陸成功後回調的詳細解釋。

OK,全部事情作完以後,咱們訪問 login.html 頁面,輸入用戶名密碼進行登陸,結果以下:

能夠看到,咱們的 _csrf 配置已經生效了。

小夥伴們能夠自行嘗試從登陸參數中去掉 _csrf,而後再看看效果。

4.小結

好了,今天主要和小夥伴們介紹了 csrf 攻擊以及如何防護的問題。你們看到,csrf 攻擊主要是藉助了瀏覽器默認發送 Cookie 的這一機制,因此若是你的前端是 App、小程序之類的應用,不涉及瀏覽器應用的話,其實能夠忽略這個問題,若是你的前端包含瀏覽器應用的話,這個問題就要認真考慮了。

好了 ,本文就說到這裏,本文相關案例我已經上傳到 GitHub ,你們能夠自行下載:https://github.com/lenve/spring-security-samples

好啦,不知道小夥伴們有沒有 GET 到呢?若是有收穫,記得點個在看鼓勵下鬆哥哦~

相關文章
相關標籤/搜索