SpringBoot 如何防御 CSRF 攻击

news2024/11/26 13:37:39

SpringBoot 如何防御 CSRF 攻击

    • CSRF 原理
    • CSRF实践
    • CSRF防御
      • 前后端不分离方案
      • 前后端分离方案

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

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

CSRF 原理

想要防御 CSRF 攻击,那我们需要先搞清楚什么是 CSRF 攻击,通过下面图例来和大家梳理 CSRF 攻击流程:
在这里插入图片描述
其实这个流程很简单:
1.假设用户打开了招商网上银行网站,并且登录。
2.登录成功后,网上银行会返回Cookie给前端,浏览器将Cookie保存下来。
3.用户在没有登出网上银行的情况下,在浏览器里打开了一个新的选项卡,然后又去访问了一个危险网站。
4.这个危险网站上有一个超链接,超链接的地址指向了招商网上银行。
4.用户点击了这个链接,由于这个超链接会自动携带上浏览器中保存的Cookie,所以用户不知不觉中就访问了网上银行,进而可能给自己造成了损失。

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

CSRF实践

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

<dependencies>
	<dependency>
		<groupId>org.springframework.boot</groupId>
		<artifactId>spring-boot-starter-security</artifactId>
	</dependency>
	<dependency>
		<groupId>org.springframework.boot</groupId>
		<artifactId>spring-boot-starter-web</artifactId>
	</dependency>

</dependencies>

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

server.port= 8866
spring.security.user.name=javaboy
spring.security.user.password=123

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

package com.mry.csrf.controller;

import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RestController;

@RestController
public class CsrfDemoController {

    @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 攻击,真实的转账接口比这复杂)。

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

package com.mry.csrf.config;

import org.springframework.context.annotation.Configuration;
import org.springframework.security.config.annotation.web.builders.HttpSecurity;
import org.springframework.security.config.annotation.web.configuration.WebSecurityConfigurerAdapter;

@Configuration
public class SecurityConfig extends WebSecurityConfigurerAdapter {
    @Override
    protected void configure(HttpSecurity http) throws Exception {
        http.authorizeRequests().anyRequest().authenticated()
                .and()
                .formLogin()
                .and()
                .csrf()
                .disable();
    }
}

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

5.我们再创建一个 csrf-loophole-web 项目,这个项目相当于是一个危险网站,为了方便,这里创建时我们只需要引入 web 依赖即可。
项目创建成功后,首先修改项目端口:

server.port= 8855

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

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>Title</title>
</head>

<body>
    <form action="http://localhost:8866/transfer" method="post">
        <input type="hidden" value="javaboy" name="name">
        <input type="hidden" value="10000" name="money">
        <input type="submit" value="点击查看美女图片">
    </form>
</body>

</html>

这里有一个超链接,超链接的文本是点击查看美女图片,当你点击了超链接之后,会自动请求 http://localhost:8866/transfer 接口,同时隐藏域还携带了两个参数。

配置完成后,就可以启动 csrf-loophole-web 项目了。

接下来,用户首先访问 csrf-simulate-web 项目中的接口,在访问的时候需要登录,用户就执行了登录操作,访问完整后,用户并没有执行登出操作,然后用户访问 csrf-loophole-web 中的页面,看到了超链接,好奇这美女到底长啥样,一点击,结果钱就被人转走了。

CSRF防御

先来说说防御思路。

CSRF 防御,一个核心思路就是在前端请求中,添加一个随机数。

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

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

Spring Security 中对此提供了很好的支持,我们一起来看下。

前后端不分离方案

Spring Security 中默认实际上就提供了 csrf 防御,但是需要开发者做的事情比较多。
首先我们来创建一个新的 Spring Boot 工程,创建时引入 Spring Security、Thymeleaf 和 web 依赖。

1.pom信息

