最新公告
  • 欢迎您光临悠哉网,本站秉承服务宗旨 履行“站长”责任,销售只是起点 服务永无止境!立即加入我们
  • SpringBoot 如何防御 CSRF 攻击?

    CSRF 就是跨域请求伪造,英文全称是 Cross Site Request Forgery。

    这是一种非常常见的 Web 攻击方式,其实是很好防御的,但是由于经常被很多开发者忽略,进而导致很多网站实际上都存在 CSRF 攻击的安全隐患。

    今天松哥就来和大家聊一聊什么是 CSRF 攻击以及 CSRF 攻击该如何防御。

    1.CSRF原理

    想要防御 CSRF 攻击,那我们得先搞清楚什么是 CSRF 攻击,松哥通过下面一张图,来和大家梳理 CSRF 攻击流程:

    其实这个流程很简单:

    • 假设用户打开了招商银行网上银行网站,并且登录。
    • 登录成功后,网上银行会返回 Cookie 给前端,浏览器将 Cookie 保存下来。
    • 用户在没有登出网上银行的情况下,在浏览器里边打开了一个新的选项卡,然后又去访问了一个危险网站。
    • 这个危险网站上有一个超链接,超链接的地址指向了招商银行网上银行。
    • 用户点击了这个超链接,由于这个超链接会自动携带上浏览器中保存的 Cookie,所以用户不知不觉中就访问了网上银行,进而可能给自己造成了损失。

    CSRF 的流程大致就是这样,接下来松哥用一个简单的例子和小伙伴们展示一下 CSRF 到底是怎么回事。

    2.CSRF实践

    接下来,我创建一个名为 csrf-1 的 Spring Boot 项目,这个项目相当于我们上面所说的网上银行网站,创建项目时引入 Web 和 Spring Security 依赖,如下:

    创建成功后,方便起见,我们直接将 Spring Security 用户名/密码 配置在 application.properties 文件中:

    1. spring.security.user.name=javaboy 
    2. spring.security.user.password=123 

    然后我们提供两个测试接口:

    1. @RestController 
    2. public class HelloController { 
    3.     @PostMapping("/transfer"
    4.     public void transferMoney(String nameInteger money) { 
    5.         System.out.println("name = " + name); 
    6.         System.out.println("money = " + money); 
    7.     } 
    8.     @GetMapping("/hello"
    9.     public String hello() { 
    10.         return "hello"
    11.     } 

    假设 /transfer 是一个转账接口(这里是假设,主要是给大家演示 CSRF 攻击,真实的转账接口比这复杂)。

    最后我们还需要配置一下 Spring Security,因为 Spring Security 中默认是可以自动防御 CSRF 攻击的,所以我们要把这个关闭掉:

    1. @Configuration 
    2. public class SecurityConfig extends WebSecurityConfigurerAdapter { 
    3.     @Override 
    4.     protected void configure(HttpSecurity http) throws Exception { 
    5.         http.authorizeRequests().anyRequest().authenticated() 
    6.                 .and() 
    7.                 .formLogin() 
    8.                 .and() 
    9.                 .csrf() 
    10.                 .disable(); 
    11.     } 

    配置完成后,我们启动 csrf-1 项目。

    接下来,我们再创建一个 csrf-2 项目,这个项目相当于是一个危险网站,为了方便,这里创建时我们只需要引入 web 依赖即可。

    项目创建成功后,首先修改项目端口:

    1. server.port=8081 

    然后我们在 resources/static 目录下创建一个 hello.html ,内容如下:

    1. <body> 
    2. <form action="http://localhost:8080/transfer" method="post"
    3.     <input type="hidden" value="javaboy" name="name"
    4.     <input type="hidden" value="10000" name="money"
    5.     <input type="submit" value="点击查看美女图片"
    6. </form> 
    7. </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 中配置用户名/密码:

    1. spring.security.user.name=javaboy 
    2. spring.security.user.password=123 

    接下来,我们提供一个测试接口:

    1. @Controller 
    2. public class HelloController { 
    3.     @PostMapping("/hello"
    4.     @ResponseBody 
    5.     public String hello() { 
    6.         return "hello"
    7.     } 

    注意,这个测试接口是一个 POST 请求,因为默认情况下,GET、HEAD、TRACE 以及 OPTIONS 是不需要验证 CSRF 攻击的。

    然后,我们在 resources/templates 目录下,新建一个 thymeleaf 模版,如下:

    1. <body> 
    2. <form action="/hello" method="post"
    3.     <input type="hidden" th:value="${_csrf.token}" th:name="${_csrf.parameterName}"
    4.     <input type="submit" value="hello"
    5. </form> 
    6. </body> 

    注意,在发送 POST 请求的时候,还额外携带了一个隐藏域,隐藏域的 key 是 ${_csrf.parameterName},value 则是 ${_csrf.token}。

    这两个值服务端会自动带过来,我们只需要在前端渲染出来即可。

    接下来给前端 hello.html 页面添加一个控制器,如下:

    1. @GetMapping("/hello"
    2. public String hello2() { 
    3.     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 中返回前端,配置方式如下:

    1. @Configuration 
    2. public class SecurityConfig extends WebSecurityConfigurerAdapter { 
    3.     @Override 
    4.     protected void configure(HttpSecurity http) throws Exception { 
    5.         http.authorizeRequests().anyRequest().authenticated() 
    6.                 .and() 
    7.                 .formLogin() 
    8.                 .and() 
    9.                 .csrf().csrfTokenRepository(CookieCsrfTokenRepository.withHttpOnlyFalse()); 
    10.     } 

    有小伙伴可能会说放在 Cookie 中不是又被黑客网站盗用了吗?其实不会的,大家注意如下两个问题:

    1. 鸿蒙官方战略合作共建——HarmonyOS技术社区
    2. 黑客网站根本不知道你的 Cookie 里边存的啥,他也不需要知道,因为 CSRF 攻击是浏览器自动携带上 Cookie 中的数据的。
    3. 我们将服务端生成的随机数放在 Cookie 中,前端需要从 Cookie 中自己提取出来 _csrf 参数,然后拼接成参数传递给后端,单纯的将 Cookie 中的数据传到服务端是没用的。

    理解透了上面两点,你就会发现 _csrf 放在 Cookie 中是没有问题的,但是大家注意,配置的时候我们通过 withHttpOnlyFalse 方法获取了 CookieCsrfTokenRepository 的实例,该方法会设置 Cookie 中的 HttpOnly 属性为 false,也就是允许前端通过 js 操作 Cookie(否则你就没有办法获取到 _csrf)。

    配置完成后,重启项目,此时我们就发现返回的 Cookie 中多了一项:

    接下来,我们通过自定义登录页面,来看看前端要如何操作。

    首先我们在 resources/static 目录下新建一个 html 页面叫做 login.html:

    1. <!DOCTYPE html> 
    2. <html lang="en"
    3. <head> 
    4.     <meta charset="UTF-8"
    5.     <title>Title</title> 
    6.     <script src="js/jquery.min.js"></script> 
    7.     <script src="js/jquery.cookie.js"></script> 
    8. </head> 
    9. <body> 
    10. <div> 
    11.     <input type="text" id="username"
    12.     <input type="password" id="password"
    13.     <input type="button" value="登录" id="loginBtn"
    14. </div> 
    15. <script> 
    16.     $("#loginBtn").click(function () { 
    17.         let _csrf = $.cookie(\'XSRF-TOKEN\'); 
    18.         $.post(\'/login.html\',{username:$("#username").val(),password:$("#password").val(),_csrf:_csrf},function (data) { 
    19.             alert(data); 
    20.         }) 
    21.     }) 
    22. </script> 
    23. </body> 
    24. </html> 

    这段 html 我给大家解释下:

    1. 鸿蒙官方战略合作共建——HarmonyOS技术社区
    2. 首先引入 jquery 和 jquery.cookie ,方便我们一会操作 Cookie。
    3. 定义三个 input,前两个是用户名和密码,第三个是登录按钮。
    4. 点击登录按钮之后,我们先从 Cookie 中提取出 XSRF-TOKEN,这也就是我们要上传的 csrf 参数。
    5. 通过一个 POST 请求执行登录操作,注意携带上 _csrf 参数。

    服务端我们也稍作修改,如下:

    1. @Configuration 
    2. public class SecurityConfig extends WebSecurityConfigurerAdapter { 
    3.     @Override 
    4.     public void configure(WebSecurity web) throws Exception { 
    5.         web.ignoring().antMatchers("/js/**"); 
    6.     } 
    7.  
    8.     @Override 
    9.     protected void configure(HttpSecurity http) throws Exception { 
    10.         http.authorizeRequests().anyRequest().authenticated() 
    11.                 .and() 
    12.                 .formLogin() 
    13.                 .loginPage("/login.html"
    14.                 .successHandler((req,resp,authentication)->{ 
    15.                     resp.getWriter().write("success"); 
    16.                 }) 
    17.                 .permitAll() 
    18.                 .and() 
    19.                 .csrf().csrfTokenRepository(CookieCsrfTokenRepository.withHttpOnlyFalse()); 
    20.     } 

    一方面这里给 js 文件放行。

    另一方面配置一下登录页面,以及登录成功的回调,这里简单期间,登录成功的回调我就给一个字符串就可以了。大家感兴趣的话,可以查看本系列前面文章,有登录成功后回调的详细解释。

    OK,所有事情做完之后,我们访问 login.html 页面,输入用户名密码进行登录,结果如下:

    可以看到,我们的 _csrf 配置已经生效了。

    小伙伴们可以自行尝试从登录参数中去掉 _csrf,然后再看看效果。

    4.小结

    好了,今天主要和小伙伴们介绍了 csrf 攻击以及如何防御的问题。大家看到,csrf 攻击主要是借助了浏览器默认发送 Cookie 的这一机制,所以如果你的前端是 App、小程序之类的应用,不涉及浏览器应用的话,其实可以忽略这个问题,如果你的前端包含浏览器应用的话,这个问题就要认真考虑了。

    好了 ,本文就说到这里,本文相关案例我已经上传到 GitHub ,大家可以自行下载:https://github.com/lenve/spring-security-samples

    本文转载自微信公众号「江南一点雨」,可以通过以下二维码关注。转载本文请联系江南一点雨公众号。

    【编辑推荐】

    1. 鸿蒙官方战略合作共建——HarmonyOS技术社区
    2. 看完这篇还不懂 MySQL主从复制,可以回家躺平了~
    3. 老男孩MySQL私房菜深入浅出精品视频第10章-MySQL日志系统知识
    4. MySQL:五个常见优化SQL的技巧
    5. 再有人问你MySQL的隔离级别是什么,就把这篇文章发给他!
    6. MySQL优化之Schema设计
    1. 本站所有资源来源于用户上传和网络,如有侵权请邮件联系站长!
    2. 分享目的仅供大家学习和交流,您必须在下载后24小时内删除!
    3. 不得使用于非法商业用途,不得违反国家法律。否则后果自负!
    4. 本站提供的源码、模板、插件等等其他资源,都不包含技术服务请大家谅解!
    5. 如有链接无法下载、失效或广告,请联系管理员处理!
    6. 本站资源售价只是赞助,收取费用仅维持本站的日常运营所需!
    7. 如遇到加密压缩包,默认解压密码为"www.yoozai.net",如遇到无法解压的请联系管理员!
    悠哉网 » SpringBoot 如何防御 CSRF 攻击?

    常见问题FAQ

    免费下载或者VIP会员专享资源能否直接商用?
    本站所有资源版权均属于原作者所有,这里所提供资源均只能用于参考学习用,请勿直接商用。若由于商用引起版权纠纷,一切责任均由使用者承担。更多说明请参考 VIP介绍。
    提示下载完但解压或打开不了?
    最常见的情况是下载不完整: 可对比下载完压缩包的与网盘上的容量,若小于网盘提示的容量则是这个原因。这是浏览器下载的bug,建议用百度网盘软件或迅雷下载。若排除这种情况,可在对应资源底部留言,或 联络我们.。
    找不到素材资源介绍文章里的示例图片?
    对于PPT,KEY,Mockups,APP,网页模版等类型的素材,文章内用于介绍的图片通常并不包含在对应可供下载素材包内。这些相关商业图片需另外购买,且本站不负责(也没有办法)找到出处。 同样地一些字体文件也是这种情况,但部分素材会在素材包内有一份字体下载链接清单。
    悠哉网 WWW.YOOZAI.NET
    悠哉网,用户消费首选的网站,喜欢你就悠哉一下。

    发表评论

    • 1072会员总数(位)
    • 40643资源总数(个)
    • 0本周发布(个)
    • 0 今日发布(个)
    • 487稳定运行(天)

    提供最优质的资源集合

    立即查看 了解详情