最稳妥的选择是 robthree/twofactorauth,它兼容 PHP 7.4–8.3、支持 TOTP 标准、含 QR 码生成与验证逻辑且文档清晰;避免使用已归档的 sonata-project/google-authenticator 或依赖废弃 mcrypt 的 phpga/phpga。

composer require 时选哪个包最稳妥
直接用 sonata-project/google-authenticator 会踩坑——它已归档,PHP 8+ 兼容性差,且不维护。实际项目里推荐 robthree/twofactorauth,它支持 TOTP(Google Authenticator 标准协议)、兼容 PHP 7.4–8.3、有完整 QR 码生成和验证逻辑,且文档清晰。
执行命令:
composer require robthree/twofactorauth
- 别装
phpga/phpga:底层用 mcrypt,PHP 7.2+ 已移除,运行时报Class 'mcrypt_module_open' not found - 如果项目已用 Laravel,可考虑
pragmarx/google2fa-laravel,但它只是封装层,底层仍依赖pragmarx/google2fa,而后者对时钟偏移容忍度低,生产环境易因服务器时间不同步导致验证失败 - 安装后检查 vendor/robthree/twofactorauth/src/TwoFactorAuth.php 是否存在,确认自动加载生效
生成密钥和 QR 码的正确写法
用户首次绑定 Google Authenticator 时,不能直接硬编码密钥;必须为每个用户生成唯一、安全的 base32 密钥,并拼进 otpauth:// URI。否则多人共用密钥会导致交叉验证通过。
示例代码片段:
$tfa = new \RobThree\Auth\TwoFactorAuth('MyApp');
$secret = $tfa->createSecret(); // 生成 16 字符 base32 密钥
$qrCodeUrl = $tfa->getQRCodeImageAsDataUri('user@domain.com', $secret); // 返回 data:image/png;base64,...
-
$tfa->createSecret()默认长度 16,足够安全;传参如createSecret(32)不必要,反而增加存储和传输开销 -
getQRCodeImageAsDataUri()依赖 GD 扩展,若报Call to undefined function imagecreate(),需启用 php-gd - URI 中的 issuer(第一个参数)建议与应用名一致,否则 iOS 版 Google Authenticator 可能显示为空白条目
验证输入时钟偏移和重放攻击怎么防
用户输入 6 位验证码后,不能只调用一次 $tfa->verifyCode($secret, $input) 就完事。TOTP 基于时间窗口,标准是 30 秒,但网络延迟或手机时钟偏差可能导致用户输入的是前一个或后一个窗口的码。
robthree/twofactorauth 默认只校验当前时间窗口,需手动放宽范围:
$valid = $tfa->verifyCode($secret, $input, 2); // 第三个参数是允许的窗口数,2 表示 ±2 个 30 秒窗口
- 设为 0:仅校验当前窗口,用户手机慢 35 秒就永远失败
- 设为 2:覆盖 ±60 秒,兼顾可用性与安全性;设为 3 或更大,会显著提升重放攻击风险
- 验证通过后,务必记录该码的使用时间,防止同一码在有效期内重复提交(简单方案:缓存
"tfa_used_{$userId}_{$code}",TTL 设为 60 秒) - 不要把
$secret存数据库明文——它等同于用户二步验证的“密码”,应像密码一样对待;至少用password_hash($secret, PASSWORD_ARGON2ID)加密存储
Laravel 项目里如何避免 Session 冲突
在 Laravel 中启用二步验证后,用户登录成功但未完成 TOTP 验证时,处于“半登录”状态。此时若直接写 session,后续中间件可能误判为已完全认证,导致绕过二次验证。
- 不要在登录后立刻调用
Auth::login($user);改用自定义 guard 或临时 session key,例如session(['tfa_pending_id' => $user->id]) - 验证通过后再执行
Auth::login($user),并清空tfa_pending_id - 中间件里检查
Auth::check() && !session('tfa_verified')时,应重定向到 TOTP 输入页,而不是放行 - CSRF token 在 TOTP 表单页必须存在,否则 POST 提交时 Laravel 报
TokenMismatchException;确保表单包含@csrf


















