spring使用spring security防范CSRF攻击

发布于 2021-04-16  115 次阅读


前几天在美团技术团队的博客中看到了CSRF相关的内容,正好鹅厂的面试官问到了相关问题,自己也没有答好。因此就研究了一下如何在Spring中使用spring security防范CSRF攻击,动手实践才是学习的最快方法。

什么是CSRF?

这里直接引用美团技术团队博客中的描述吧(大佬写的博客语言简练,讲得又清楚)。

CSRF(Cross-site request forgery跨站请求伪造:攻击者诱导受害者进入第三方网站,在第三方网站中,向被攻击网站发送跨站请求。利用受害者在被攻击网站已经获取的注册凭证,绕过后台的用户验证,达到冒充用户对被攻击的网站执行某项操作的目的。

一个典型的CSRF攻击有着如下的流程:

①受害者登录a.com,并保留了登录凭证(Cookie)。

②攻击者引诱受害者访问了b.com。

③b.com 向 a.com 发送了一个请求:a.com/act=xx。浏览器会默认携带a.com的Cookie。

④a.com接收到请求后,对请求进行验证,并确认是受害者的凭证,误以为是受害者自己发送的请求。

⑤a.com以受害者的名义执行了act=xx。

⑥攻击完成,攻击者在受害者不知情的情况下,冒充受害者,让a.com执行了自己定义的操作。

更多CSRF相关的概念请看我刚刚提到的美团团队的博客。

CSRF利用

假设今天小明同学一大早起床,就看见沙雕群友发了个链接。链接的标题是:《震惊,西南某985高校男寝室竟发生这样的事!》。

小明同学好奇地点卡网页,却发现是个空白页,就失望地离开了。

没过几分钟,小明的微信就被刷爆了。很多朋友都来问小明同学的校园论坛(community.scu.edu.cn)号是不是被盗了。

原来是小明在校园论坛发表了一个内容为“我是个大傻逼!”的帖子,并被顶到了全校热榜上。

小明百思不得其解。仔细思考后,打开了之前打开的空白页,并查看HTML。

其HTML如下:

<form method="POST" action="https://community.scu.edu.cn/publish" enctype="multipart/form-data">
      <input type="hidden" name="head" value=" 114514!"/>  
      <input type="hidden" name="post" value=" 我是个大傻逼!"/>    
</form>  
<script>
      document.forms[0].submit(); 
</script>

小明之前使用web端登录过校园论坛,浏览器中也就存在着cookie。

显然,小明遭到了杀掉群友的CSRF攻击恶作剧!

这样的攻击,如果只是恶作剧,那也只是“社死”罢了。但是如果发生在银行转账系统等关键的网站,后果不堪设想!

如何防范CSRF攻击?

主流的方法有:

①同源检测

②CSRFtoken

同源检测可以使用origin header或者referer header实现。

CSRF Token则是对每个会话都生成一个专属的token。只有通过特定的网站访问时才有这个token,因此无法进行跨站的请求伪造攻击。

具体概念也见美团技术团队的文章。

如何在Spring 中使用 Spring security实现对CSRF的防御?

使用maven引入spring security模块

<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
         xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
         xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
    <parent>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-parent</artifactId>
        <version>2.1.10.RELEASE</version>
        <relativePath/> <!-- lookup parent from repository -->
    </parent>
    <modelVersion>4.0.0</modelVersion>

    <artifactId>testOfCSRF</artifactId>

    <dependencies>
        <!-- 实现对 Spring MVC 的自动化配置 -->
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-web</artifactId>
        </dependency>

        <!-- 实现对 Spring Security 的自动化配置 -->
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-security</artifactId>
        </dependency>
    </dependencies>

</project>

重写configure(HttpSecurity)方法进行配置

@Override
    protected void configure(HttpSecurity http) throws Exception {
        http
                //.csrf()                 
                //.disable();
    }

CSRF过滤实际上是默认开启的,因此不需要进行特殊配置。若需要关闭则使用上面的方法关闭即可。

在传统前后端不分离情况下:

在传统的不分离的情况下实现非常简单。CSRF默认会拦截使得状态变化的请求(如GET,HEAD,OPTIONS和TRACE请求)。我们只需要在使用thymeleaf进行action时,传入一个hidden的CSRFTOKEN就可以。只要方法带着对应的token,后端不需要进行修改就可以完成。

下面我们就以发帖的HTML为例:

<form action="/publish" method="post">
    <input type="hidden" th:value="${_csrf.token}" th:name="${_csrf.parameterName}">
    <input type="hidden" name="id" th:value="${id}">
    <div class="form-group">
        <label for="title">问题标题(简单扼要):</label>
        <input type="text" class="form-control" th:value="${title}" id="title" name="title"
               placeholder="问题标题……"
               autocomplete="off">
    </div>
    <div class="form-group" id="question-editor" style="z-index: 2">
        <label for="description">问题补充 :</label>
        <textarea name="description" id="description" th:text="${description}" style="display:none;"
                  class="form-control"
                  cols="30"
                  rows="10"></textarea>
    </div>
</form>
在上传了贴文后,可以看到多了个_CSRF 数据

此时推文发表成功。

若把 <input type="hidden" th:value="${_csrf.token}" th:name="${_csrf.parameterName}"> 注释掉,则无法发布贴文。

在前后端分离的项目中实现CSRF防御:

在这种情况下,则_csrf 不通过model来到前端的thymeleaf.而是存在cookie中返回.

对httpSecurity类进行如下配置

@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());
     }
 }

这样token就从cookie中传给了前端,再由前端逻辑进行处理.


你好哇!欢迎来到雷公马碎碎念的地方:)