一次诡异的登录失效

线上告警:用户上午 10:00 登录成功,10:15 刷新页面却跳回登录页。

排查发现,你给 token Cookie 设置了 Max-Age=900,但运维在 Nginx 又加了一层 proxy_cookie_path,把过期时间改写成 Session

用户浏览器里同时出现了两条同名 Cookie,一条 15 min 一条 Session,结果请求带的是后者------登录状态瞬间蒸发。

要彻底堵这种坑,得先拆清楚 Cookie 到底由哪些"零件"组成。


下面这段代码来自我们统一封装的 setCookie 工具,把每个字段都做成可选参数,避免手写字符串拼错。

ts 复制代码
// utils/cookie.ts
export const setCookie = (opts: {
  name: string;
  value: string;
  maxAge?: number;      // 秒
  expires?: Date;
  path?: string;
  domain?: string;
  secure?: boolean;
  sameSite?: 'Strict' | 'Lax' | 'None';
  httpOnly?: boolean;   // 🔥 仅服务端可写
}) => {
  const parts = [
    `${opts.name}=${encodeURIComponent(opts.value)}`,
    opts.maxAge   && `Max-Age=${opts.maxAge}`,
    opts.expires  && `Expires=${opts.expires.toUTCString()}`,
    opts.path     && `Path=${opts.path}`,
    opts.domain   && `Domain=${opts.domain}`,
    opts.secure   && 'Secure',
    opts.sameSite && `SameSite=${opts.sameSite}`,
    opts.httpOnly && 'HttpOnly',
  ].filter(Boolean);
  document.cookie = parts.join('; ');
};

🔍 关键决策点

  • encodeURIComponent 兜住中文或特殊符号。
  • Max-AgeExpires 二选一,现代浏览器优先 Max-Age
  • SameSite=None 必须同时带 Secure,否则 Chrome 直接拒绝。

原理剖析:从"键值对"到"安全策略"的三层视角

层级 字段 作用域 设计哲学
数据层 name=value 浏览器 ↔ 服务器 纯业务键值,必须 URL 编码
作用域层 Domain / Path 决定 Cookie 随哪些请求头走 最小权限原则,防跨域污染
安全层 Secure / HttpOnly / SameSite 决定谁能读、谁能写、何时发 纵深防御,层层收紧

时序文字图

  1. 浏览器收到 Set-Cookie: token=abc; Path=/api; Secure; SameSite=Lax
  2. 下次请求匹配 Path=/api 且为 HTTPS,才把 token=abc 塞进 Cookie: 头。
  3. 前端 JS 无法读取 HttpOnly,XSS 偷不走;SameSite=Lax 挡住 CSRF 的 POST 跨站。

生产环境我们拆成三档:

ts 复制代码
// 会话级:关闭标签即失效
setCookie({ name: 'ui-theme', value: 'dark', path: '/', sameSite: 'Lax' });

// 持久化:7 天免登录
setCookie({
  name: 'refreshToken',
  value: jwt,
  maxAge: 7 * 24 * 3600,
  path: '/auth',
  secure: true,
  sameSite: 'Strict',
});

// 服务端专用:前端不可见
setCookie({
  name: 'sessionId',
  value: uuid,
  httpOnly: true,
  secure: true,
  sameSite: 'None',
});

环境适配说明

  • 本地 localhost 开发时,Chrome 允许 Secure Cookie 通过 HTTP,但 Safari 不行,需加 start --ignore-certificate-errors 或直接用 HTTPS 自签证书。
  • 子域共享登录态时,Domain=.example.com 记得带点,否则只对当前子域生效。

举一反三:三个变体场景实现思路

  1. 多租户隔离

    Path 设成 /tenant/:id,前端切换租户时动态改写 Cookie,避免跨租户串号。

  2. A/B 实验灰度

    Max-Age=3600 的短期 Cookie 存分组 ID,服务端按 Cookie 值路由到不同版本,实验结束自然过期,无需清理脚本。

  3. 第三方埋点兼容

    埋点域名 analytics.xxx.com 需要回写 Cookie,但主站是 www.xxx.com,设置 Domain=xxx.com + SameSite=None; Secure,既跨子域又防 CSRF。


小结

  • Cookie 不是"一串字符串",而是 7 个独立旋钮的组合。
  • 把每个旋钮显式写成参数,就能在代码层面杜绝"拼错分号"这类低级 Bug。
  • 真正上线前,用 Chrome DevTools → Application → Cookies 面板再核对一次,比读 RFC 6265 更直观。
相关推荐
QCY4 分钟前
「完全理解」1 分钟实现自己的 Coding Agent
前端·agent·claude
一拳不是超人33 分钟前
Electron主窗口弹框被WebContentView遮挡?独立WebContentView弹框方案详解!
前端·javascript·electron
anyup42 分钟前
🔥2026最推荐的跨平台方案:H5/小程序/App/鸿蒙,一套代码搞定
前端·uni-app·harmonyos
雮尘1 小时前
如何在非 Claude IDE (TARE、 Cursor、Antigravity 等)下使用 Agent Skills
前端·agent·ai编程
icebreaker1 小时前
Weapp-vite:原生模式之外,多一种 Vue SFC 选择
前端·vue.js·微信小程序
icebreaker1 小时前
重走 Vue 长征路 Weapp-vite:编译链路与 Wevu 运行时原理拆解
前端·vue.js·微信小程序
wuhen_n1 小时前
代码生成:从AST到render函数
前端·javascript·vue.js
喝咖啡的女孩1 小时前
浏览器前端指南
前端
wuhen_n1 小时前
AST转换:静态提升与补丁标志
前端·javascript·vue.js
喝咖啡的女孩1 小时前
浏览器前端指南-2
前端