松哥手把手教你在 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 到呢?若是有收获,记得点个在看鼓励下松哥哦~

相关文章
相关标签/搜索