JWT 鉴权:Token 存储在 LocalStorage 还是 Cookie 中?——一场关于安全与便利的深度探讨
大家好,欢迎来到今天的讲座。我是你们的技术导师,今天我们要深入探讨一个看似简单却极其关键的问题:
JWT(JSON Web Token)应该存在 LocalStorage 还是 Cookie 中?
这个问题在前端开发中频繁出现,尤其是在使用单页应用(SPA)、微前端架构或前后端分离项目时。很多开发者凭直觉选择其中一种方式,但往往忽略了背后的安全性、兼容性、易用性和业务场景差异。
我们将从以下几个维度展开:
- 什么是 JWT?
- LocalStorage vs Cookie 的基本区别
- 安全风险对比(XSS、CSRF)
- 实际代码示例:如何分别存储和读取
- 最佳实践建议 + 表格总结
- 常见误区澄清
一、什么是 JWT?
JWT 是一种开放标准(RFC 7519),用于在网络应用环境间安全地传输信息。它由三部分组成:
- Header:声明类型(JWT)和签名算法(如 HMAC SHA256)
- Payload:包含用户身份、权限等自定义数据(可被解码)
- Signature:防止篡改,通过密钥对前两部分进行签名
例如一个典型的 JWT 字符串如下:
eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiIxMjM0NTY3ODkwIiwibmFtZSI6IkpvaG4gRG9lIiwiaWF0IjoxNTE2MjM5MDIyfQ.SflKxwRJSMeKKF2QT4fwpMeJf36POk6yJV_adQssw5c
它通常作为 HTTP 请求头中的 Authorization: Bearer <token> 发送给后端进行验证。
二、LocalStorage 和 Cookie 的本质区别
| 特性 | LocalStorage | Cookie |
|---|---|---|
| 存储位置 | 浏览器内存(持久化到磁盘) | 浏览器缓存区(随请求自动发送) |
| 生命周期 | 手动清除或关闭浏览器才失效 | 可设置过期时间(Expires/Max-Age) |
| 是否随请求自动发送 | ❌ 不会 | ✅ 自动附加到同域请求中 |
| 跨域访问 | ❌ 同源策略限制 | ✅ 可设置 SameSite 属性控制跨站行为 |
| 安全性 | 易受 XSS 攻击 | 更容易防范 CSRF(配合 SameSite=Strict/Lax) |
| 大小限制 | ~5MB(现代浏览器) | ~4KB(每个 cookie) |
关键点解析:
- LocalStorage:适合长期保存状态,比如用户登录态,但它不会自动发送给服务器。
- Cookie:天生为 HTTP 设计,可以配置为仅限 HTTPS、HttpOnly(防 JS 访问)、SameSite(防 CSRF)。
三、安全风险对比:谁更危险?
1. XSS(跨站脚本攻击)
如果攻击者注入恶意脚本(如 <script src="malicious.js">),那么:
-
LocalStorage 中的 Token:会被 JavaScript 直接读取 → 极易被盗!
// 如果页面被 XSS 注入: alert(localStorage.getItem('authToken')); // 👉 攻击者拿到 token! -
Cookie 中的 Token:若设置了
HttpOnly=true,JavaScript 无法访问 → 更安全!
✅ 推荐做法:将 JWT 存放在带 HttpOnly 标志的 Cookie 中,避免 XSS 泄露。
2. CSRF(跨站请求伪造)
这是另一个经典问题:攻击者诱导用户点击链接,触发非预期操作。
- LocalStorage 方案:因为没有自动携带 Token,所以 CSRF 成本高(需要手动加 header)→ 相对安全。
- Cookie 方案:默认会随请求发送,容易被利用(除非启用 SameSite=Strict/Lax)。
⚠️ 注意:如果你把 JWT 放在 Cookie 中但未设置 SameSite=Strict 或 Lax,就等于给 CSRF 开门!
✅ 正确做法:使用 SameSite=Lax 或 Strict + Secure(HTTPS)+ HttpOnly 组合。
四、实际代码示例:两种方案实现
示例场景:用户登录成功后,保存 JWT 并在后续请求中带上它
✅ 方案一:存储在 Cookie 中(推荐用于大多数 Web 应用)
// 登录接口返回后,设置 HttpOnly + Secure + SameSite=Strict 的 Cookie
function setAuthCookie(token) {
document.cookie = `authToken=${token}; Path=/; Secure; HttpOnly; SameSite=Strict`;
}
// 获取 Cookie 中的 Token(只能后端处理,前端不能直接读)
function getAuthTokenFromCookie() {
const cookies = document.cookie.split('; ');
for (let cookie of cookies) {
const [name, value] = cookie.split('=');
if (name === 'authToken') return value;
}
return null;
}
// 前端发起请求时,必须靠 axios 拦截器自动附带 Cookie(由浏览器自动完成)
axios.interceptors.request.use(config => {
config.withCredentials = true; // 必须显式开启,否则不发 Cookie
return config;
});
后端 Node.js Express 示例(Express.js + cookie-parser):
app.use(cookieParser());
app.post('/login', (req, res) => {
const token = jwt.sign({ userId: user.id }, process.env.JWT_SECRET, { expiresIn: '1h' });
res.cookie('authToken', token, {
httpOnly: true,
secure: true, // 仅 HTTPS 使用
sameSite: 'strict', // 防止 CSRF
maxAge: 3600000 // 1小时
});
res.json({ success: true });
});
✅ 方案二:存储在 LocalStorage 中(适用于某些特殊场景)
// 登录成功后保存到 localStorage
function saveAuthTokenToLS(token) {
localStorage.setItem('authToken', token);
}
// 从 localStorage 获取 Token
function getAuthTokenFromLS() {
return localStorage.getItem('authToken');
}
// 在 axios 请求拦截器中手动添加 Authorization Header
axios.interceptors.request.use(config => {
const token = getAuthTokenFromLS();
if (token) {
config.headers.Authorization = `Bearer ${token}`;
}
return config;
});
⚠️ 缺点:前端 JS 可以随意读写,一旦 XSS 成功,Token 立刻暴露!
五、最佳实践建议(结合业务场景)
| 场景 | 推荐方式 | 理由 |
|---|---|---|
| 单页应用(SPA) | ✅ Cookie + HttpOnly + SameSite=Strict | 安全性强,自动携带,适合 React/Vue/Angular |
| 移动 Web App(WebView) | ✅ Cookie(iOS Safari 限制较多) | 需注意 iOS 上 Cookie 的兼容性问题 |
| API 服务调用(无前端 UI) | ✅ Header(Bearer Token) | 不涉及浏览器存储,无需考虑 XSS |
| 微前端架构(多个子应用共用鉴权) | ✅ Cookie(统一管理) | 各子应用共享同一域名下的 Cookie,便于统一登出 |
| 对安全性要求极高(金融类) | ✅ Cookie + HttpOnly + SameSite=Strict + Refresh Token | 多层防护,降低泄露风险 |
| 快速原型开发 / 内部工具 | ✅ LocalStorage | 快速调试方便,但上线前务必替换 |
📌 小贴士:
- 永远不要同时使用两者!
- 不要把敏感信息放 Payload 中(如密码、身份证号)
- 定期刷新 Token(Refresh Token + Access Token 分离机制)
六、常见误区澄清
❌ 误区 1:“我用了 LocalStorage 就很安全”
❌ 错误!LocalStorage 没有任何保护机制,只要页面被注入脚本,就能窃取 Token。尤其在 SPA 中,大量第三方库可能引入漏洞。
❌ 误区 2:“我把 Token 放 Cookie 就一定能防 CSRF”
❌ 错误!必须配合 SameSite=Strict 或 Lax,否则仍然可能被跨站请求利用。
❌ 误区 3:“HttpOnly 的 Cookie 前端就不能用了”
✅ 正确!但你可以通过后端代理(如 /api/me 返回当前用户信息)来间接获取用户上下文,而不是让前端直接读 Cookie。
❌ 误区 4:“所有项目都应该用 Cookie”
❌ 错误!有些场景不适合 Cookie(如移动端 WebView、嵌套 iframe),此时可用 LocalStorage 或 Header 方式。
结语:选择不是“哪个更好”,而是“哪个更适合”
我们不是在争论 LocalStorage 和 Cookie 的优劣,而是在理解它们各自的适用边界。
- 如果你追求极致的安全性和自动管理(尤其是企业级 Web 应用),请优先选择 带 HttpOnly、Secure、SameSite=Strict 的 Cookie。
- 如果你在做快速原型、内部系统或移动 H5 页面,且能接受一定风险,Local Storage 也可以作为一种妥协方案。
记住一句话:
“安全不是完美的,而是权衡后的最优解。”
希望今天的分享让你对 JWT 存储有了更清晰的认知。下次再遇到这个问题时,不妨问问自己:“我的业务场景是否允许 XSS?是否担心 CSRF?是否需要跨域支持?” —— 答案就在那里。
谢谢大家!欢迎留言讨论你的实战经验 😊