security第十集 csrf

关于csrf在第八集中用postman访问 post请求时 已经提到需要 禁用 csrf 才可以访问

http.csrf().disable();

那么到底什么是 csrf 呢?
CSRF(跨站请求伪造)是一种Web安全漏洞,攻击者诱使用户在登录受信任网站的情况下,执行非预期的操作。这种攻击利用了Web应用对用户浏览器的信任。

csrf攻击原理

  1. 用户登录:用户登录到受信任的网站A
  2. 保持会话:网站A在用户浏览器中设置认证cookie
  3. 恶意诱导:用户访问恶意网站B,网站B包含针对网站A的请求
  4. 自动提交:浏览器自动附带网站A的cookie发送请求
  5. 非预期操作:网站A认为这是用户的合法请求并执行

csrf防御机制
服务端生成一个随机 Token(如 abcd1234),存储在 Session 或 Cookie 中
客户端在提交请求时需携带该 Token(通过表单字段或请求头)
服务端校验 Token 是否匹配

关于security中的csrf是默认开启的,是利用一个过滤器 CsrfFilter 实现的。在这个过滤器中使用CsrfTokenRepository的实现类 来 生成,存储 csrfToken。security提供了CsrfTokenRepository的三种实现类

  • HttpSessionCsrfTokenRepository(默认) CSRF Token 会被存储在 HTTP Session 中
  • CookieCsrfTokenRepository CSRF Token 会存入 Cookie(默认名称为 XSRF-TOKEN)
    特点: 适合前后端分离架构 需配合 withHttpOnlyFalse() 允许前端 JavaScript 读取
 http.csrf()
       .csrfTokenRepository(CookieCsrfTokenRepository.withHttpOnlyFalse());
  • LazyCsrfTokenRepository 包装另一个 TokenRepository,仅在需要时生成/存储 Token

下面结合security的 CsrfFilter 过滤器的源码 ,进行分析其大概实现原理

protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain filterChain) throws ServletException, IOException {
        //将 HttpServletResponse 对象存入请求属性,供后续流程使用
        request.setAttribute(HttpServletResponse.class.getName(), response);
        //从 TokenRepository(默认 HttpSessionCsrfTokenRepository)中读取已存储的 Token
        CsrfToken csrfToken = this.tokenRepository.loadToken(request);
        boolean missingToken = csrfToken == null;
        //若 Token 不存在
        if (missingToken) {
        	//生成新 Token
            csrfToken = this.tokenRepository.generateToken(request);
            //保存 Token 到存储库(如 Session 或 Cookie)
            this.tokenRepository.saveToken(csrfToken, request, response);
        }
		/*
		将 Token 存入请求属性,供视图层(如 JSP、Thymeleaf)渲染时使用
		默认规则
			需要防护的请求:POST、PUT、PATCH、DELETE 等修改数据的请求。
			无需防护的请求:GET、HEAD、TRACE、OPTIONS(安全方法)。
		若无需防护:直接放行请求(doFilter)。	
		*/
        request.setAttribute(CsrfToken.class.getName(), csrfToken);
        request.setAttribute(csrfToken.getParameterName(), csrfToken);
		//通过 requireCsrfProtectionMatcher 判断当前请求是否需要 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);
        } else {
        	//获取客户端 Token 优先从请求头(默认 X-CSRF-TOKEN)读取
            String actualToken = request.getHeader(csrfToken.getHeaderName());
            //若头不存在,从请求参数(默认 _csrf)读取(适用于表单提交)
            if (actualToken == null) {
                actualToken = request.getParameter(csrfToken.getParameterName());
            }
			/*
			比对 Token
			使用 equalsConstantTime(防时序攻击的安全比对)比较服务端 Token 和客户端 Token
			若不匹配,抛出异常
				InvalidCsrfTokenException(Token 不合法)
				MissingCsrfTokenException(Token 缺失)
			通过 accessDeniedHandler 返回 403 错误。
			*/
            if (!equalsConstantTime(csrfToken.getToken(), actualToken)) {
                this.logger.debug(LogMessage.of(() -> {
                    return "Invalid CSRF token found for " + UrlUtils.buildFullRequestUrl(request);
                }));
                AccessDeniedException exception = !missingToken ? new InvalidCsrfTokenException(csrfToken, actualToken) : new MissingCsrfTokenException(actualToken);
                this.accessDeniedHandler.handle(request, response, (AccessDeniedException)exception);
            } else {
                filterChain.doFilter(request, response);
            }
        }
    }

完整流程总结

  • 首次请求:

    生成 Token → 存入 Session → 返回给客户端(通过表单或头)。

  • 后续修改数据的请求:

    客户端需携带 Token(头或参数)→ 服务端校验 → 通过则放行,否则拒绝。

  • 安全方法(如 GET):

    跳过校验直接放行。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

凯凯凯凯神

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值