【深入浅出 Spring Security(八)】前后端分离-使用CSRF漏洞保护详讲

news2024/11/18 11:41:16

CSRF 漏洞保护

  • 一、CSRF 概述
  • 二、CSRF 攻击演示
  • 三、CSRF 防御
    • 令牌同步模式
  • 四、前后端分离使用 CSRF
    • CsrfFilter 源码分析
      • 源码一些方法的细究
    • 测试
  • 五、总结

一、CSRF 概述

CSRF(Cross-Site Request Forgery 跨站请求伪造),也可称为一键式攻击(one-click-attack),通常缩写为 CSRF 或者 XSRF。

CSRF 攻击是一种挟持用户在当前已登录的浏览器上发送恶意请求的攻击方法。相对于 xss 利用用户对指定网站的信任,CSRD 则是利用网站对用户网页浏览器的信任。简单来说,CSRF 是致击者通过一些技术手段欺骗用户的浏览器,去访问一个用户曾经认证过的网站并执行恶意请求,例如发送邮件、发消息、甚至财产操作(如转账和购买商品)。由于客户端(浏览器)已经在该网站中认证过了,所以该网站会认为是真正用户在操作而执行请求(实际上并非用户的本意)。

举个简单的例子:

假设 小柴 现在登录了某银行的网站准备完成一项转账操作,转账的链接如下:

https://bank.xxx.com/withdraw?aaccount=小菜&amount=1000&for=myz

根据这个链接的请求参数可以看见,这个链接是想从 小柴 这个账户下转账 1000 元到 myz 账户下,假设 小柴 没有注释登录该银行的网站,就在同一个浏览器新的选项卡中打开了一个危险网站,这个危险网站中有一幅图片,代码如下:

<img src="https://bank.xxx.com/withdraw?aaccount=小菜&amount=1000&for=zhangsan" />

一旦用户打开了这个网站,这个图片链接中的请求就会自动发送出去。由于是同一个浏览器并且用户尚未注销登录,所以该请求会自动携带上对应的有效的 Cookie 信息,进而完成一次转账操作。

这就是 CSRF 跨站请求伪造。

Spring Security 默认是开启了 CSRF 防御的(没关闭即是开启)。
在这里插入图片描述

二、CSRF 攻击演示

银行网站:server.port=8080
转账接口

@RestController
public class HelloController {

    @PostMapping("/withdraw")
    public String withdraw(){
        System.out.println("执行了一次转账操作");
        return "执行了一次转账操作";
    }

}

该网站安全配置(CSRF防御关闭)

@EnableWebSecurity
public class SecurityConfig {

    @Bean
    public SecurityFilterChain securityFilterChain(HttpSecurity http) throws Exception {
        return http.authorizeRequests()
                .anyRequest()
                .authenticated()
                .and()
                .formLogin()
                .and()
                .csrf()
                 .disable()
                // .and()
                .build();
    }

    @Bean
    public AuthenticationManager authenticationManager(HttpSecurity http) throws Exception {
        return http.getSharedObject(AuthenticationManagerBuilder.class)
                .userDetailsService(userDetailsService())
                .and()
                .build();
    }

    @Bean
    public UserDetailsService userDetailsService(){
        return new InMemoryUserDetailsManager(
                User.withUsername("root")
                        .password("{noop}123")
                        .roles("root")
                        .build()
        );
    }

}

攻击网站:server.port=8081
resources/static 目录下新建 index.html 文件,如下所示:

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>模拟 CSRF 跨站请求伪造</title>
</head>
<body>

    <form action="http://localhost:8080/withdraw" method="post">
        <input name="name" type="hidden" value="小柴"/>
        <input name="money" type="hidden" value="10000"/>
        <input type="submit" value="点我"/>
    </form>

</body>
</html>

测试

请添加图片描述

注意:满足同源策略才会携带Cookie信息。

三、CSRF 防御

CSRF 攻击的根源在于浏览器默认的身份验证机制(自动携带当前网站的Cookie信息,当然是满足同源策略的情况下),这种机制虽然可以保证请求是来自用户的某个浏览器,但是无法确保这请求是用户授权发送。攻击者和用户发送的请求一模一样,这意味着我们没有办法去直接拒绝这里的某个请求。如果能在合法请求中额外携带一个攻击者无法获取的参数,就可以成功区分出两种不同的情况,进而直接拒绝掉恶意请求。在 Spring Security 中就提供了这种机制来防御 CSRF 攻击,这种机制我们称之为 令牌同步模式

