laravel需借助jetstream或fortify启用totp双因素认证,必须运行迁移添加two_factor_secret等字段,fortify需手动拦截登录检查2fa状态,livewire中应调用gettwofactorsecretqrcodeurl而非decrypt,恢复码须先decrypt再json_decode。

直接上结论:Laravel 本身不内置完整双因素认证流程,但通过 TwoFactorAuthenticatable trait + Jetstream 或 Fortify 可快速启用标准 TOTP(基于时间的一次性密码)方案;手动集成需补全密钥生成、验证逻辑和会话标记,否则登录后仍可绕过。
启用 TwoFactorAuthenticatable trait 后必须跑迁移
仅在 User 模型中 use TwoFactorAuthenticatable 是不够的——数据库缺字段会导致后续所有操作失败,比如调用 $user->enableTwoFactorAuthentication() 时抛出 SQLSTATE[42S22]: Column not found 错误。
- 运行
php artisan make:migration add_two_factor_columns_to_users_table - 在迁移文件中添加:
string('two_factor_secret')->nullable()、text('two_factor_recovery_codes')->nullable()、timestamp('two_factor_confirmed_at')->nullable() - 执行
php artisan migrate,别跳过;Jetstream 安装时已自动包含该迁移,但 Breeze 或手写认证需自行补全
Fortify 中二因素认证默认不拦截登录请求
Fortify 的 Features::twoFactorAuthentication() 只负责提供设置界面和密钥存储,它不会自动在登录成功后跳转到验证码页。你得自己加逻辑,否则用户输完账号密码就直接进去了,2FA 形同虚设。
- 在
config/fortify.php的features数组里确认启用了Features::twoFactorAuthentication() - 修改
app/Providers/FortifyServiceProvider.php,在boot()方法中注册自定义响应: - 使用
Fortify::authenticateUsing()包裹原始验证逻辑,并在返回前检查$user->two_factor_secret && !$request->session()->has('two-factor-authenticated') - 若条件成立,返回重定向到
/two-factor-challenge(Fortify 默认路由),而非直接返回$user
Livewire 组件里 decrypt($user->two_factor_secret) 会报错
Jetstream 的 two-factor-authentication-form.blade.php 模板里有类似 {{ decrypt($this->user->two_factor_secret) }} 的写法,但 decrypt() 在 Livewire 组件上下文中可能因密钥未加载或 session 加密配置不一致而失败,表现为空白 QR 码或“DecryptException”。
- 不要在 Blade 模板或 Livewire
render()中直接调用decrypt() - 改用
$user->getTwoFactorSecretQrCodeUrl()(该方法内部已处理解密与 URL 编码) - 或者在组件
mount()中预先解密并赋值给 public 属性:$this->qrCodeUrl = $this->user->getTwoFactorSecretQrCodeUrl(); - 确保
APP_KEY未被轮换过;一旦轮换,旧的two_factor_secret就无法再解密,用户将永久无法扫码
最易被忽略的是恢复代码的加密存储方式——two_factor_recovery_codes 字段内容是 JSON 数组经 encrypt() 处理后的字符串,不是明文。如果你用 DB::table() 直接查这个字段然后 json_decode(),会得到乱码;必须先 decrypt() 再 json_decode(),否则“Show Recovery Codes”按钮点开永远是空数组。











