前端鉴权实战:Token 存哪、怎么刷新、怎么防 XSS 与 CSRF

前端鉴权实战:Token 存哪、怎么刷新、怎么防 XSS 与 CSRF

"localStorage 里存 token 有 XSS 风险,那放 cookie 就安全了"------错,CSRF 在等着。

前后端分离项目的鉴权方案,网上一搜全是互相矛盾的答案。这篇不讲理论推演,直接给结论和落地代码:Token 放哪、过期怎么无感刷新、XSS 和 CSRF 各自怎么防。这套方案我用在一个有支付功能的项目里,安全评审一次过。

一、先分清两个威胁,方案才不纠结

威胁 攻击者怎么拿/用你的凭证 防御核心
XSS 注入脚本在你的页面上下文里执行,能读 JS 能读的一切 CSP、不把 token 放 JS 可读的地方、输入输出转义
CSRF 恶意网站诱导浏览器自动携带 cookie 发请求(不需要读任何数据) Cookie SameSite、CSRF Token

记住这句就够:XSS 防的是"读走",CSRF 防的是"带上"。Token 放 localStorage 能防 CSRF(恶意站读不到),但挡不住 XSS;放 cookie 防得住"读走"(httpOnly),但要防 CSRF。没有银弹,是组合拳。

text 复制代码
Access Token(短期,15 分钟)  → 内存中持有,Authorization 头发送
Refresh Token(长期,7~30 天) → httpOnly + Secure + SameSite=Strict cookie

为什么这么分:

  • Access Token 放内存:刷新页面就没了,XSS 读到了也只有 15 分钟寿命,且不参与 CSRF(自定义请求头天然免疫,跨站表单带不上它)。
  • Refresh Token 放 httpOnly cookie :JS 读不到,XSS 偷不走;只发往 /auth/refresh 一个接口,攻击面被锁死;SameSite=Strict 让跨站请求根本不携带它,CSRF 无从谈起。

后端设置 refresh cookie 的关键属性(Express 示例):

js 复制代码
res.cookie("refresh_token", token, {
  httpOnly: true,                  // JS 不可读
  secure: true,                    // 仅 HTTPS
  sameSite: "strict",              // 跨站请求不携带
  path: "/auth/refresh",           // 只在这个路径发送,缩小暴露面
  maxAge: 7 * 24 * 3600 * 1000,
});

path 这一手经常被忽略:refresh token 只需要给刷新接口用,限定 path 后,其他任何请求(包括被 XSS 劫持的请求)都带不上它。

三、无感刷新:用户永远不该看到"请重新登录"

Access Token 15 分钟过期,如果用户被打断到登录页,产品体验就崩了。标准解法:请求拦截器发现 401 → 用 refresh token 换新 token → 重放原请求。Axios 实现:

ts 复制代码
let refreshing: Promise<string> | null = null;

api.interceptors.response.use(undefined, async (error) => {
  if (error.response?.status !== 401 || isRetry(error.config)) throw error;

  refreshing ??= refreshToken().finally(() => (refreshing = null)); // 并发只刷一次
  const newToken = await refreshing;                                 // 所有请求等同一个结果
  error.config.headers.Authorization = `Bearer ${newToken}`;
  error.config._retried = true;
  return api(error.config);                                          // 重放原请求
});

两个必须处理的细节:

  1. 并发去重 :页面一进来发 8 个请求,token 恰好过期,8 个 401 同时到。不去做并发合并,就会发起 8 次刷新、7 次因 refresh token 被轮换而失败、用户被登出。refreshing ??= ... 保证整页只有一个刷新请求,大家等同一个 Promise。
  2. 刷新也失败 :refresh token 过期/被吊销,此时清状态、跳登录 ,并把用户当前页面的 URL 存下来(sessionStorage.redirect = location.href),登录后跳回去。这个"登回原页面"的小细节,留存率差异很明显。

后端配套:refresh token 每次使用后轮换(旧的作废、发新的),并在数据库记录"设备会话"。同一个 refresh token 被第二次使用 = 可能被偷了,直接吊销整个会话。这是 OWASP 推荐的 refresh token rotation。

