首頁  >  文章  >  Java  >  SpringBoot防禦CSRF攻擊的流程及原理是什麼

SpringBoot防禦CSRF攻擊的流程及原理是什麼

WBOY
WBOY轉載
2023-05-12 21:13:04841瀏覽

CSRF 原理

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

SpringBoot防禦CSRF攻擊的流程及原理是什麼

其實這個流程很簡單:
1.假設使用者開啟了招商網路銀行網站,並且登入。
2.登入成功後,網路銀行會回到Cookie給前端,瀏覽器將Cookie儲存下來。
3.用戶在沒有登出網路銀行的情況下,在瀏覽器裡打開了一個新的選項卡,然後又去訪問了一個危險網站。
4.這個危險網站上有一個超鏈接,超鏈接的地址指向了招商網上銀行。
4.用戶點擊了這個鏈接,由於這個超鏈接會自動攜帶上瀏覽器中保存的Cookie,所以用戶不知不覺中就訪問了網上銀行,進而可能給自己造成了損失。

CSRF的流程大致上就是這樣,接下來用一個簡單的例子來展示CSRF到底是怎麼一回事。

CSRF實踐

1.我創建一個名為csrf-mry 的Spring Boot 項目,這個項目相當於我們上面所說的網上銀行網站,創建項目時引入Web 和Spring Security依賴,如下:

<dependencies>
	<dependency>
		<groupId>org.springframework.boot</groupId>
		<artifactId>spring-boot-starter-security</artifactId>
	</dependency>
	<dependency>
		<groupId>org.springframework.boot</groupId>
		<artifactId>spring-boot-starter-web</artifactId>
	</dependency>
</dependencies>

2.創建成功後,方便起見,我們直接將Spring Security 用戶名/密碼配置在application.properties 文件中:

server.port= 8866
spring.security.user.name=javaboy
spring.security.user.password=123

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

package com.mry.csrf.controller;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RestController;
@RestController
public class CsrfDemoController {
    @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 攻擊,真實的轉帳介面比這複雜)。

4.我們還需要設定 Spring Security,因為 Spring Security 中預設是可以自動防禦 CSRF 攻擊的,所以我們要把這個關閉掉。

package com.mry.csrf.config;
import org.springframework.context.annotation.Configuration;
import org.springframework.security.config.annotation.web.builders.HttpSecurity;
import org.springframework.security.config.annotation.web.configuration.WebSecurityConfigurerAdapter;
@Configuration
public class SecurityConfig extends WebSecurityConfigurerAdapter {
    @Override
    protected void configure(HttpSecurity http) throws Exception {
        http.authorizeRequests().anyRequest().authenticated()
                .and()
                .formLogin()
                .and()
                .csrf()
                .disable();
    }
}

設定完成後,我們啟動 csrf-simulate-web 專案。

5.我們再建立一個 csrf-loophole-web 項目,這個項目相當於是一個危險網站,為了方便,這裡創建時我們只需要引入 web 依賴即可。
專案建立成功後,先修改專案連接埠:

server.port= 8855

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

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>Title</title>
</head>
<body>
    <form action="http://localhost:8866/transfer" method="post">
        <input type="hidden" value="javaboy" name="name">
        <input type="hidden" value="10000" name="money">
        <input type="submit" value="点击查看美女图片">
    </form>
</body>
</html>

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

設定完成後,就可以啟動 csrf-loophole-web 專案了。

接下來,用戶首先訪問csrf-simulate-web 專案中的接口,在訪問的時候需要登錄,用戶就執行了登錄操作,訪問完整後,用戶並沒有執行登出操作,然後用戶造訪csrf-loophole-web 中的頁面,看到了超鏈接,好奇這美女到底長啥樣,一點擊,結果錢就被人轉走了。

CSRF防禦

先來說說防禦思路。

CSRF 防禦,一個核心想法就是在前端請求中,加入一個隨機數。

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

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

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

前後端不分離方案

Spring Security 中預設實際上就提供了 csrf 防禦,但是需要開發者做的事情比較多。
首先我們來創建一個新的 Spring Boot 工程,創建時引入 Spring Security、Thymeleaf 和 web 依賴。

1.pom資訊