令牌同步模式

这是目前主流的 CSRF 攻击防御方案。具体的操作方式就是在每一个 HTTP 请求中,除了默认自动携带的 Cookie 参数之外,再提供一个安全的、随机生成的字符串,我们称之为 CSRF 令牌。这个 CSRF 令牌由服务器端生成,生成后在 HttpSession 中保存一份。当前端请求到达后,将该请求携带的 CSRF 令牌信息和服务器端中保存的令牌进行比对,如果两者不相等,则拒绝掉该 HTTP 请求。

开启 CSRF(当然是默认开启的)
在这里插入图片描述
服务器端生成的 CSRF 令牌

在这里插入图片描述

四、前后端分离使用 CSRF

CsrfFilter 源码分析

在 【深入浅出Spring Security(二)】Spring Security的实现原理 中小编阐述了默认加载的过滤器,其中有个叫 CsrfFilter 的过滤器,它即是用来处理 CSRF 攻击的。

下面是它核心方法的源码

	@Override
	protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain filterChain)
			throws ServletException, IOException {
		request.setAttribute(HttpServletResponse.class.getName(), response);
		// 从请求 Cookie 中找 csrf 令牌
		CsrfToken csrfToken = this.tokenRepository.loadToken(request);
		// 是否存在令牌的标志
		boolean missingToken = (csrfToken == null);
		if (missingToken) {
			// 如果不存在的话就会去生成一个令牌,并存到 tokenRepository 仓库中
			csrfToken = this.tokenRepository.generateToken(request);
			this.tokenRepository.saveToken(csrfToken, request, response);
		}
		// 将 csrf 令牌放入 Request 作用域中
		request.setAttribute(CsrfToken.class.getName(), csrfToken);
		request.setAttribute(csrfToken.getParameterName(), csrfToken);
		// 判断请求方式,判断请求是否需要 CSRF 保护
		if (!this.requireCsrfProtectionMatcher.matches(request)) {
			if (this.logger.isTraceEnabled()) {
				this.logger.trace("Did not protect against CSRF since request did not match "
						+ this.requireCsrfProtectionMatcher);
			}
			filterChain.doFilter(request, response);
			return;
		}
		// 从请求头中获取名为 X-XSRF-TOKEN 令牌值
		String actualToken = request.getHeader(csrfToken.getHeaderName());

		if (actualToken == null) {
		// 请求头中没有的话,就从请求参数中获取名为 _csrf 的令牌值
			actualToken = request.getParameter(csrfToken.getParameterName());
		}
		// 将请求Cookie中的和请求参数/请求头中的 CSRF 令牌进行比对
		// 当然请求Cookie中不包含的话,会重新获取一个然后
		// 一致的话就放行,否则打印日志过滤掉该请求
		if (!equalsConstantTime(csrfToken.getToken(), actualToken)) {
			this.logger.debug(
					LogMessage.of(() -> "Invalid CSRF token found for " + UrlUtils.buildFullRequestUrl(request)));
			AccessDeniedException exception = (!missingToken) ? new InvalidCsrfTokenException(csrfToken, actualToken)
					: new MissingCsrfTokenException(actualToken);
			this.accessDeniedHandler.handle(request, response, exception);
			return;
		}
		filterChain.doFilter(request, response);
	}

源码一些方法的细究

  • 如果请求中的 Cookie 不包含 CSRF 令牌信息的话,会通过 CsrfTokenRepository 中的 saveToken 方法 去存一个 Csrf 令牌(以Cookie的形式)放入 response 中响应回客户端。
    在这里插入图片描述

  • 下面红框框圈到的请求方式不参与 CSRF 防护。
    在这里插入图片描述

  • 如果是把 Csrf 令牌放入到请求头中去处理 Csrf 的话,请求头中参数名应该为X-XSRF-TOKEN;如果是把 Csrf 令牌放入到请求参数中处理 Csrf 的话,那请求参数中的参数名应该为_csrf
    在这里插入图片描述

  • 如果请求方式满足,但请求参数或者请求头中没有 Csrf 对应的令牌的话,那请求是无法通过的。
    在这里插入图片描述

测试

前后端分离开发时,需要将生成的 csrf 令牌放入到 Cookie 中,并在请求时获取 Cookie 中令牌信息进行提交即可(以请求头或者请求参数的形式提交)。

自定义认证过滤器处理登录认证

public class LoginFilter extends UsernamePasswordAuthenticationFilter {


