mfa 页面安全核心在于后端对 mfatoken 的严格生命周期管理及校验一致性。mfatoken 须由后端生成(≤5分钟有效期)、隐藏传入表单、一次一用;前端需限制输入、禁自动填充、模糊错误提示、延迟提交校验,并配合后端统一 sleep 和限流,否则 mfa 形同虚设。

直接用 HTML 做 MFA 页面本身不难,但真正卡住人的从来不是“怎么放个输入框”,而是「如何与后端 TOTP 校验逻辑安全对齐」「怎么避免被绕过或时序攻击」「哪些字段必须传、哪些必须隐藏」——这些细节错一点,整个 MFA 就形同虚设。
HTML 表单必须携带 mfaToken 字段
前端不能只提交 totp 输入值。服务端需要靠 mfaToken 关联临时会话和用户密钥,否则无法查出该验证对应哪个用户的 TOTP 密钥,也无法做速率限制或防重放。
-
mfaToken应由后端在第一步登录成功且检测到 MFA 启用后生成,有效期建议 ≤ 5 分钟 - 该字段必须作为隐藏域写入表单:
<input type="hidden" name="mfaToken" value="abc123..."> - 绝不能从 URL 参数读取或 localStorage 里拼接——容易被 XSS 或中间人篡改
- 每次提交后,后端校验完应立即作废该
mfaToken,防止重放
输入框要限制长度、类型和粘贴行为
6 位 TOTP 码看似简单,但放任用户自由输入会引入大量无效请求和误判。
- 用
<input inputmode="numeric" pattern="[0-9]*" maxlength="6">强制数字键盘、限制长度 - 监听
paste事件并截断非数字字符(用户可能复制带空格/换行的验证码) - 禁止自动填充:
autocomplete="off" autocapitalize="none" autocomplete="one-time-code"(后者是 Chrome 对 OTP 的专用提示) - 不要用
type="number"—— 它会允许输入e、+等非法字符,且移动端兼容性差
错误提示必须模糊且延迟一致
返回 “TOTP 错误” 或 “令牌已过期” 这类精确提示,等于告诉攻击者:用户名存在、MFA 已启用、甚至当前时间偏移范围。
- 所有失败分支统一返回相同文案,例如:
"验证未通过,请检查输入或稍后重试" - 后端必须对每次校验强制 sleep(1s) 左右(哪怕校验瞬间失败),防止时序侧信道泄露
- 前端不要在 JS 里判断
mfaToken是否为空再禁用提交按钮——这会让攻击者探测 token 生效状态 - 连续 3 次失败后,应要求重新走完整登录流程(而非仅刷新 MFA 页面),避免绕过首层凭证校验
页面加载时就要校验 mfaToken 有效性
用户可能手动刷新 MFA 页面、或从书签打开旧链接——此时 mfaToken 很可能已过期或被用过。不能等用户点提交才报错。
- 页面 JS 加载后立即发起一次轻量校验请求(如
POST /api/mfa/validate-token),只传mfaToken - 若返回 401 或 400,直接跳转回登录页,并清空 URL 中的 token 参数
- 不要依赖 cookie 或 localStorage 存 token——它们无法绑定到具体一次登录会话,易被复用
- 这个校验接口本身也需限流(如 IP + token 哈希组合每分钟 ≤ 2 次),防探测
最常被忽略的是:前端页面只是通道,真正的安全边界在后端对 mfaToken 的生命周期管理、滑动窗口校验、以及所有响应延迟的一致性。写得再漂亮的 HTML,只要后端漏掉一个 sleep 或复用一次 token,MFA 就只剩心理安慰作用。
前端入门到VUE实战笔记:立即使用
在学习笔记中,你将探索 前端 的入门与实战技巧!











