固定 Profile 也挡不住会话过期:自动化登录链路里的 SSO Ticket 与 MFA 中间页

举报
yd_233360489 发表于 2026/11/10 10:06:24 2026/11/10
【摘要】 选了「信任此浏览器」却仍被踢到登录页?免二次验证不等于免登录。拆解设备信任标识、SSO 会话、一次性 ticket 三层状态,并给出可自愈的登录状态机与 MFA 引导中间页处理。

现象:明明选了「信任此浏览器」,隔天还是被踢到登录页

有个每天定时跑一次的浏览器自动化脚本,前一天登录时已经在「是否信任此浏览器?」弹窗里选了信任,并且把 Playwright 改成了固定 userDataDir 的持久化上下文,退出也走 ctx.close() 优雅关闭。按理说 cookie 该落盘了。结果第二天启动,打开业务页一看:登录 / 注册查看您的积分,积分读不到,任务卡片全变回「去完成」。

第一反应是 profile 又没落盘,或者「信任」根本没生效。但翻完链接和页面状态后,结论是:这两件事根本不在一个层面上。

先分清:免二次验证 ≠ 免登录

「信任此浏览器」跳过的是二次验证(短信验证码 / MFA),不是认证本身。服务端在登录成功后往浏览器写一个长期的设备标识,下次认证时读到它就不再发短信;但业务站的会话 cookie 仍然有自己的有效期,跨天、跨长时间闲置后被清掉是正常行为。

所以正确的预期是:固定 profile 让脚本不再需要人工收验证码,但脚本依然要能自己走一遍密码登录。把「免短信」理解成「永不掉登录」,是这次误判的根源。

回跳 URL 里的 ticket 是什么

登录成功后业务页地址会长成这样:

https://developer.huaweicloud.com/grow?ggw_kfz
  &ticket=ST-8598199-yEAyyHhQHJcyrHnupfI5fDUp-sso

这是典型 CAS 风格的一次性服务票据:认证中心签发,业务站拿它换取本地会话,换完即失效。它的存在恰恰说明「刚刚完成了一次完整认证」,而不是「会话一直没断」。自动化脚本如果把这个 ticket 当成常态缓存、或者把带 ticket 的 URL 存下来下次直接 goto,第二天必然拿到过期票据。

可以这样把三层状态分开记:

  1. 设备信任标识:长期有效,靠固定 profile + 优雅退出保住;
  2. SSO 会话:有时效,跨天大概率失效,需要脚本自己重登;
  3. 业务 ticket:一次性,只在认证完成的瞬间有意义。

新冒出来的中间页:MFA 引导

这次重登还多了一步。密码提交后没有直接回业务页,而是跳到 #/loginBindMfa,页面提示「账号未开启双重验证」,给两个选择:前往开启 / 暂不开启。对自动化来说这是个计划外的中间态——线性脚本会卡在这里,后面所有操作全部落到错误页面上。

更细的坑是「暂不开启」默认不可点:

<input id="promptRedirectEnableCheckbox" type="checkbox">
<div id="promptRedirectEnableCancelBtn"
     class="btnDiv cancelBtnDisbaled ...">暂不开启</div>

必须先勾上「我已了解风险,30 天内不再提示」这个免责复选框,Angular 才会把按钮 class 从 cancelBtnDisbaled 改成 cancelBtn。而且这里要用 Playwright 的真实 click(),eval 里的 DOM 级 element.click() 经常不触发 ng-model 更新,按钮会一直是灰的。

处理原则:不改账号安全设置。只勾「暂不开启」的免责确认,不点「前往开启」,不给账号开 MFA——那会让后续自动化每天多一道人工环节。

把线性脚本改成状态机

结论是登录环节不能写成一串固定步骤,而要按当前 URL 分支。简化后的结构:

async function ensureLogin(page) {
  await page.goto(TARGET);
  await page.waitForTimeout(3000);

  // 1) 已登录态:直接返回
  if (await page.evaluate(() =>
        /我的积分/.test(document.body.innerText))) return 'cached';

  // 2) 掉登录:点登录,进认证页
  await page.locator('a:has-text("登录")').first().click();
  await page.waitForLoadState('domcontentloaded');

  // 3) 密码登录(凭据只在运行时注入,不落盘)
  await page.fill('input[placeholder="手机号/邮件地址/账号名"]', USER);
  await page.fill('input[type=password][placeholder="密码"]', PWD);
  await page.locator('.hwid-btn.hwid-btn-primary').click();

  // 4) 中间页分支:MFA 引导
  if (page.url().includes('loginBindMfa')) {
    await page.locator('#promptRedirectEnableCheckbox').click();
    await page.waitForTimeout(300);
    await page.locator('#promptRedirectEnableCancelBtn').click();
  }

  // 5) 短信验证分支:信任浏览器生效时不会走到这里
  if (await page.locator('input[placeholder*="验证码"]').count()) {
    throw new Error('need manual SMS code');
  }

  await page.waitForURL(/developer\.huaweicloud\.com\/grow/);
  return 'relogin';
}

关键点:throw new Error('need manual SMS code') 而不是硬等一个永远不会出现的验证码输入框。信任标识真的失效时,脚本应该明确失败并把原因报出来,而不是静默挂住或反复重试触发风控。

三条可以复用的结论

  1. 固定 userDataDir 是必要条件,不是充分条件。它保证设备标识不丢,不能保证会话不过期;登录链路要能自愈。
  2. 认证中间页是常态,不是异常。MFA 引导、协议确认、数据共享勾选这类插页会随平台策略变化,用 URL + 可见元素做分支,别写死步骤序号。
  3. 退出必须优雅。ctx.close() 或专门的 quit 指令,别 kill -9 进程树——Chromium 来不及 flush cookie / LevelDB,丢的正是你费劲保住的设备标识。

把「信任此浏览器」当成省掉一条短信的优化,而不是免登录的银弹,这类隔天掉线就不会再变成一次事故排查了。

【声明】本内容来自华为云开发者社区博主,不代表华为云及华为云开发者社区的观点和立场。转载时必须标注文章的来源(华为云社区)、文章链接、文章作者等基本信息,否则作者和本社区有权追究责任。如果您发现本社区中有涉嫌抄袭的内容,欢迎发送邮件进行举报,并提供相关证据,一经查实,本社区将立刻删除涉嫌侵权内容,举报邮箱: cloudbbs@huaweicloud.com
  • 点赞
  • 收藏
  • 关注作者

评论(0)

0/1000
抱歉,系统识别当前为高风险访问,暂不支持该操作

全部回复

上滑加载中

设置昵称

在此一键设置昵称,即可参与社区互动!

*长度不超过10个汉字或20个英文字符,设置后3个月内不可修改。

*长度不超过10个汉字或20个英文字符,设置后3个月内不可修改。