    public LoginFilter() {
    }

    public LoginFilter(AuthenticationManager authenticationManager) {
        super(authenticationManager);
    }

    @Override
    public Authentication attemptAuthentication(HttpServletRequest request, HttpServletResponse response) throws AuthenticationException {

        if (!request.getMethod().equals("POST")) {
            throw new AuthenticationServiceException("Authentication method not supported: " + request.getMethod());
        }
        if(request.getContentType().equalsIgnoreCase(MediaType.APPLICATION_JSON_VALUE)){
            try {
                Map<String,String> loginInfo = JSONObject.parseObject(request.getInputStream(), Map.class);
                String username = loginInfo.get(getUsernameParameter());
                String password = loginInfo.get(getPasswordParameter());
                UsernamePasswordAuthenticationToken auth = new UsernamePasswordAuthenticationToken(username, password);
                setDetails(request,auth);
                return getAuthenticationManager().authenticate(auth);
            } catch (IOException e) {
                throw new RuntimeException(e);
            }
        }
        return super.attemptAuthentication(request, response);
    }
}

Spring Security 对应的安全配置信息

@EnableWebSecurity
public class SecurityConfig {

    @Bean
    public UserDetailsService userDetailsService(){
        return new InMemoryUserDetailsManager(
                User.withUsername("root")
                        .password("{noop}123")
                        .roles("admin")
                        .build()
        );
    }

    @Bean
    public AuthenticationManager authenticationManager(HttpSecurity http) throws Exception {
        return http.getSharedObject(AuthenticationManagerBuilder.class)
                .userDetailsService(userDetailsService())
                .and()
                .build();
    }

    @Bean
    public SecurityFilterChain securityFilterChain(HttpSecurity http) throws Exception {
        return http.authorizeRequests()
                .anyRequest()
                .authenticated()
                .and()
                .logout()
                .logoutUrl("/api/auth/logout")
                .and()
                .addFilterBefore(loginFilter(http), LogoutFilter.class)
                .csrf()
                .csrfTokenRepository(CookieCsrfTokenRepository.withHttpOnlyFalse()) // 将令牌保存到 Cookie 中,允许 Cookie 前端获取
                .and().build();
    }

    @Bean
    public LoginFilter loginFilter(HttpSecurity http) throws Exception {
        LoginFilter loginFilter = new LoginFilter(authenticationManager(http));
        loginFilter.setFilterProcessesUrl("/api/auth/login");
        loginFilter.setAuthenticationFailureHandler(this::onAuthenticationFailure);
        loginFilter.setAuthenticationSuccessHandler(this::onAuthenticationSuccess);
        return loginFilter;
    }

    @Resource
    private ObjectMapper objectMapper;
    private void onAuthenticationFailure(HttpServletRequest request, HttpServletResponse response,
                                         AuthenticationException exception) throws IOException, ServletException {
        response.setCharacterEncoding("utf-8");
        response.setContentType("text/html;charset=utf-8");
        PrintWriter out = response.getWriter();
        out.print(objectMapper.writeValueAsString(JsonData.failure(401,exception.getMessage())));
        out.close();
    }


    private void onAuthenticationSuccess(HttpServletRequest request, HttpServletResponse response,
                                         Authentication authentication) throws IOException, ServletException{
        response.setCharacterEncoding("utf-8");
        response.setContentType("text/html;charset=utf-8");
        PrintWriter out = response.getWriter();
        if(request.getRequestURI().endsWith("/login")){
            Map<String,Object> info = new HashMap<>();
            info.put("msg","登录成功");
            info.put("用户信息", SecurityContextHolder.getContext().getAuthentication().getPrincipal());
            out.write(objectMapper.writeValueAsString(JsonData.success(info)));
        }
        else if(request.getRequestURI().endsWith("/logout"))
            out.write(objectMapper.writeValueAsString(JsonData.success("注销成功")));
        // out.write(JSONObject.toJSONString(JsonData.success("登录成功")));
        out.close();
    }
}

测试结果

请求头中输入参数测试

请添加图片描述

Url 请求参数中输入参数测试

请添加图片描述

