jwt应存于httponly+secure+samesite cookie中,因其可防xss窃取、强制https传输、抵御csrf攻击且服务端可控过期;localstorage虽易用但易受xss侵害、需手动管理过期与请求头,安全性较低。

JWT 存在的意义是传递身份凭证,而它存哪儿,直接决定了这个凭证有多容易被偷、多难被用、多麻烦被维护。localStorage 和 Cookie 不是“选一个就好”的简单选项,而是两种不同安全逻辑的代表——一个把控制权交给前端代码,一个把关键防护交给浏览器机制。
localStorage:方便得危险
它就像把钥匙挂在门把手上——你自己随时能拿,但路过的人也能顺手抄走。
-
XSS 是致命短板:只要页面存在一处未过滤的用户输入(比如评论框、搜索栏),攻击者注入一行
localStorage.getItem('auth_token')就能直接拿走 JWT -
没有自动过期:token 到期了,localStorage 不会自己消失;你得靠 JS 定时检查 payload 的
exp字段,一旦出错或漏判,过期 token 仍可被滥用 -
手动携带请求:每次调用 API 都得写
headers: { Authorization: 'Bearer ' + token },容易遗漏,也增加前端逻辑负担 - 跨域共享受限:a.example.com 写的 localStorage,b.example.com 根本读不到,微前端或多子域场景下很被动
Cookie(带 HttpOnly + Secure + SameSite):笨重但可靠
它像把钥匙锁进保险柜,只允许特定通道(HTTP 请求)使用,连主人都不能直接伸手去摸。
-
HttpOnly 拦住 XSS:恶意脚本执行时,
document.cookie返回空字符串,JS 无法读取 token 内容 - Secure 强制 HTTPS:防止中间人明文截获 token,非加密连接下 cookie 根本不会发送
- SameSite=Strict/Lax 控制跨站行为:有效阻断大部分 CSRF 场景,比如从恶意网站发起的表单提交不会带上该 cookie
-
服务端可控生命周期:设置
Max-Age=3600,1 小时后浏览器自动丢弃,无需前端干预
便利性不是牺牲安全的理由
有人觉得 localStorage “写起来快”“调试方便”“不用配 CORS”,但这恰恰掩盖了真实风险。现实是:
- 现代框架(如 Next.js、Nuxt)已内置 Cookie 管理工具,
cookies().set()一行就能安全写入 - CSRF 并非无解:配合
SameSite=Strict或双提交 Cookie(Double Submit),比手动管理 localStorage 过期逻辑更稳定 - 真正需要“前端随时读 token”的场景极少——用户信息展示、权限按钮显隐等,完全可用内存缓存(in-memory)+ 服务端接口兜底,不必暴露原始 JWT
折中方案:分层存储更务实
不强求“全用 Cookie”或“全用 localStorage”,而是按令牌类型分工:
- 访问令牌(Access Token):短时效(如 15 分钟),存内存变量,随每次请求由拦截器自动注入 header
- 刷新令牌(Refresh Token):长时效(如 7 天),必须存 HttpOnly + Secure Cookie,禁止 JS 访问,仅用于后台静默换 token
- 用户基础信息(如 name、avatar):可存 localStorage,但需脱敏、不包含角色/权限等敏感字段