<dependencies>
	<dependency>
		<groupId>org.springframework.boot</groupId>
		<artifactId>spring-boot-starter-web</artifactId>
	</dependency>

	<dependency>
		<groupId>org.springframework.boot</groupId>
		<artifactId>spring-boot-starter-security</artifactId>
	</dependency>

	<dependency>
		<groupId>org.springframework.boot</groupId>
		<artifactId>spring-boot-starter-thymeleaf</artifactId>
	</dependency>

	<dependency>
		<groupId>org.springframework.boot</groupId>
		<artifactId>spring-boot-starter-test</artifactId>
		<scope>test</scope>
	</dependency>
</dependencies>

2.项目创建成功后,我们还是在 application.properties 中配置用户名/密码

spring.security.user.name=mry
spring.security.user.password=123456

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

package com.mry.csrf.controller;

import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.ResponseBody;
import org.springframework.web.bind.annotation.RestController;

@RestController
public class SecurityCsrfController {

    @PostMapping("/hello")
    @ResponseBody
    public String hello() {
        return "hello";
    }

}

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

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

<!DOCTYPE html>
<!--导入thymeleaf的名称空间-->
<html lang="en" xmlns:th="http://www.thymeleaf.org">
<head>
    <meta charset="UTF-8">
    <title>Title</title>
</head>
<body>
    <form action="/hello" method="post">
        <input type="hidden" th:value="${_csrf.token}" th:name="${_csrf.parameterName}">
        <input type="submit" value="hello">
    </form>
</body>
</html>

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

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

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

@GetMapping("/hello")
public String hello2() {
	return "hello";
}

6.添加完成后,启动项目,我们访问 hello 页面,在访问时候,需要先登录,登录成功之后,我们可以看到登录请求中也多了一个参数
在这里插入图片描述
这里我们用了 Spring Security 的默认登录页面,如果大家使用自定义登录页面,可以参考上面 hello.html 的写法,通过一个隐藏域传递 _csrf 参数。

访问到 hello 页面之后,再去点击【hello】按钮,就可以访问到 hello 接口了。
在这里插入图片描述

这是 Spring Security 中默认的方案,通过 Model 将相关的数据带到前端来。

如果你的项目是前后端不分项目,这种方案就可以了,如果你的项目是前后端分离项目,这种方案很明显不够用。

前后端分离方案

如果是前后端分离项目,Spring Security 也提供了解决方案。
这次不是将 _csrf 放在 Model 中返回前端了,而是放在 Cookie 中返回前端,配置方式如下:

package com.mry.csrf.config;

import org.springframework.context.annotation.Configuration;
import org.springframework.security.config.annotation.web.builders.HttpSecurity;
import org.springframework.security.config.annotation.web.configuration.WebSecurityConfigurerAdapter;
import org.springframework.security.web.csrf.CookieCsrfTokenRepository;

@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="https://libs.baidu.com/jquery/2.1.4/jquery.min.js"></script>
    <script src="https://cdn.bootcss.com/jquery-cookie/1.4.1/jquery.cookie.min.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 参数。

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

package com.mry.csrf.config;

import org.springframework.context.annotation.Configuration;
import org.springframework.security.config.annotation.web.builders.HttpSecurity;
import org.springframework.security.config.annotation.web.builders.WebSecurity;
import org.springframework.security.config.annotation.web.configuration.WebSecurityConfigurerAdapter;
import org.springframework.security.web.csrf.CookieCsrfTokenRepository;

@Configuration
public class SecurityConfig extends WebSecurityConfigurerAdapter {

