深入解析Cookie机制与前端应用实践
又踩坑了,登录态莫名其妙丢了
今天早上刚到公司,泡好咖啡准备摸鱼,结果测试群里一条消息炸了:用户登着登着突然跳登录页了。我第一反应是后端接口抽风,查了下日志发现 session 没过期,token 也正常返回,前端却拿不到 Cookie —— 明明上一秒还好好地带着 Set-Cookie 头。
这里我踩了个大坑:我以为是 axios 配置漏了 withCredentials: true,赶紧加上,发测试环境一试……还是不行。折腾了快一个小时,抓包看了十几遍请求,发现第一次登录请求确实收到了 Set-Cookie,但第二次请求(比如获取用户信息)就完全没带上这个 Cookie。
浏览器开发者工具里看 Application -> Cookies 根本没有这条记录。这就邪门了。
排查过程像在盲人摸象
我先怀疑是不是跨域问题。项目前端跑在 http://localhost:3000,后端是 https://api.jztheme.com,虽然 devServer 配了 proxy,但实际请求是直接打到线上域名的。于是我把本地启了个 HTTPS 服务(用 mkcert 搞了个本地证书),换成 https://localhost:3000 访问,心想总该满足 SameSite 的安全要求了吧?
结果还是不行。
后来翻 MDN 才意识到关键点:Cookie 的 Secure 属性意味着它只能通过 HTTPS 连接传输,但如果前端和后端不在同一个 origin 下,就算你本地开了 HTTPS,只要不是同源,浏览器默认也不会自动携带跨域 Cookie,除非你在请求时显式设置 credentials: 'include',而且服务端也得配合。
我这时候才想起来检查后端响应头:
Set-Cookie: sessionid=abc123; Path=/; Domain=jztheme.com; Secure; HttpOnly; SameSite=Lax
看到 Domain=jztheme.com 我就懂了——这玩意根本不会被 localhost 存下来。浏览器只允许当前页面所在 domain 或其父 domain 设置 Cookie。你现在让 jztheme.com 给 localhost 设置 Cookie?门都没有。
所以哪怕你 withCredentials: true 写得再对,浏览器压根就不收这个 Cookie,自然也就谈不上后续发送。
换方案:反向代理搞定开发环境
后来试了下发现最简单的解法就是:别让前端直接请求线上 API 域名。开发环境下统一走本地反代。
我在 vite.config.js 里加了这么一段:
export default defineConfig({
server: {
proxy: {
'/api': {
target: 'https://api.jztheme.com',
changeOrigin: true,
secure: false,
cookieDomainRewrite: 'localhost',
cookiePathRewrite: '/'
}
}
}
})
重点来了:changeOrigin: true 是为了让 Host 头改成目标服务器;secure: false 允许转发 HTTPS 时忽略证书错误(本地调试常见操作);最关键的是中间件会自动帮你 rewrite Set-Cookie 里的 Domain 和 Path。
不过 vite 自带的 proxy 并不直接支持 cookieDomainRewrite,这是我自己封装的中间件逻辑。真实代码其实是这样的:
import { createProxyMiddleware } from 'http-proxy-middleware'
export default defineConfig({
server: {
middlewareMode: false,
proxy: {
'/api': createProxyMiddleware({
target: 'https://api.jztheme.com',
changeOrigin: true,
secure: false,
onProxyRes: (proxyRes, req, res) => {
const setCookie = proxyRes.headers['set-cookie']
if (Array.isArray(setCookie)) {
proxyRes.headers['set-cookie'] = setCookie.map(cookie =>
cookie
.replace(/Domain=[^;s]*/ig, 'Domain=localhost')
.replace(/Secure;?/ig, '')
)
}
}
})
}
}
})
这里做了两件事:
- 把所有
Set-Cookie里的Domain=jztheme.com改成Domain=localhost,这样浏览器愿意收 - 去掉
Secure标志,因为本地是 HTTP,带 Secure 的话 Chrome 直接无视这条 Cookie
改完之后刷新页面,登录成功,Cookie 出现在 localhost 下,后续请求也能正常带上,问题解决。
当然这方法有个小瑕疵:生产环境不能这么搞。所以我们用环境变量控制:
const isDev = import.meta.env.DEV
const proxyOptions = isDev
? {
target: 'https://api.jztheme.com',
changeOrigin: true,
secure: false,
onProxyRes: (proxyRes) => {
const cookies = proxyRes.headers['set-cookie']
if (!cookies) return
proxyRes.headers['set-cookie'] = cookies.map(c =>
c.replace(/Domain=[^;s]*/i, 'Domain=localhost').replace(/Secure;/i, '')
)
}
}
: null // 生产环境走正常部署,同域就没这些问题
// 然后传给 proxy
顺手补了个小工具函数
虽然主问题解决了,但我还是担心某些老接口可能漏设 SameSite,导致 Safari 或 iOS 微信里出问题。于是写了个简单检测脚本,在 devtools console 里跑一下看看当前页面有哪些可疑 Cookie:
function checkCookieIssues() {
const url = new URL(window.location.href)
const domain = url.hostname
const cookies = document.cookie.split(';').map(c => c.trim())
console.group('🔍 Cookie 安全检查')
cookies.forEach(cookie => {
const [key] = cookie.split('=')
const value = document.cookie.match(new RegExp(${key}=([^;]+)))?.[1]
// 实际上 JS 拿不到 HttpOnly 的值,但这只是提示
if (value === undefined && cookie.includes('HttpOnly')) {
console.warn(⚠️ ${key} 是 HttpOnly,JS 不可读)
}
// 检查是否设置了 Secure 但当前是 HTTP
if (location.protocol === 'http:' && /Secure/i.test(cookie)) {
console.error(❌ ${key} 设置了 Secure,但在 HTTP 下无效)
}
})
console.log(✅ 当前域:${domain},共 ${cookies.length} 条 Cookie)
console.groupEnd()
}
checkCookieIssues()
这个脚本其实帮我在另一个项目里发现了问题:某个第三方 SDK 注入的 Cookie 带了 Secure 却跑在内网 HTTP 环境下,结果一直登不进去。这种细节纯靠肉眼很难发现。
说点原理上的事
这次折腾让我重新看了遍 Cookie 的作用域规则。总结几点容易忽略的点:
- Domain 必须是当前 host 的父级或相同。比如你不能让 baidu.com 给 taobao.com 设 Cookie,但可以给
.baidu.com或sub.baidu.com设 - Secure 的 Cookie 只能在 HTTPS 下传输,包括发送和接收。就算你本地开 HTTPS,只要目标 API 是 HTTP,照样不会发 Secure Cookie
- SameSite 默认是 Lax,意味着跨站 POST 请求都不会带 Cookie。如果是 iframe 里的表单提交、图片加载等场景要注意
- HttpOnly 的 Cookie 无法被 JS 读取,防 XSS 的,但仍然会在请求中自动带上
- 浏览器是否存储 Cookie 还受隐私策略限制,比如 Safari 的 ITP、Chrome 的第三方 Cookie 禁用等
另外很多人不知道的一点:当浏览器收到一个 Set-Cookie,它并不会无脑存下来。它要判断当前 response 是否属于“same-site” or “same-origin”,否则可能直接丢弃(尤其是设置了 SameSite=Strict 或 Lax 的时候)。
结尾吐槽一下
改完之后仍有小问题:某些旧安卓机的 WebView 在清除缓存后依然会丢失 Cookie,怀疑是厂商定制系统搞的事。但这已经超出可控范围了,暂时不管。
以上是我踩坑后的总结,希望对你有帮助。如果你有更好的方案欢迎评论区交流。特别是有没有办法不改 Domain 就能让跨域 Cookie 被正确处理?我目前还没找到靠谱做法。

暂无评论