把会话 Cookie 改成 30 天,为什么还是会掉登录
我在一台没有显示器的服务器上跑着常驻 Chromium,通过 CDP 让 Playwright 接管它,每天定时做几件需要登录才能做的事。登录这件事本来很简单——用户从 noVNC 里手动输入一次验证码,之后就都好了。问题是"之后"并不久。
第一次发现异常很直观:浏览器进程重启一次,登录态就没了。查下来原因也不复杂——华为云那套 CAS 单点登录里,CASTGC、SSOJTC、HUAWEIID_CAS_ISCASLOGIN 全都是会话 Cookie,expires 是 -1。会话 Cookie 只活在内存里,Chrome 正常退出时不会被写进 profile 目录下的 Cookies SQLite 库,进程一退就是彻底消失。
于是有了第一个想法,也是这篇文章真正要讨论的事:如果把会话 Cookie 的过期时间改成 30 天,是不是就能免疫重启了?
一、cookie 持久化的正确做法
只用 context.cookies() 把 Cookie 存成 JSON 是不够的。存下来的 JSON 只是一份快照,profile 里躺着的仍然是 expires=-1,Chrome 下次启动照样丢掉。所以必须做两件事,缺一不可:
- 把
expires == -1的项改成一个具体的未来时间戳; - 写回正在运行的浏览器(
context.add_cookies或 CDP 的Network.setCookie),让 Chrome 自己走一遍持久化逻辑落到 profile 里。
第二步是最容易漏的。只改 JSON 不写回浏览器,看起来"我明明保存了",重启后却依然掉登录——因为落盘的是 Chrome 的内部状态,不是你手里的那份 JSON。
写成脚本大概是这个结构:
cookies = ctx.cookies()
fixed = []
for c in cookies:
c = dict(c)
if c["expires"] in (-1, None):
c["expires"] = time.time() + 30 * 86400
fixed.append(c)
ctx.add_cookies(fixed) # 关键:回写运行中的浏览器
json.dump(fixed, open(store, "w"))
再配一个每 15 分钟跑一次的 systemd timer 做快照,重启时用 ExecStartPost 把 Cookie 灌回去。到这里,"浏览器重启就掉登录"这个问题确实解决了——实测重启后 LOGGED_IN。
二、比持久化更麻烦的三个坑
坑一:restore 的时序
Chrome 刚起来的时候,页面还停在 about:blank,document.body 是 null。这时候急着 add_cookies 是一半成功一半白干:CDP 调用可能返回成功,但紧接着 Chrome 恢复上次的标签页,那个动作会覆盖掉刚灌进去的 Cookie。
正确顺序是:先等到 document.readyState === "complete" 且 document.body 存在,再 add_cookies,然后才让页面去导航。听起来像玄学,其实很好理解——你在它还没初始化完的时候塞东西,它初始化的时候会把你的东西冲掉。
坑二:判活判错了对象
Cookie 里有两个长得很像的东西:CASTGC 和 SSOJTC。
CASTGC是 CAS 的票据 Cookie,服务端会话死了它还会残留,看上去一直"有效";SSOJTC只在会话真正有效时才存在。
我一开始拿 CASTGC 当判据,结论是"登录态正常",然后所有需要登录的请求全部被重定向到登录页。判活只能看 SSOJTC。
坑三:死 Cookie 制造出的登录死循环
这是最坑的一个,症状是用户说"我怎么都登录不了"。
服务端会话过期后,如果 restore 逻辑老老实实把那份"有效期还有 30 天"的旧 CASTGC 灌回浏览器,登录页会进入这样一个循环:
检测到有会话 → 去服务端校验 → 校验失败 → 弹回登录页 → 又检测到有会话 → …
页面本身完全正常,账号密码也对,但人就是登不进去。根因是那份被人工延长了有效期的死 Cookie:它把"我没有会话"这个事实掩盖了。
修法很短:一旦发现 SSOJTC 缺失,立刻把 CASTGC、HUAWEIID_CAS_ISCASLOGIN、SSOJTC、CASLOGINSITE、LOGINACCSITE 全部删掉——浏览器里的和存档里的一起删。宁可让用户老老实实登一次,也不要留一份会骗人的 Cookie。
三、这套方案的上限
必须说清楚:cookie 持久化只解决"浏览器重启",解决不了"服务端会话过期"。
实测数据:在没有任何重启的情况下,会话也只活了大约 20 小时。也就是说,不管你把 cookie 的有效期改成 30 天还是 3 年,服务端那边的会话一到期,本地这份 Cookie 就只是一张过期的门票。
所以这套东西的真实价值是把重登频率从"每次重启掉一次"压到"每天最多一次",而不是"永久登录"。不要向任何人承诺永久登录,也不要把"今天没掉"当成机制修好了——那只是今天运气好。
四、比追求永久登录更值得做的事
既然上限摆在那里,工程上的注意力应该换一个方向:
1. 把"掉线"变成一条会主动告诉你的消息。 一个每 10 分钟跑一次的探针,只在状态翻转时推送:in → out 推一次"登录态失效",out → in 推一次"已恢复",中间完全静默。这样你不需要盯着,也不会被"一切正常"淹掉。
2. 三信号联合判定,别信单一指标。 我在踩坑过程中遇到过三种假信号:
- 某社区站点会用缓存渲染出"看着已登录"的昵称和内容,但上游 CAS 早就死了;
- 页面导航栏偶尔渲染出未登录态的"登录"链接,其实登录是好的;
- 反过来,一个自己写的判定脚本会在页面慢渲染时误报"未登录",白白把整次任务停掉。
结论是:页面文本 + 关键接口是否被重定向到登录页 + 关键 Cookie 是否存在,三个信号一起看,缺一个都不下结论。
3. 用日志留一条时间线。 那个每 15 分钟的快照任务同时在日志里打一行 Cookie 指纹。排障时最有用的一句话就是:“SSOJTC 是从哪一分钟开始不在列表里的”——那一分钟就是会话真正死亡的时刻,能立刻区分"服务端过期"还是"浏览器重启",而这两者的排查方向完全不同。
写在最后
这件事真正的教训不是某个 API 怎么调,而是一个判断上的错误:把客户端状态当成了服务端状态。
Cookie 是本地的,会话是服务端的,两者的生命周期没有任何强制绑定关系。凡是"延长本地凭据有效期"的方案,都要先问一句:服务端认不认这份凭据,认多久。想清楚这一点,就不会去追求那个不存在的"永久登录",而是老老实实做监控、做恢复、把预期管理好——这比任何技巧都值钱。
- 点赞
- 收藏
- 关注作者
评论(0)