在 Web 中落地 MFA(基于 TOTP)的完整指南

本文共计 3767 个字,预计阅读时长 13 分钟

多因素认证(Multi‑Factor Authentication, MFA)是给账号加第二把锁的通用做法。在 Web 里最常见的是“密码 + 一次性动态验证码(TOTP)”。本文从原理到实现、从后端到前端、从安全到体验,手把手带你把 TOTP‑MFA 上到你的站点。


什么是 TOTP

  • TOTP(Time‑based One‑Time Password)基于共享密钥和时间窗口(通常 30 秒)生成 6 位一次性验证码。
  • 开放标准:RFC 6238(在 HOTP/RFC 4226 基础上加入时间维度)。
  • 常见验证器:Google Authenticator、Microsoft Authenticator、1Password、Authy 等。

工作原理简述:

  • 服务器与用户设备共享一个“密钥”(Secret)。
  • 每 30 秒,设备用 Secret + 时间片计算出新的 6 位数字;服务器用同样算法校验。
  • 为兼容网络/设备时间抖动,通常允许 ±1 个时间窗口(即 90 秒内有效)。

端到端流程(绑定与登录)

1) 绑定(首次或重置时)

  • 用户已通过“已知身份”校验(如已输入正确用户名+密码)。
  • 服务器为该用户生成(或派生)一个 Secret。
  • 服务器构造 otpauth URI,并返回一个二维码给前端。
  • 用户用手机验证器“扫描二维码”或手动输入 Secret 完成绑定。

2) 登录

  • 用户输入用户名 + 密码,或切换为“代码登录”(仅输入 6 位 TOTP)。
  • 服务器基于用户的 Secret 计算当前时间片可接受的 TOTP,校验通过则登录成功。

服务器端实现要点(以 PHP 为例)

1. 生成/派生 Secret

两种常见方式:

  • 随机生成(推荐在有安全存储时)
  • 从用户稳定材料派生(无持久化时的折中)

随机生成示例(20 字节随机后做 Base32):

function base32EncodeNoPad(string $data): string { $alphabet = 'ABCDEFGHIJKLMNOPQRSTUVWXYZ234567'; $bits = ''; for ($i = 0; $i < strlen($data); $i++) { $bits .= str_pad(decbin(ord($data[$i])), 8, '0', STR_PAD_LEFT); } $chunks = str_split($bits, 5); $out = ''; foreach ($chunks as $c) { $c = str_pad($c, 5, '0', STR_PAD_RIGHT); $out .= $alphabet[bindec($c)]; } return rtrim($out, '='); } function generateTotpSecret(int $bytes = 20): string { return base32EncodeNoPad(random_bytes($bytes)); }

无持久化时的安全派生(示例):

  • 使用 HMAC(pepper, lowercase(username) + ':' + passwordHashInDB) 生成 20 字节,再做 Base32。
  • 密码变更会导致 Secret 随之变更,安全性优于“仅用户名派生”。
