SpringBoot防禦CSRF攻擊的流程及原理是什麼
CSRF 原理
想要防禦CSRF 攻擊,那我們需要先搞清楚什麼是CSRF 攻擊,透過下面圖例來和大家梳理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 頁面,在訪問時候,需要先登錄,登錄成功之後,我們可以看到登入請求中也多了一個參數
这里我们用了 Spring Security 的默认登录页面,如果大家使用自定义登录页面,可以参考上面 hello.html 的写法,通过一个隐藏域传递 _csrf 参数。
访问到 hello 页面之后,再去点击【hello】按钮,就可以访问到 hello 接口了。
这是 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 中多了一项:
接下来,我们通过自定义登录页面,来看看前端要如何操作。
首先我们在 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('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 参数。
服务端我们也稍作修改,如下:
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 页面,输入用户名密码进行登录,结果如下:
可以看到,我们的 _csrf 配置已经生效了。
以上是SpringBoot防禦CSRF攻擊的流程及原理是什麼的詳細內容。更多資訊請關注PHP中文網其他相關文章!

熱AI工具

Undresser.AI Undress
人工智慧驅動的應用程序,用於創建逼真的裸體照片

AI Clothes Remover
用於從照片中去除衣服的線上人工智慧工具。

Undress AI Tool
免費脫衣圖片

Clothoff.io
AI脫衣器

AI Hentai Generator
免費產生 AI 無盡。

熱門文章

熱工具

記事本++7.3.1
好用且免費的程式碼編輯器

SublimeText3漢化版
中文版,非常好用

禪工作室 13.0.1
強大的PHP整合開發環境

Dreamweaver CS6
視覺化網頁開發工具

SublimeText3 Mac版
神級程式碼編輯軟體(SublimeText3)

熱門話題

Jasypt介紹Jasypt是一個java庫,它允許開發員以最少的努力為他/她的專案添加基本的加密功能,並且不需要對加密工作原理有深入的了解用於單向和雙向加密的高安全性、基於標準的加密技術。加密密碼,文本,數字,二進位檔案...適合整合到基於Spring的應用程式中,開放API,用於任何JCE提供者...添加如下依賴:com.github.ulisesbocchiojasypt-spring-boot-starter2. 1.1Jasypt好處保護我們的系統安全,即使程式碼洩露,也可以保證資料來源的

一、Redis實現分散式鎖原理為什麼需要分散式鎖在聊分散式鎖之前,有必要先解釋一下,為什麼需要分散式鎖。與分散式鎖相對就的是單機鎖,我們在寫多執行緒程式時,避免同時操作一個共享變數產生資料問題,通常會使用一把鎖來互斥以保證共享變數的正確性,其使用範圍是在同一個進程中。如果換做是多個進程,需要同時操作一個共享資源,如何互斥?現在的業務應用通常是微服務架構,這也意味著一個應用會部署多個進程,多個進程如果需要修改MySQL中的同一行記錄,為了避免操作亂序導致髒數據,此時就需要引入分佈式鎖了。想要實現分

springboot讀取文件,打成jar包後訪問不到最新開發出現一種情況,springboot打成jar包後讀取不到文件,原因是打包之後,文件的虛擬路徑是無效的,只能通過流去讀取。文件在resources下publicvoidtest(){Listnames=newArrayList();InputStreamReaderread=null;try{ClassPathResourceresource=newClassPathResource("name.txt");Input

在Springboot+Mybatis-plus不使用SQL語句進行多表添加操作我所遇到的問題準備工作在測試環境下模擬思維分解一下:創建出一個帶有參數的BrandDTO對像模擬對後台傳遞參數我所遇到的問題我們都知道,在我們使用Mybatis-plus中進行多表操作是極其困難的,如果你不使用Mybatis-plus-join這一類的工具,你只能去配置對應的Mapper.xml文件,配置又臭又長的ResultMap,然後再寫對應的sql語句,這種方法雖然看上去很麻煩,但具有很高的靈活性,可以讓我們

SpringBoot和SpringMVC都是Java開發中常用的框架,但它們之間有一些明顯的差異。本文將探究這兩個框架的特點和用途,並對它們的差異進行比較。首先,我們來了解一下SpringBoot。 SpringBoot是由Pivotal團隊開發的,它旨在簡化基於Spring框架的應用程式的建立和部署。它提供了一種快速、輕量級的方式來建立獨立的、可執行

1.自訂RedisTemplate1.1、RedisAPI預設序列化機制基於API的Redis快取實作是使用RedisTemplate範本進行資料快取操作的,這裡開啟RedisTemplate類,查看該類別的源碼資訊publicclassRedisTemplateextendsRedisAccessorimplementsRedisOperations,BeanClassLoaderAware{//聲明了value的各種序列化方式,初始值為空@NullableprivateRedisSe

在專案中,很多時候需要用到一些配置信息,這些信息在測試環境和生產環境下可能會有不同的配置,後面根據實際業務情況有可能還需要再做修改。我們不能將這些設定在程式碼中寫死,最好是寫到設定檔中,例如可以把這些資訊寫到application.yml檔案中。那麼,怎麼在程式碼裡取得或使用這個位址呢?有2個方法。方法一:我們可以透過@Value註解的${key}即可取得設定檔(application.yml)中和key對應的value值,這個方法適用於微服務比較少的情形方法二:在實際專案中,遇到業務繁瑣,邏

本文來寫個詳細的例子來說下dubbo+nacos+Spring Boot開發實戰。本文不會講述太多的理論的知識,會寫一個最簡單的例子來說明dubbo如何與nacos整合,快速建構開發環境。
