CSRF 就是跨域請求僞造,英文全稱是 Cross Site Request Forgery。html
這是一種很是常見的 Web 攻擊方式,實際上是很好防護的,可是因爲常常被不少開發者忽略,進而致使不少網站實際上都存在 CSRF 攻擊的安全隱患。前端
今天鬆哥就來和你們聊一聊什麼是 CSRF 攻擊以及 CSRF 攻擊該如何防護。java
本文是本系列第 18 篇,閱讀本系列前面文章有助於更好的理解本文:jquery
想要防護 CSRF 攻擊,那咱們得先搞清楚什麼是 CSRF 攻擊,鬆哥經過下面一張圖,來和你們梳理 CSRF 攻擊流程:git
其實這個流程很簡單:github
CSRF 的流程大體就是這樣,接下來鬆哥用一個簡單的例子和小夥伴們展現一下 CSRF 究竟是怎麼回事。web
接下來,我建立一個名爲 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 中的頁面,看到了超連接,好奇這美女到底長啥樣,一點擊,結果錢就被人轉走了。
先來講說防護思路。
CSRF 防護,一個核心思路就是在前端請求中,添加一個隨機數。
由於在 CSRF 攻擊中,黑客網站實際上是不知道用戶的 Cookie 具體是什麼的,他是讓用戶本身發送請求到網上銀行這個網站的,由於這個過程會自動攜帶上 Cookie 中的信息。
因此咱們的防護思路是這樣:用戶在訪問網上銀行時,除了攜帶 Cookie 中的信息以外,還須要攜帶一個隨機數,若是用戶沒有攜帶這個隨機數,則網上銀行網站會拒絕該請求。黑客網站誘導用戶點擊超連接時,會自動攜帶上 Cookie 中的信息,可是卻不會自動攜帶隨機數,這樣就成功的避免掉 CSRF 攻擊了。
Spring Security 中對此提供了很好的支持,咱們一塊兒來看下。
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 將相關的數據帶到前端來。
若是你的項目是先後端不分項目,這種方案就能夠了,若是你的項目是先後端分離項目,這種方案很明顯不夠用。
若是是先後端分離項目,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 中不是又被黑客網站盜用了嗎?其實不會的,你們注意以下兩個問題:
_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 我給你們解釋下:
_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
,而後再看看效果。
好了,今天主要和小夥伴們介紹了 csrf 攻擊以及如何防護的問題。你們看到,csrf 攻擊主要是藉助了瀏覽器默認發送 Cookie 的這一機制,因此若是你的前端是 App、小程序之類的應用,不涉及瀏覽器應用的話,其實能夠忽略這個問題,若是你的前端包含瀏覽器應用的話,這個問題就要認真考慮了。
好了 ,本文就說到這裏,本文相關案例我已經上傳到 GitHub ,你們能夠自行下載:https://github.com/lenve/spring-security-samples
好啦,不知道小夥伴們有沒有 GET 到呢?若是有收穫,記得點個在看鼓勵下鬆哥哦~