如何正确实现Anti-CSRF Token防御机制并避免常见误区

♫丹丹 安全 阅读 1,876
赞 98 收藏
二维码
手机扫码查看
反馈

开头直接说:为什么我要对比这几种Anti-CSRF方案

最近在开发一个新项目的时候,遇到了CSRF防护的需求。说实话,Anti-CSRF Token这种东西我之前用过不少次,但这次因为涉及到前后端分离架构,再加上需要兼容第三方系统调用,选型变得有点复杂。所以干脆把常见的几种方案都试了一遍,踩了几个坑之后,总算理清了思路。

如何正确实现Anti-CSRF Token防御机制并避免常见误区

核心问题其实就一个:怎么选一个既安全又不折腾的方案?这里我把常用的几种Anti-CSRF实现方式拿出来对比一下,包括:

  • 基于Cookie的Double Submit Cookie模式
  • 基于Session的Token存储模式
  • 基于JWT的自定义Header模式

我的结论是:如果你用的是前后端分离架构,JWT方案更灵活;如果是传统服务端渲染应用,Double Submit Cookie会省事很多。 下面详细聊聊。

核心代码展示:三种方案的具体实现

先上代码,直观感受一下每种方案的复杂度。

1. Double Submit Cookie模式

这个方案的核心思想是,前端和后端各自存储一份相同的Token,然后在请求中进行校验。

// 后端生成Token并设置到Cookie
const csrfToken = generateRandomToken();
res.cookie('csrf-token', csrfToken, { httpOnly: false, secure: true });

// 前端在请求头中携带Token
fetch('https://jztheme.com/api/endpoint', {
  method: 'POST',
  headers: {
    'X-CSRF-Token': getCookieValue('csrf-token') // 从Cookie中读取Token
  },
  body: JSON.stringify({ data: 'example' })
});

后端接收到请求时,会对比请求头中的X-CSRF-Token和Cookie中的csrf-token是否一致。

2. Session存储模式

传统的Session模式比较简单粗暴,Token直接存储在服务器端的Session中,客户端只需要带着Session ID就能完成校验。

// 后端生成Token并存储到Session
req.session.csrfToken = generateRandomToken();

// 前端在表单中或者请求头中携带Token
fetch('https://jztheme.com/api/endpoint', {
  method: 'POST',
  headers: {
    'X-CSRF-Token': 'abc123' // 这个Token需要通过其他接口提前获取
  },
  body: JSON.stringify({ data: 'example' })
});

这种方式适合传统的服务端渲染应用,但在前后端分离场景下,Token的传递会稍微麻烦一点。

3. JWT自定义Header模式

JWT的方式比较现代化,特别适合前后端分离架构。Token本身可以包含一些额外信息,比如用户ID、过期时间等。

// 后端生成JWT Token
const jwtToken = signJwt({ userId: 123 }, 'secret-key');

// 前端在请求头中携带JWT Token
fetch('https://jztheme.com/api/endpoint', {
  method: 'POST',
  headers: {
    'Authorization': Bearer ${jwtToken} // 使用Authorization头
  },
  body: JSON.stringify({ data: 'example' })
});

后端解析JWT Token时,可以直接验证签名和过期时间,顺带完成CSRF校验。

谁更灵活?谁更省事?

从灵活性来看,JWT方案明显胜出。它不仅可以用来做CSRF防护,还能顺便解决身份认证的问题。比如,我在项目里用了JWT之后,发现连登录状态管理都简化了不少。不过,JWT有个小坑:如果Token被盗,攻击者可以在有效期内继续使用它,除非你引入黑名单机制。

Double Submit Cookie模式则非常省事,特别适合那些不想花太多精力在安全机制上的项目。它的原理简单,代码也少,基本没有学习成本。但缺点是,如果你的Cookie配置不当(比如没启用HttpOnlySecure),容易被XSS攻击。

至于Session存储模式,我个人已经很少用了。虽然它很传统,也很成熟,但在分布式系统中,Session同步是个大问题。而且每次请求都要查Session,性能开销也不小。

性能对比:差距比我想象的大

说到性能,JWT方案的表现让我有点意外。一开始我以为JWT解析会比Double Submit Cookie慢,但实际上,JWT解析的开销几乎可以忽略不计,尤其是在Node.js环境下。

而Double Submit Cookie模式在高并发场景下可能会遇到瓶颈,因为它需要同时读取Cookie和请求头,多了一次I/O操作。当然,这种性能差距只有在极端情况下才会显现出来,90%的项目其实根本不用考虑。

Session模式的性能是最差的,尤其是当你用Redis或者其他外部存储来同步Session时,延迟会明显增加。我之前在一个电商项目里踩过这个坑,后来果断换成了JWT。

我的选型逻辑

最终的选择还是要看具体场景。如果是小型项目或者对性能要求不高,我会优先选择Double Submit Cookie模式,因为它真的太省事了,代码量最少,维护成本也低。

但如果项目规模较大,或者需要支持分布式部署,我肯定会选JWT方案。虽然前期可能需要多写几行代码,但从长远来看,它的扩展性和灵活性绝对值得投入。

至于Session模式,我个人已经不太推荐了,除非你的项目是纯传统的服务端渲染应用。

踩坑提醒:这三点一定注意

最后再提醒几个容易踩的坑:

  1. Cookie配置一定要正确:无论是Double Submit Cookie还是JWT,Cookie的HttpOnlySecure属性都不能漏掉,否则很容易被XSS攻击。
  2. Token过期时间要合理:JWT的过期时间不能太长,也不能太短。我一般设置为1小时,配合Refresh Token机制。
  3. 前后端时间同步问题:JWT的时间戳校验对服务器时间非常敏感,如果前后端时间不同步,可能会导致Token失效。

以上是我的对比总结,有不同看法欢迎评论区交流。这个技巧的拓展用法还有很多,后续会继续分享这类博客。

本文章不代表JZTHEME立场,仅为作者个人观点 / 研究心得 / 经验分享,旨在交流探讨,供读者参考。
发表评论

暂无评论