function deriveSecretFromPasswordHash(string $username, string $passwordHash, string $pepper): string { $normalized = strtolower(trim($username)); $raw = hash_hmac('sha1', $normalized . ':' . $passwordHash, $pepper, true); // 20 bytes return base32EncodeNoPad($raw); }

2. 生成 otpauth URI

$issuer = '你的站点名'; $account = $username; // 建议为登录名或邮箱 $query = http_build_query([ 'secret' => $secret, 'issuer' => $issuer, 'algorithm' => 'SHA1', 'digits' => 6, 'period' => 30, ]); $otpauth = "otpauth://totp/" . rawurlencode("$issuer:$account") . "?$query";

3. 生成二维码(云服务或本地库)

  • 云服务(不落地文件):推荐 https://api.qrtool.cn/
    参考参数说明,见文档链接:qrtool.cn 开放参数
$qrUrl = 'https://api.qrtool.cn/?' . http_build_query([ 'text' => $otpauth, 'size' => 240, // 宽高 'margin' => 2, // 白边 'level' => 'M', // 容错级别:L/M/Q/H ]);
  • 或使用本地 QR 生成库(如 PHP 扩展/JS 前端渲染),减少对外网依赖。

4. 验证 TOTP

function computeTotp(string $secretBytes, int $time, int $period = 30): string { $counter = intdiv($time, $period); $msg = pack('N*', 0) . pack('N*', $counter); // 8 字节 $hmac = hash_hmac('sha1', $msg, $secretBytes, true); $offset = ord($hmac[19]) & 0x0F; $bin = substr($hmac, $offset, 4); $val = unpack('N', $bin)[1] & 0x7FFFFFFF; return str_pad((string)($val % 1000000), 6, '0', STR_PAD_LEFT); } function verifyTotp(string $account, string $code, int $window = 1, int $period = 30): bool { if (!preg_match('/^\d{6}$/', $code)) return false; // 拿到 secretBytes(从存储取,或按约定规则派生) $secretBytes = ...; // decode Base32 or from HMAC派生的原始字节 $now = time(); for ($w = -$window; $w <= $window; $w++) { if (hash_equals(computeTotp($secretBytes, $now + $w * $period, $period), $code)) { return true; } } return false; }

前端实现要点

绑定模态的两步体验

  • 步骤 1:输入“账号 + 密码”,点击“生成二维码”。
  • 步骤 2:显示二维码与 Secret,提示“仅需扫码一次,后续输入验证码即可”。
  • 二维码支持点击刷新(追加时间戳防缓存)。
  • 可在
    localStorage
    记住最近一次生成的二维码(建议设置有效期,如 7 天),下次打开直接展示,点“删除”再清空。

登录表单的模式切换

  • 提供“密码登录 / 代码登录”切换(PC 端)。
  • “代码登录”仅输入 6 位 TOTP;支持记忆用户上一次选择的登录方式(localStorage)。
  • 手机端可只保留密码登录入口(按业务需要)。

安全与合规建议

  • 必要的身份校验:生成二维码前务必确保用户已通过“已知身份”校验(如用户名+密码正确)。
  • 传输安全:全站 HTTPS。
  • 限流与防枚举:接口级别加速率限制(基于 IP/用户名/设备指纹)。
  • 时间同步:TOTP 依赖时间;提示用户开启自动对时。服务器端也要对时。
  • 存储安全:
  • 若能持久化,Secret 应加密存储或 KMS 托管;备份码/恢复流程要完善。
  • 无持久化时,优先选择“与密码哈希绑定的派生方案”,降低被离线推导风险。
  • 设备丢失/换机:提供重置 MFA 的客服/自助流程(邮箱验证、短信、人工审核等)。
  • 风险场景二审:高风险操作(如提现、修改安全项等),可以强制追加 TOTP 二次确认。
  • 审计与告警:记录绑定、解绑、失败尝试等;异常频率触发告警。

体验优化建议

  • 让“扫描二维码”与“手动输入密钥”两种方式并存。
  • 给出明确文案:
  • “仅需扫码一次,后续登录输入验证码即可”
  • “验证码每 30 秒更新”
  • “验证码无效请检查手机时间是否自动同步”
  • 错误提示用轻量 Toast,不要阻断表单输入。
  • 对桌面端记忆上次登录方式;对移动端可隐藏 MFA(按业务决定)。

常见问题(FAQ)

  • Q:换手机后无法登录?
    A:提供“重置 MFA”的路径(邮箱验证/人工审核);建议用户换机前在新设备再次绑定。
  • Q:验证码总是错?
    A:检查手机时间同步;检查是否输入的是当前滚动周期的 6 位码;服务器端可允许 ±1 时间窗口。
  • Q:二维码服务无法访问?
    A:可切换到备用 QR 服务或使用本地 QR 库;推荐 https://api.qrtool.cn/,参数 text/size/margin/level 见其开放文档。

参考链接

  • RFC 6238(TOTP)与 RFC 4226(HOTP)
  • QR 生成服务文档:qrtool.cn 开放参数
  • API 入口:https://api.qrtool.cn/

总结

TOTP‑MFA 在 Web 的落地不复杂,关键是“何时生成/如何保存/怎样验证/如何兜底”。遵循上面的绑定‑登录两阶段流程、加上合适的安全与体验策略,你就能为站点带来显著的安全提升,同时保持良好的使用体验。