Cookie隔离实战指南:解决跨域请求中的身份认证难题
项目初期的技术选型
去年下半年接了个 SSO 改造项目,客户原有系统是三个子站:admin.jztheme.com、shop.jztheme.com、api.jztheme.com,全靠一套后端 Session + Cookie 做登录态。问题来了——用户在 shop 页面登录后,admin 页面要刷新才能同步状态;更糟的是,某天测试同学发现:在 shop 下点登出,admin 居然还挂着,没登出。
开始我以为是前端没发登出请求,查了一圈发现后端登出只清了 shop 的 Cookie,admin 的 Cookie 还稳稳躺在浏览器里。翻了下 Chrome DevTools 的 Application → Cookies 标签页,好家伙,三个域名各有一份 session_id,互不干扰,但逻辑上它们该是一体的。
这时候想到 Cookie 隔离(SameSite + Secure + HttpOnly 组合)不是正好解决这种跨域会话管理混乱的问题吗?而且新项目要求支持 iOS 16+ 和 Chrome 110+,SameSite=Lax/Strict 的兼容性完全够用。于是拍板:干,把登录态收归到主域 jztheme.com 下统一管理,子站通过 iframe 或 postMessage 拉取主域 Cookie 状态,不再各自为政。
最大的坑:性能问题
方案写得挺顺,代码也撸得飞快,本地跑通了,高兴不到两小时——测试反馈:“登录后首页白屏 3 秒”。我懵了,F12 打开 Network,发现一个接口卡在 pending 状态:https://jztheme.com/api/auth/status。它本该秒回,但实际要等 2~3 秒才响应。
排查半天,发现是浏览器对 SameSite=None; Secure Cookie 的校验机制在作怪:当子站(比如 shop.jztheme.com)发起请求到主域(jztheme.com)时,如果 Cookie 设置了 SameSite=None,Chrome 会强制等待 TLS 握手完成后再附带 Cookie。而我们当时用的 Nginx 配置没开启 HTTP/2,TLS 是单次握手 + 多路复用未生效,每次请求都走完整 TLS 握手流程……这延迟就堆出来了。
折腾了半天发现,根本不用 SameSite=None。我们压根没跨「第三方上下文」(比如从百度跳转进我们的页面),所有子站都是同主域下的二级域,直接上 SameSite=Lax + Domain=.jztheme.com 就行。Lax 允许顶级导航带 Cookie(比如点击链接跳 admin.jztheme.com),又阻止 iframe 内自动携带(防 CSRF),刚好符合我们需求。
核心代码就这几行
后端(PHP)设置 Cookie 的关键逻辑:
setcookie(
'session_id',
$sessionId,
[
'expires' => time() + 86400,
'path' => '/',
'domain' => '.jztheme.com',
'secure' => true,
'httponly' => true,
'samesite' => 'Lax'
]
);
前端子站(shop/admin)判断登录态时,不再读本地 Cookie(因为现在 Cookie 只存在 .jztheme.com 下,子站 JS 读不到 HttpOnly 的),而是用 fetch 请求主域接口:
// 在 shop.jztheme.com 页面中
async function checkAuth() {
try {
const res = await fetch('https://jztheme.com/api/auth/status', {
method: 'GET',
credentials: 'include', // 关键!必须带上
headers: {
'Content-Type': 'application/json'
}
});
return await res.json();
} catch (e) {
console.error('auth check failed', e);
return { logged_in: false };
}
}
后端接口(/api/auth/status)只要简单返回当前 session 状态即可,它能读到 Cookie 是因为同域(.jztheme.com)且 credentials=include。
踩坑提醒:这三点一定注意
- Domain 必须带前导点号:写成
Domain=jztheme.com不生效,必须是Domain=.jztheme.com,否则浏览器认为是精确匹配,不会下发给子站 - Secure 和 SameSite=None 不能共存除非真需要第三方调用:我们一开始误以为“只要 HTTPS 就得设 None”,结果触发 TLS 额外握手。Lax 完全支持 Secure,别自己加戏
- iframe 通信不是必须的:有同事建议用 window.postMessage 让子站向主域 iframe 询问状态,听起来高大上,实际增加了复杂度和失败路径。我们最后就靠 fetch + credentials=include 解决了全部问题,干净利落
回顾与反思
上线后整体效果不错:三个子站登录态完全同步,登出即全局失效,CSRF 风险明显降低(后端删掉了大量手动 token 校验逻辑)。监控显示 /api/auth/status 接口 P95 延迟压到了 80ms 以内,比之前稳定太多。
但也有点小遗憾:iOS Safari 对 SameSite=Lax 的某些导航场景(比如 form.submit() 触发的 POST 跳转)偶尔不带 Cookie,导致个别用户登出后跳转页面时出现短暂未登录态。我们加了个前端兜底逻辑——跳转前先 fetch 一次 status,如果已登出就跳登录页,不完美,但影响极小,没再深究。
另外,这个方案对老 IE 完全不兼容(IE 不支持 SameSite),不过客户明确说放弃 IE11,所以连 polyfill 都懒得写。
总的来说,Cookie 隔离不是银弹,但它在「同主域多子站」这个特定场景下,确实比自己搞 JWT localStorage 同步靠谱得多。没有花里胡哨的加密解密,没有定时刷新 token 的定时器,就靠浏览器原生机制,反而最稳。
以上是我踩坑后的总结,希望对你有帮助。如果你也在搞类似 SSO 场景,欢迎评论区交流——特别是你遇到过哪些 SameSite 的诡异 case,我很想听听。

暂无评论