<dependencies>
	<dependency>
		<groupId>org.springframework.boot</groupId>
		<artifactId>spring-boot-starter-web</artifactId>
	</dependency>
	<dependency>
		<groupId>org.springframework.boot</groupId>
		<artifactId>spring-boot-starter-security</artifactId>
	</dependency>
	<dependency>
		<groupId>org.springframework.boot</groupId>
		<artifactId>spring-boot-starter-thymeleaf</artifactId>
	</dependency>
	<dependency>
		<groupId>org.springframework.boot</groupId>
		<artifactId>spring-boot-starter-test</artifactId>
		<scope>test</scope>
	</dependency>
</dependencies>

2.專案建立成功後,我們還是在application.properties 中設定使用者名稱/密碼

spring.security.user.name=mry
spring.security.user.password=123456

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

package com.mry.csrf.controller;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.ResponseBody;
import org.springframework.web.bind.annotation.RestController;
@RestController
public class SecurityCsrfController {
    @PostMapping("/hello")
    @ResponseBody
    public String hello() {
        return "hello";
    }
}

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

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

<!DOCTYPE html>
<!--导入thymeleaf的名称空间-->
<html lang="en" xmlns:th="http://www.thymeleaf.org">
<head>
    <meta charset="UTF-8">
    <title>Title</title>
</head>
<body>
    <form action="/hello" method="post">
        <input type="hidden" th:value="${_csrf.token}" th:name="${_csrf.parameterName}">
        <input type="submit" value="hello">
    </form>
</body>
</html>

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

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

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

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

6.添加完成後,啟動項目,我們訪問hello 頁面,在訪問時候,需要先登錄,登錄成功之後,我們可以看到登入請求中也多了一個參數

SpringBoot防禦CSRF攻擊的流程及原理是什麼

这里我们用了 Spring Security 的默认登录页面,如果大家使用自定义登录页面,可以参考上面 hello.html 的写法,通过一个隐藏域传递 _csrf 参数。

访问到 hello 页面之后,再去点击【hello】按钮,就可以访问到 hello 接口了。

SpringBoot防禦CSRF攻擊的流程及原理是什麼

这是 Spring Security 中默认的方案,通过 Model 将相关的数据带到前端来。

如果你的项目是前后端不分项目,这种方案就可以了,如果你的项目是前后端分离项目,这种方案很明显不够用。

前后端分离方案

如果是前后端分离项目,Spring Security 也提供了解决方案。
这次不是将 _csrf 放在 Model 中返回前端了,而是放在 Cookie 中返回前端,配置方式如下:

package com.mry.csrf.config;
import org.springframework.context.annotation.Configuration;
import org.springframework.security.config.annotation.web.builders.HttpSecurity;
import org.springframework.security.config.annotation.web.configuration.WebSecurityConfigurerAdapter;
import org.springframework.security.web.csrf.CookieCsrfTokenRepository;
@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 中多了一项:

SpringBoot防禦CSRF攻擊的流程及原理是什麼

接下来,我们通过自定义登录页面,来看看前端要如何操作。

首先我们在 resources/static 目录下新建一个 html 页面叫做 login.html:

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>Title</title>
    <script src="https://libs.baidu.com/jquery/2.1.4/jquery.min.js"></script>
    <script src="https://cdn.bootcss.com/jquery-cookie/1.4.1/jquery.cookie.min.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(&#39;XSRF-TOKEN&#39;);
        $.post(&#39;/login.html&#39;,{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 参数。

服务端我们也稍作修改,如下:

package com.mry.csrf.config;
import org.springframework.context.annotation.Configuration;
import org.springframework.security.config.annotation.web.builders.HttpSecurity;
import org.springframework.security.config.annotation.web.builders.WebSecurity;
import org.springframework.security.config.annotation.web.configuration.WebSecurityConfigurerAdapter;
import org.springframework.security.web.csrf.CookieCsrfTokenRepository;
@Configuration
public class SecurityConfig extends WebSecurityConfigurerAdapter {
    @Override
    public void configure(WebSecurity web) throws Exception {
        web.ignoring().antMatchers("/static/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 页面,输入用户名密码进行登录,结果如下:

SpringBoot防禦CSRF攻擊的流程及原理是什麼

可以看到,我们的 _csrf 配置已经生效了。

以上是SpringBoot防禦CSRF攻擊的流程及原理是什麼的詳細內容。更多資訊請關注PHP中文網其他相關文章!

陳述:
本文轉載於:yisu.com。如有侵權,請聯絡admin@php.cn刪除