核心结论:不要把 JWT 存在 localStorage /sessionStorage 做身份认证,极易遭受 XSS 窃取;优先使用 HttpOnly + Secure Cookie 存储 JWT,这是业界最安全方案。
方案对比
表格
方案一:HttpOnly Cookie(生产最优)
后端 Set‑Cookie 关键属性(重点)
Set‑Cookie: access_token=xxxJWTxxx;
HttpOnly; // JS document.cookie 无法读取,抵御XSS偷令牌
Secure; // 仅HTTPS下发送,生产必须开启;http本地开发关闭
SameSite=Strict/Lax; // 防御CSRF,Lax兼容性更好
Path=/;
Max‑Age=3600; // token过期时间
Access Token:短期有效期(15‑60min),放在 HttpOnly Cookie
Refresh Token:长时效,同样放入 HttpOnly Cookie,用于无感刷新令牌
前端不需要写任何 JS 存取 token,浏览器自动在请求头携带 Cookie。
CSRF 配套防御(必须做)
SameSite 不能 100% 兜底,建议搭配:
SameSite=Lax或StrictCSRF Token:后端返回一个 JS 可读取的 token(放 localStorage/meta),每次 POST/PUT 请求放在请求头
X‑CSRF‑Token,后端校验。
流程:浏览器自动带 Cookie (JWT) + 请求头携带 CSRF 随机串,双重校验。
方案二:内存存储(SPA 纯前端,无 HttpOnly 条件)
没有办法设置后端 Cookie 时的降级方案,刷新页面令牌丢失,需要登录或者调用刷新接口重新获取。
JWT 仅保存在 Vue/React state、pinia/vuex 内存变量中,绝不写入 localStorage
页面初始化,调用后端接口获取 access token,放在内存
axios 请求拦截器从内存取出放到
Authorization: Bearer xxx页面刷新内存清空,跳转登录;或者用 HttpOnly 的 refresh cookie 静默刷新拿到新 access 放入内存。
// axios示例:token只存在内存pinia,不落地存储
const authStore = useAuthStore()
request.interceptors.request.use(config => {
if(authStore.accessToken){
config.headers.Authorization = `Bearer ${authStore.accessToken}`
}
return config
})
优点:XSS 拿到 JS 环境也拿不到令牌;缺点:刷新页面丢失会话。
❌为什么不推荐 localStorage 存 JWT
XSS 漏洞一旦出现,攻击者 JS 直接执行
localStorage.getItem('token'),把 JWT 发送到黑客服务器,完全接管账号。很多 SPA 框架(Vue/React)如果存在富文本、渲染用户输入,很容易出现 XSS。
很多老教程写 localStorage 存 JWT,属于过时不安全实践。
Refresh Token 最佳实践
AccessToken:短期,HttpOnly Cookie;
RefreshToken:更长时效,HttpOnly Cookie;
刷新接口
/refresh:接收 Cookie 里的 refresh token,返回新 access token;黑名单机制:登出时后端把 refresh token 加入黑名单,即使 token 没过期直接作废;
支持主动吊销会话,防止令牌泄露永久可用。
常见坑点
❌ 开启 HttpOnly,前端 JS 就拿不到 token,不要试图
document.cookie读取 JWT,设计如此。❌ 开发环境 http 不能设置 Secure,生产环境必须 HTTPS+Secure。
❌ SameSite=None 的时候必须带上 Secure,否则浏览器拒绝写入 Cookie。
❌ 不要把敏感信息放在 JWT payload,JWT 只是编码不是加密,前端可以解码看到 payload。
简单选型总结
后端可控,优先:HttpOnly + Secure + SameSite Cookie 存 JWT,搭配 CSRF Token(最安全)
后端不能改 Cookie:内存保存 access token,refresh token 尽量走 HttpOnly Cookie
禁止:localStorage/sessionStorage 存放登录身份 JWT
补充:如果必须 localStorage(遗留项目,不得已)
只能作为妥协,必须额外做防护:
CSP 内容安全策略,禁止内联脚本,降低 XSS 攻击面
JWT 有效期尽量短
做好 XSS 漏洞审计,过滤所有用户输入
本文原创作者:易君召,详见:https://www.yijunzhao.cn/authors/yijunzhao,转载请注明出处。
原文链接
欢迎访问 小易撩挨踢