You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

如何在Laravel Filament后台登录中使用自定义SMS接口实现2FA认证

如何在Laravel Filament后台登录中使用自定义SMS接口实现2FA认证

嘿,这问题我熟!要在Laravel Filament后台登录里用上你自己的SMS接口做2FA认证,咱们可以通过自定义登录流程来实现,一步步来:

第一步:确保你的SMS发送Trait能正常工作

你已经写好了OTPmessageTrait,先别急着集成,先单独测试下这个Trait的sendOtp方法能不能正常发短信。比如在Artisan Tinker里跑个测试命令:

php artisan tinker

然后执行:

use App\Models\User;
User::first()->sendOtp('96654XXXXXX', '测试验证码:123456');

如果能成功收到短信,说明这个Trait没问题,可以继续下一步。另外记得把用户模型和这个Trait关联上,还要确保用户表有phone字段存手机号:

// app/Models/User.php
use App\OTPmessageTrait;

class User extends Authenticatable
{
    use OTPmessageTrait;
    // ...其他原有代码

    // 封装一个获取手机号的方法,方便后续调用
    public function getPhoneForOtp()
    {
        return $this->phone; // 这里的phone是你用户表存手机号的字段,要符合SMS接口的格式要求
    }
}

第二步:生成自定义Filament登录页面

Filament允许我们替换默认的登录页面来添加自定义逻辑,先跑命令生成自定义登录页:

php artisan make:filament-page Login --section=Auth

生成的文件在app/Filament/Pages/Auth/Login.php,接下来我们要修改这个文件,把登录流程改成「邮箱密码验证→发送OTP→验证OTP→登录成功」的三步逻辑。

第三步:修改自定义登录页面的逻辑

打开生成的Login.php,替换成下面的代码,我都加了注释,你可以跟着调整:

// app/Filament/Pages/Auth/Login.php
namespace App\Filament\Pages\Auth;

use Filament\Pages\Auth\Login as BaseLogin;
use Illuminate\Support\Facades\Cache;
use Illuminate\Support\Str;
use Filament\Notifications\Notification;
use Illuminate\Support\Facades\Hash;
use App\Models\User;

class Login extends BaseLogin
{
    // 存储用户输入的OTP和临时用户ID
    public $otp = '';
    public $showOtpForm = false;
    public $pendingUserId = null;

    // 页面加载时判断是否需要直接显示OTP输入框
    public function mount(): void
    {
        parent::mount();
        if (session()->has('pending_otp_user')) {
            $this->pendingUserId = session()->get('pending_otp_user');
            $this->showOtpForm = true;
        }
    }

    // 重写核心的登录验证逻辑
    public function authenticate()
    {
        // 如果当前显示的是OTP输入框,就走OTP验证流程
        if ($this->showOtpForm) {
            return $this->verifyOtpCode();
        }

        // 否则先验证邮箱和密码
        $formData = $this->form->getState();
        $user = User::where('email', $formData['email'])->first();

        // 验证邮箱密码是否正确
        if (!$user || !Hash::check($formData['password'], $user->password)) {
            Notification::make()
                ->title('邮箱或密码不对哦')
                ->danger()
                ->send();
            return;
        }

        // 生成6位数字OTP,你也可以调整长度
        $otpCode = Str::random(6, '0123456789');
        // 把OTP存到缓存里,15分钟过期
        $cacheKey = "user_{$user->id}_otp";
        Cache::put($cacheKey, $otpCode, now()->addMinutes(15));

        // 调用你的SMS接口发送验证码
        try {
            $user->sendOtp($user->getPhoneForOtp(), "你的Filament后台登录验证码是:{$otpCode},15分钟内有效,别告诉别人哦!");
        } catch (\Exception $e) {
            Notification::make()
                ->title('验证码发送失败了,稍后再试试?')
                ->danger()
                ->send();
            return;
        }

        // 把用户ID存到会话,标记为待验证OTP状态
        session()->put('pending_otp_user', $user->id);
        $this->pendingUserId = $user->id;
        $this->showOtpForm = true;
    }

    // 单独写OTP验证的逻辑
    protected function verifyOtpCode()
    {
        $cacheKey = "user_{$this->pendingUserId}_otp";
        $storedOtp = Cache::get($cacheKey);

        // 验证OTP是否正确或过期
        if (!$storedOtp || $storedOtp !== $this->otp) {
            Notification::make()
                ->title('验证码不对或者过期啦')
                ->danger()
                ->send();
            return;
        }

        // 验证通过,登录用户
        $user = User::findOrFail($this->pendingUserId);
        auth()->login($user, $this->form->getState('remember'));

        // 清理缓存和会话里的临时数据
        Cache::forget($cacheKey);
        session()->forget('pending_otp_user');

        // 跳转到用户原本要去的页面
        $this->redirectIntended($this->getRedirectUrl());
    }

    // 根据当前状态切换表单字段
    protected function getFormSchema(): array
    {
        if ($this->showOtpForm) {
            return [
                \Filament\Forms\Components\TextInput::make('otp')
                    ->label('短信验证码')
                    ->required()
                    ->length(6)
                    ->numeric()
                    ->autofocus(),
            ];
        }

        // 不是OTP状态就用默认的邮箱密码表单
        return parent::getFormSchema();
    }

    // 根据当前状态切换按钮文字
    protected function getSubmitButtonLabel(): string
    {
        return $this->showOtpForm ? '验证验证码并登录' : '获取验证码';
    }
}

第四步:告诉Filament用咱们的自定义登录页

打开config/filament.php配置文件,找到pages配置项,把默认的登录页替换成咱们刚写的:

// config/filament.php
'pages' => [
    'login' => \App\Filament\Pages\Auth\Login::class,
],

一些要注意的细节

  • 确保用户表的phone字段格式和你的SMS接口要求一致(比如要国际号段,不带+号或者带+号,得和你Trait里的dests参数格式匹配)
  • 可以调整OTP的过期时间,比如改成10分钟,或者限制用户请求OTP的次数防止滥用
  • 如果用户多次输错OTP,可以考虑临时锁定用户一段时间,提升安全性
  • 记得在.env里填好你的SMS接口配置:SMS_API_USERNAME、SMS_API_TOKEN、SMS_API_SENDER_ID,还要确保这些配置能被Trait正确读取

这样一套下来,用户登录Filament后台就需要先输邮箱密码,然后收短信验证码,验证通过后才能进入后台,完美用上你自己的SMS接口做2FA啦!

备注:内容来源于stack exchange,提问作者Pierre Sameh

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.04.16 02:54:37