五、总结

  • CSRF(Cross-Site Request Forgery 跨站请求伪造),也可称为一键式攻击(one-click-attack),通常缩写为 CSRF 或者 XSRF。CSRF 攻击是一种挟持用户在当前已登录的浏览器上发送恶意请求的攻击方法。
  • Spring Security 默认开启 CSRF 防御,当开启 CSRF 防御时,即安全过滤器链 SecurityFilterChain 中会多一个名为 CsrfFilter 的过滤器,该过滤器可以说是 CSRF 防御认证的过程。
  • 前后端分离使用 CSRF 漏洞保护,首先是得从 Cookie 中获取对应的 xsrf(即csrf)令牌信息,享受服务器端的服务需要在请求参数中或者请求头中配置这个令牌信息(这里请求方式不包括GET、HEAD、OPTIONS、TRACE,如果是这四个请求方式,过滤器会直接放行),请求参数中配置名为_csrf,值是令牌信息;请求头的话配置名是X-XSRF-TOKEN,值是令牌信息。这样在一定程度上就提高了系统的安全性,不刻意去搞的话,这种方式很大程度上防御了跨站请求伪造(CSRF)。
  1. 请求参数中携带令牌
key: _csrf
value: "???"
  1. 请求头中携带令牌
X-XSRF-TOKEN:value

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

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

相关文章

乐盟互动申请纳斯达克IPO上市,募资2000万美元

来源&#xff1a;猛兽财经 作者&#xff1a;猛兽财经 猛兽财经获悉&#xff0c;来自北京的程序化广告平台【乐盟互动】近期已向美国证券交易委员会&#xff08;SEC&#xff09;提交招股书&#xff0c;申请在纳斯达克IPO上市&#xff0c;股票代码&#xff08;LIAI&#xff09;&…

SpringBoot社区小区物业管理停车场系统(Java+Layui+MyBatis+Python+Mysql)

wx供重浩&#xff1a;创享日记 对话框发送&#xff1a;69小区 获取完整源码源文件说明文档数据库文件 项目特色 本项目使用现行主流技术与架构模式&#xff08;控制层、服务层、数据层&#xff09;代码结构清晰&#xff0c;严格遵循模块化、组件化、接口化思想&#xff1b;关…

mysq在RR级别怎么解决不可重复读和幻读

1、定义 不可重复读&#xff1a; 事务1读取一行&#xff0c;事务2然后修改或删除该行数据并且提交事务&#xff0c;事务1再次读取结果不一样&#xff1b; 幻读&#xff1a;事务1按条件读取查询数据&#xff0c;事务2按照同样的条件新增一条或多条数据并且提交事务&#xff0c…

mysql8查看大事务

文章目录 1、查看大事务的原因2、构建测试数据3、模拟大事务场景4、查询mysql的事务5、查询大事务的详情 1、查看大事务的原因 大事务的特点是执行时间长&#xff0c;长期占有锁不释放&#xff0c;导致其他想操作同一行数据的线程阻塞&#xff0c;如果客户端设置了超时时间&am…

单正态总体和双正态总体的假设检验

1.单正态总体和双正态总体的假设检验 笔者之前的相关笔记&#xff1a; 1.正态总体下常见的抽样分布 2.假设检验&#xff08;Hypothesis Testing&#xff09; 个人理解假设检验&#xff1a;先对总体参数提出一个假设值&#xff0c;利用样本信息判断这一假设是采取拒绝该假设还是…

opencv人与摄像头距离检测

参考&#xff1a; https://chtseng.wordpress.com/2018/09/18/%E5%A6%82%E4%BD%95%E4%BC%B0%E7%AE%97%E5%89%8D%E6%96%B9%E4%BA%BA%E7%89%A9%E7%9A%84%E8%B7%9D%E9%9B%A2/ WeChat_20230611160620 1、cv2加载摄像头慢解决方法&#xff0c;单独重新cv2.VideoCapture() https://b…

使用vue进行Lodop打印的一些方法

文章目录 使用Lodop进行打印的一般步骤vue中使用lodopkr-print-designer简介打印模板设计器打印预览模板设计页面安装引入 Lodop是一个JavaScript控件&#xff0c;用于在Web应用程序中进行打印操作。 使用Lodop进行打印的一般步骤 下载Lodop控件&#xff1a;首先&#xff0c;你…

对比学习做了什么?

什么是对比学习&#xff1f; 对比学习貌似处于“无明确定义、有指导原则”的状态 什么是对比学习呢&#xff1f;(这个是微信链接)全文比较长&#xff0c;但是逻辑框架还是不错的。 如果想要更快速的了解什么是对比学习或者说对比学习是怎么做的&#xff0c;可以看SimCLR这个模…

全网最详细,软件测试-性能测试岗面试题总结(大全)

