• 浅析SpringSecurity如何防御CSRF攻击


      今天无意间看到原来 SpringSecurity 自带了 CSRF 防御处理,所以记录下,不得不说 SpringSecurity 功能还是挺强大的,蛮多业务场景都提供了支持。

      CSRF 就是跨域请求伪造,英文全称是 Cross Site Request Forgery。这是一种非常常见的 Web 攻击方式,其实是很好防御的,但是由于经常被很多开发者忽略,进而导致很多网站实际上都存在 CSRF 攻击的安全隐患。

    一、CSRF 原理

      想要防御 CSRF 攻击,那我们得先搞清楚什么是 CSRF 攻击:

    二、CSRF 实践

      我创建一个名为 csrf-1 的 Spring Boot 项目,这个项目相当于我们上面所说的网上银行网站,创建项目时引入 Web 和 Spring Security 依赖。创建成功后提供2个接口

    @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 是一个转账接口。

      最后我们还需要配置一下 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 依赖即可。

      然后我们在 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 防御,一个核心思路就是在前端请求中,添加一个随机数。

      因为在 CSRF 攻击中,黑客网站其实是不知道用户的 Cookie 具体是什么的,他是让用户自己发送请求到网上银行这个网站的,因为这个过程会自动携带上 Cookie 中的信息。

      所以我们的防御思路是这样:用户在访问网上银行时,除了携带 Cookie 中的信息之外,还需要携带一个随机数,如果用户没有携带这个随机数,则网上银行网站会拒绝该请求。黑客网站诱导用户点击超链接时,会自动携带上 Cookie 中的信息,但是却不会自动携带随机数,这样就成功的避免掉 CSRF 攻击了。

      Spring Security 中对此提供了很好的支持,我们一起来看下:Spring Security 中默认实际上就提供了 csrf 防御,但是需要开发者做的事情比较多。我们主要看下前后端分离的项目。

      如果是前后端分离项目,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>

      其实也就是从 cookie 里拿出 _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 放行,允许操作 cookie;另一方面配置一下登录页面,以及登录成功的回调。这里简单起见,登录成功的回调我就给一个字符串就可以了。

      所有事情做完之后,我们访问 login.html 页面,输入用户名密码进行登录,就可以看到,我们的 _csrf 配置已经生效了。

      个人总结:其实就是利用了防止 CSRF攻击里的双重 Cookie 的方法,还是不错的,有兴趣的可以看我之前博客。本文详细内容见这篇文章:《【SpringSecurity系列(十八)】SpringBoot 如何防御 CSRF 攻击?》https://mp.weixin.qq.com/s/gOecyB84nt8Q5UKLIJrJiQ

  • 相关阅读:
    nginx+vue刷新404
    java-Object类的解析(持续更新)
    Python源码学习(六)-PyCodeObject初探
    经典算法之不定方程问题
    MySql中的视图的概念及应用
    数据结构之 折半插入排序
    mahout算法源码分析之Itembased Collaborative Filtering实战
    【Android】为Android虚拟机创建SDCard
    30个酷毙的交互式网站(HTML5+CSS3)
    项目总结——也谈svn版本库迁移
  • 原文地址:https://www.cnblogs.com/goloving/p/16186349.html
Copyright © 2020-2023  润新知