    @Override
    public void configure(WebSecurity web) throws Exception {
        web.ignoring().antMatchers("/static/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 配置已经生效了。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/477727.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

压缩HTML引用字体

内容简介 有些网站为了凸显某部分字体&#xff0c;而引入自定义字体&#xff0c;但由于自定义字体相对都比较大&#xff08;几M&#xff09;,导致页面加载缓慢&#xff1b;所以本文介绍三种压缩字体的方法&#xff0c;可根据项目情况自行选择。 压缩方法 1、利用Fontmin程序&a…

ETL工具 - Kettle 输入输出算子介绍

一、Kettle 输入输出算子介绍 上篇文章对 Kettle 进行了简单的介绍及做了个简单的案例&#xff0c;但是对 Kettle 的Step算子工具没有做过多的解释&#xff0c;本篇文章从输入输出算子工具开始进行介绍。 下面是上篇文章的地址&#xff1a; ETL工具 - Kettle 介绍及基本使用 …

正点原直流有刷驱动板的硬件解说

这里写目录标题 H桥电流采集电路电源电压采集电路温度采集电路过流检测电路过流保护电路![请添加图片描述](https://img-blog.csdnimg.cn/fa130e17e23b471e8e27ae942e1ff125.png) H桥 因为光耦(EL0631)作用&#xff0c;PWM 引脚电平输入IN相反。 然后半桥芯片的逻辑表为&#x…

【Python】BeautifulSoup

一、什么是BeautifulSoup Beautiful Soup 简称 BS4&#xff08;其中 4 表示版本号&#xff09;是一个 Python 第三方库&#xff0c;它可以从 HTML 或 XML 文档中快速地提取指定的数据。 二、BS4下载安装 点击最下面的终端进如命令行界面 &#xff08;进入成功界面&#xff1a;&…

C++/Qt 小知识记录3

工作中遇到的一些小问题&#xff0c;总结的小知识记录&#xff1a;C/Qt 小知识 QLineEdit限制输入大于0的正整数QLayout内清空已布局的WidgetWindows结束进程直接结束&#xff0c;子进程不响应结束事件正常结束&#xff0c;子进程响应结束事件 CMake关闭控制台Console实体与值对…

【网络编程】网络基础知识总结

文章目录 一、局域网与广域网二、协议2.1 协议的概念2.2 协议分层2.3 数据传输的条件 三、OSI七层模型四、TCP/IP五层&#xff08;四层&#xff09;模型五、网络传输5.1 预备知识——报头5.2 局域网通信原理5.3 以太网和令牌环网5.3 网络传输流程图5.3.1 同一网段的两台计算机的…

海岛旅行回忆总结(不定更)

文章目录 概述大辣甲岛外伶仃岛、东澳岛长洲、南丫岛下川岛 概述 喜欢大海&#xff0c;与海滩相比&#xff0c;海岛的空气更好、海水更靓、人更少、污染更少 海岛位置日期天气小结大辣甲岛惠州2017年7月29~30日台风前夕&#xff0c;云淡风轻&#xff0c;阳光猛烈开发程度低&…

分享2款CSS3母亲节主题寄语文字动画特效

目录 ❤️ 前言 第一款&#xff1a;妈妈您辛苦了&#xff01; 一、效果图 二、代码实现 第二款&#xff1a;Mothers Day&#xff01; 一、效果图 二、代码实现 ❤️ 祝福 ❤️ 前言 母亲节&#xff0c;在每年五月的第二个星期日&#xff0c;是用来感谢母亲的节日。…

SpringAOP

SpringAOP 一、AOP1. AOP简介1.1 AOP简介和作用1.2 AOP中的核心概念 2. AOP入门案例【重点】2.1 AOP入门案例思路分析2.2 AOP入门案例实现【第一步】导入aop相关坐标【第二步】定义dao接口与实现类【第三步】定义通知类&#xff0c;制作通知方法【第四步】定义切入点表达式、配…

阿里云g8i服务器Intel Xeon(Sapphire Rapids) Platinum 8475B

阿里云服务器ECS通用型实例规格族g8i采用2.7 GHz主频的Intel Xeon(Sapphire Rapids) Platinum 8475B处理器&#xff0c;3.2 GHz睿频&#xff0c;g8i实例采用阿里云全新CIPU架构&#xff0c;可提供稳定的算力输出、更强劲的I/O引擎以及芯片级的安全加固。阿里云百科分享阿里云服…

< C++ >lesson2.C++入门下

文章目录 1. 内联函数❤️1.1 内联函数的定义1.2 特性 2. auto关键字&#x1f90d;2.1 auto简介2.2 auto使用细则2.3 auto不能进行推导的场景 3. 基于范围的for循环&#x1f9e1;3.2 范围for的条件 4. nullptr指针&#x1f49a; 1. 内联函数❤️ 在C语言中有宏函数的概念&…

【嵌入式】Modbus实践

前言 最近接了一个项目&#xff0c;需要使用Modbus协议&#xff0c;虽然之前有所耳闻&#xff0c;但一直没有实操过&#xff0c;但实践之后发现其实还是很简单的&#xff0c;我认为它本质上就是对串口传输进行“二次封装”。 建议的入门顺序 大神带你秒懂Modbus通信协议——理…

【zotero】从知网万方维普WOS等数据库上导出格式引文-引文格式到zotero中

一、问题背景 最近新中特课要交课程论文作业了&#xff0c;老师说要有参考文献引注。 于是我找了很多参考文献&#xff0c;但是不打算放在我的zotero文献库中&#xff0c;因为我不想我的zotero文献库中包括与我专业学习不相关的东西。 鉴于导入文献后识别成引注后续导出打包…

iOS开发之Masonry框架源码深度解析

Masonry是iOS在控件布局中经常使用的一个轻量级框架&#xff0c;Masonry让NSLayoutConstraint使用起来更为简洁。Masonry简化了NSLayoutConstraint的使用方式&#xff0c;让我们可以以链式的方式为我们的控件指定约束。本篇博客的主题不是教你如何去使用Masonry框架的&#xff…

1992-2022年31省人均gdp/各省人均地区生产总值

1992-2022年31省人均gdp/各省人均地区生产总值 1、时间&#xff1a;1992-2022年 2、来源&#xff1a;国家统计J、各省NJ 3、范围&#xff1a;包括31省 4、缺失情况说明&#xff1a;无缺失 5、指标包括&#xff1a;各省人均GDP/省人均地区生产总值 6、指标解释&#xff1a…

sql注入(二)盲注,二次注入

目录 一、布尔盲注 1.判断库名的长度 2.判断数据库名 2.1判断数据库名首字符 2.2 判断数据库名的其余字符 二、时间盲注&#xff1a; 1.判断库名的长度 2.判断库名&#xff1a; 3.判断表名payload&#xff1a; 4.爆出列名 5.爆数据 三、二次注入 1.原理&#xff1a; …

Linux 常用操作技巧

Linux 操作技巧大全 Linux是一种强大的操作系统&#xff0c;掌握各种操作技巧可以帮助我们惬意地使用它。在这篇博客中&#xff0c;我们将分享一些实用的Linux技巧&#xff0c;希望能对您有所帮助&#xff01; 1. 使用Tab进行自动补全 在输入命令时&#xff0c;按下Tab键可…

漫谈大数据 - 数据湖认知篇

导语&#xff1a;数据湖是目前比较热的一个概念&#xff0c;许多企业都在构建或者准备构建自己的数据湖。但是在计划构建数据湖之前&#xff0c;搞清楚什么是数据湖&#xff0c;明确一个数据湖项目的基本组成&#xff0c;进而设计数据湖的基本架构&#xff0c;对于数据湖的构建…

Embarcadero Delphi 11 和 C++Builder 11 免费社区版发布!

Embarcadero为Delphi和CBuilder的最新11.3版本提供了社区版许可证。这是Delphi或CBuilder的免费版本&#xff0c;适用于学生&#xff0c;业余爱好者和初创公司&#xff08;因为许可证仅对于收入有限的公司或个人&#xff09;。 什么是CE社区版&#xff1f; Delphi 和 CBuilde…

【Git 入门教程】第七节、Git 远程仓库(Github)

GitHub是一个基于Git的代码托管平台&#xff0c;它是全球最大的开源软件社区之一。GitHub允许开发者在上面创建和托管他们的Git仓库&#xff0c;并与其他开发者进行协作。 GitHub是一个非常流行的Git工具&#xff0c;它是开源社区的重要组成部分&#xff0c;并支持许多著名的开…