目录&#xff1a;导读 前言一、Python编程入门到精通二、接口自动化项目实战三、Web自动化项目实战四、App自动化项目实战五、一线大厂简历六、测试开发DevOps体系七、常用自动化测试工具八、JMeter性能测试九、总结&#xff08;尾部小惊喜&#xff09; 前言 描述一下你们公司…

论文阅读:Denoising Diffusion Probabilistic Models

论文阅读&#xff1a;Denoising Diffusion Probabilistic Models 最近一两年&#xff0c;在图像生成领域&#xff0c;扩散模型受到了越来越多的关注&#xff0c;特别是随着 DALL-E2 以及 Midjourney 的持续火爆&#xff0c;扩散模型也变得越来越流行&#xff0c;之前很多基于 …

C++ 参数的三种传递方式和应用场景

C 参数的三种传递方式分别是值传递、指针传递和引用传递。 值传递 值传递的实质 将实参的值&#xff08;a、b&#xff09;复制到形参(m、n)相应的存储单元中&#xff0c;即形参和实参分别占用不同的存储单元。 值传递的特点 值传递的特点是单向传递&#xff0c;即主调函数…

Java Web开发实战经典学习过程笔记

Java Web开发实战经典学习简单笔记 第一章 Java Web 开发简介 1.胖客户端程序指的是&#xff0c;当一个程序运行时需要一个单独的客户端程序支持(如&#xff1a;QQ)。瘦客户端程序在操作时不需要任何其他程序的安装(如&#xff1a;登录网上论坛&#xff0c;只需浏览器即可)。 2…

I.MX6ull UART

一 简介 UART 全称叫做串行接口&#xff0c;通常也叫做 COM 接口&#xff0c;串行接口指的是数据一个一个的顺序传输&#xff0c;通信线路简单。使用两条线即可实现双向通信&#xff0c;一条用于发送&#xff0c;一条用于接收。串口通信距离远&#xff0c;但是速度相对会低&…

Self-Attention 自注意力机制

输出形式 李宏毅讲到&#xff1a; 模型的输入是只有一种形式——词向量但是输出的形式却是不唯一的&#xff0c;主要有以下三种&#xff1a; 每一个向量对应一个输出(多对多&#xff0c;且一一对应) 每个序列只有一个输出(多对一) 一个序列对应一个序列(多对多&#xff0c;长…

MySQL 索引的10 个核心要点

文章目录 &#x1f349;1. 索引底层采用什么数据结构&#xff1f;为什么不用hash&#x1f349;2. B树与B树区别&#xff1f;为何用B树&#xff1f;&#x1f349;3. 自增主键理解&#xff1f;&#x1f349;4. 为什么自增主键不连续&#x1f349;5. Innodb为什么推荐用自增ID&…

代码随想录第59天

1.下一个更大元素II 有两种方法&#xff1a; 1.把两个一样的数组拼起来&#xff1a; // 版本一 class Solution { public:vector<int> nextGreaterElements(vector<int>& nums) {// 拼接一个新的numsvector<int> nums1(nums.begin(), nums.end());nu…

Chapter7: SpringBoot与数据访问

尚硅谷SpringBoot顶尖教程 1. JDBC 1.1 依赖及配置 <dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-jdbc</artifactId> </dependency> <dependency><groupId>mysql</groupId…

《Reinforcement Learning: An Introduction》第4章笔记

Chapter 4 Dynamic Programming 动态规划&#xff08;Dynamic Programming&#xff0c;DP&#xff09;是一类在给定完备环境模型的MDP后用来计算最优策略的算法。动态规划算法在强化学习中因为&#xff1a;1. 假设有一个完美的环境模型&#xff1b;2. 极大的计算代价 实际用处…

树莓派4B连接不了产品开的热点

目的 关于树莓派连接不了产品开的5G热点&#xff0c; 当时还是一头雾水。 参考这篇博客 把思路方向转向了频率&#xff0c; 信道&#xff0c; 通过给的产品A相关规格说明wifi 5.18GHz, 信道36。 于是乎我两款产品A、产品B为例。 树莓派是能连接产品B开的热点&#xff08;5.74…

【Unity SRP】实现基础的Temporal AA(未完)

写在前面 【技术美术图形部分】简述主流及新的抗锯齿技术&#xff0c;花了点时间盘点了一些主流AA技术&#xff0c;再在SRP下的URP管线中实现一下目前游戏用得比较多的TAA。参考Unity的TAA&#xff08;比较容易懂&#xff09;以及sienaiwun的实现思路&#xff0c;也参考了很多…