30 秒的验证码为什么总在提交时失效

举报
yd_268799136 发表于 2026/11/02 14:42:46 2026/11/02
【摘要】 TOTP 自动化登录反复提示验证码错误,算法没错、种子没错,问题在时间窗口余量、六格输入框的事件、以及一个点不动的确认按钮。附检查顺序和代码。

现象

写了个脚本自动填登录表单,密码那关一直很顺,卡在两步验证:TOTP 六位数填进去、点确认,返回「验证码错误」。手动在手机上取一个码填进去就成功。

同一套代码、同一个种子,为什么手动的行、自动的不行?问题不在算法,在时间窗口。

TOTP 的窗口是滑动的,不是固定的

TOTP 的算法是 HMAC(secret, floor(unix_time / 30)),每 30 秒产生一个新码。关键在于:码的有效期不是从你取码那一刻开始算 30 秒,而是跟着绝对时间走。

假设现在是 14:39:28,距下一个窗口只剩 2 秒。这时候取到码,走完这些步骤:

  • DOM 定位六个输入框 ≈ 0.2s
  • 逐个派发 input / keyup 事件 ≈ 0.3s
  • 点击「验证」 ≈ 0.1s
  • 请求飞到服务端 ≈ 0.3~1s

快到 1 秒的链路,放在 2 秒余量里就是运气。服务端收到时已经跨过 30 秒边界,校验的是新窗口的码,自然对不上。

手动操作反而快:人看一眼手机、敲六位数字、回车,全程一两秒,但通常是看到码就立刻动手,不会先做别的。自动化脚本容易在中间插入截图、日志、状态检查,把余量吃光。

怎么判断自己踩了这个坑

取码时顺手打印剩余秒数:

import time
remain = 30 - (int(time.time()) % 30)
if remain < 8:
    time.sleep(remain + 0.5)   # 等下一个窗口,拿一个"新鲜"的码

这个判断很便宜,能消掉大部分偶发失败。阈值取 8 秒是经验值:链路稳定时 3 秒够,网络慢或页面重时给到 8 秒更稳。

第二个坑:六个输入框不能粘贴

很多站点的 TOTP 输入框是六个独立的 input[type=tel],而不是一个 length=6 的输入框。这类控件有三个特征:

  • 直接对第一个框 fill("123456") 只会得到一位
  • 剪贴板粘贴无效,或者只粘进第一个框
  • 用 .value = 赋值不会触发校验,按钮仍是灰的

正确做法是对每个框单独设值,并且用原生 setter 派发事件:

const setter = Object.getOwnPropertyDescriptor(
  window.HTMLInputElement.prototype, 'value').set;
inputs.forEach((inp, i) => {
  inp.focus();
  setter.call(inp, code[i]);
  inp.dispatchEvent(new Event('input',  { bubbles: true }));
  inp.dispatchEvent(new Event('change', { bubbles: true }));
  inp.dispatchEvent(new KeyboardEvent('keyup', { bubbles: true, key: code[i] }));
});

特别是 keyup,很多前端组件靠它判断「这一位填完了、跳下一格」。少了这个事件,值填进去了但组件状态还是空的。

第三个坑:确认按钮不是 button

那次失败的最后一个原因是点错了元素。页面里文本为「验证」的元素有三个:

div.button-base-box        宽度 106   外层容器,点击无效
div.dialogFooterBtn        宽度 106   底部容器,点击无效
div.dialog-btn.btn-next    宽度 106   真正的按钮

三个都是 div,尺寸一样,只有 class 能区分。querySelectorAll('button') 抓不到它们,el.click() 对前两个无效,用真实鼠标点击落到正确坐标才行。

判断办法:拿 class 里有没有框架自己的按钮标识(这里是 dialog-btn),别只看文本和尺寸。

一个可用的检查顺序

  1. 先确认种子和算法对:用同一个 secret 在本地算码,和手机上显示的码比对。对不上说明 secret 或算法(SHA1/SHA256、位数、周期)错了。
  2. 再确认时间:取码前后各 date -u 一次,服务端和本地时差超过 30 秒,任何码都不会对。
  3. 最后才是窗口余量。余量不足就等一下个窗口,别硬试——连续错三次有些平台会锁定。

实践建议

  • 取码和提交放在同一段逻辑里,中间不要截图、不要写日志、不要查状态。
  • 余量低于 8 秒就等,等待成本远低于重试和锁定成本。
  • 六格输入框要逐格派发 input + keyup,粘贴和整串赋值都不行。
  • 按钮先按 class 定位,再按文本过滤;纯文本匹配在多处命中时容易点到容器上。
  • 整个过程别把码打印到日志里,30 秒后就失效,但日志会长期留存。
【声明】本内容来自华为云开发者社区博主,不代表华为云及华为云开发者社区的观点和立场。转载时必须标注文章的来源(华为云社区)、文章链接、文章作者等基本信息,否则作者和本社区有权追究责任。如果您发现本社区中有涉嫌抄袭的内容,欢迎发送邮件进行举报,并提供相关证据,一经查实,本社区将立刻删除涉嫌侵权内容,举报邮箱: cloudbbs@huaweicloud.com
  • 点赞
  • 收藏
  • 关注作者

评论(0)

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

全部回复

上滑加载中

设置昵称

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

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

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