四、XSS 防御:鉴权方案只是兜底

Token 放内存只是降低损失,XSS 本身还要正面防:

  1. 不拼接 HTML 。React/Vue 默认转义插值,dangerouslySetInnerHTML/v-html 只允许出现在内容经过 DOMPurify 消毒的地方。
  2. CSP 收紧Content-Security-Policy: default-src 'self'; script-src 'self'。禁掉 inline script 和可疑外域,XSS 载荷大多数直接没法加载。注意用了 inline 事件或 JSON.pARSE 水合的要配 nonce。
  3. 依赖也是攻击面 :npm 投毒事件真实发生过,lock 文件提交、CI 用 npm ci、定期 npm audit
  4. 别在 URL、日志里带 token:token 出现在 query string 会进各种访问日志。

五、CSRF:用对了 SameSite,传统 CSRF Token 可以省

CSRF 的成立条件是"浏览器自动携带凭证发跨站请求"。逐条拆解我们的方案:

  • 核心写接口用 Authorization 头 → 跨站表单/图片请求带不上,天然免疫。
  • refresh cookie 是 SameSite=Strict → 跨站请求根本不携带,免疫。
  • 如果站点必须兼容老浏览器、或 refresh cookie 用了 SameSite=Lax,再补一层 CSRF Token(后端下发随机值,写请求带头携带,服务端校验)。Lax 下跨站 POST 不带 cookie,但顶级导航的 GET 会带,所以GET 永远不要有副作用------这条比任何 token 都重要。

六、登出与"记住我":两个容易做错的点

登出不是删前端 token 就完了。后端必须吊销:access token 短命可以等它自然过期,refresh token 必须立即从会话表删除(或加黑名单)。否则"登出"只是前端表演,偷到 token 的人照用 7 天。

"记住我"的正确实现 是延长 refresh token 有效期(7 天 → 30 天),而不是延长 access token。见过把 access token 直接设 30 天的项目,等于把最容易被偷的凭证做成了长期凭证,前面所有努力白费。

七、总结

把整套方案压成一张决策表:

问题 答案
Access Token 放哪 内存,15 分钟,Authorization 头
Refresh Token 放哪 httpOnly + Secure + SameSite=Strict + 限定 path 的 cookie
过期怎么办 拦截器 401 → 并发合并的无感刷新 → 重放请求
refresh 怎么防偷 每次轮换 + 二次使用即吊销会话
XSS 输出转义 + CSP + 依赖审计(token 方案只是降损)
CSRF 自定义头免疫 + SameSite=Strict,GET 永远无副作用

按这张表把现有项目对一遍,重点检查三处:refresh token 是不是也在 localStorage 里(最常见的错)、401 并发刷新有没有合并、登出有没有吊销后端会话。

相关推荐
子非鱼a16 小时前
【XSS】乌托邦·王的实验室
html·xss
子非鱼a20 小时前
【XSS】这个知识库你需要申请
xss
csdn_aspnet1 天前
.NetCore Webapi 接口验证请求来源
.netcore·ip·token·签名·webapi·cors·sign
丿Wayne1 天前
iOS Token 刷新实战(Actor 避免并发重复刷新)
ios·swift·token·actor·swift并发
花北城2 天前
【C#底层库】access_token授权鉴权验证
c#·鉴权·token·授权
七夜zippoe2 天前
Agent 上下文工程:Token 管理、上下文压缩与分层记忆设计
ai·agent·token·上下文压缩·分层记忆
九章云极DataCanvas3 天前
让大模型少做重复计算 九章云极开源分布式KV Cache系统DingoCache
人工智能·开源·token·九章智算云
zander2583 天前
知识分享平台项目复习第二天:登录模块中的双令牌与刷新安全(2)
xss·csrf·cors·登录模块·cookie的三个属性
SoStraw4 天前
从零接入 webrpc:按这份最小清单跑通双端,再写业务
sdk·p2p·入门·token·webrpc·opensession·senddata