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

Laravel应用基于手机号与短信验证码的认证系统改造方法(MongoDB数据库)

刚好做过类似的需求,给你一步步拆解怎么改造Laravel的认证系统来适配手机号+短信验证码的登录注册,同时兼容MongoDB:

一、先搞定MongoDB的基础适配

首先得让Laravel能正常连接MongoDB,我们用社区成熟的jenssegers/mongodb扩展:

  • 安装扩展:在项目根目录执行 composer require jenssegers/mongodb
  • 配置数据库:打开config/database.php,在connections数组里添加MongoDB配置:
    'mongodb' => [
        'driver' => 'mongodb',
        'host' => env('DB_HOST', '127.0.0.1'),
        'port' => env('DB_PORT', 27017),
        'database' => env('DB_DATABASE'),
        'username' => env('DB_USERNAME'),
        'password' => env('DB_PASSWORD'),
        'options' => [
            'database' => 'admin' // 设置认证数据库
        ]
    ],
    
    然后把默认数据库连接改成mongodb,或者在User模型里单独指定。
  • 修改User模型:让它继承MongoDB的模型,同时去掉不需要的字段和特性:
    <?php
    
    namespace App\Models;
    
    use Jenssegers\Mongodb\Auth\User as Authenticatable;
    use Laravel\Sanctum\HasApiTokens; // API项目保留,Web项目可选
    
    class User extends Authenticatable
    {
        use HasApiTokens; // 根据需求调整
    
        protected $connection = 'mongodb'; // 指定MongoDB连接
        protected $collection = 'users'; // 指定集合名
        protected $fillable = [
            'phone', // 只保留手机号核心字段
            // 可添加其他业务字段,比如nickname、avatar等
        ];
    
        // 因为不用密码验证,重写该方法返回空值即可
        public function getAuthPassword()
        {
            return '';
        }
    }
    
二、实现短信验证码的发送与存储逻辑

验证码是临时凭证,不适合存在用户集合里,用Laravel缓存存储最便捷:

  • 创建一个SmsController处理验证码发送:
    <?php
    
    namespace App\Http\Controllers;
    
    use Illuminate\Http\Request;
    use Illuminate\Support\Facades\Cache;
    use Illuminate\Support\Facades\Validator;
    
    class SmsController extends Controller
    {
        public function send(Request $request)
        {
            // 验证手机号格式
            $validator = Validator::make($request->all(), [
                'phone' => 'required|regex:/^1[3-9]\d{9}$/',
            ]);
    
            if ($validator->fails()) {
                return response()->json(['message' => '手机号格式错误'], 400);
            }
    
            $phone = $request->phone;
            // 限制每分钟发送次数,防止恶意刷短信
            $sendCountKey = "sms_send_count_{$phone}";
            if (Cache::has($sendCountKey) && Cache::get($sendCountKey) >= 3) {
                return response()->json(['message' => '发送过于频繁,请稍后再试'], 429);
            }
    
            // 生成4位随机验证码
            $code = rand(1000, 9999);
            // 缓存验证码,有效期5分钟
            Cache::put("sms_verify_{$phone}", $code, 300);
            // 累计发送次数,有效期1分钟
            Cache::increment($sendCountKey, 1);
            Cache::put($sendCountKey, Cache::get($sendCountKey), 60);
    
            // 这里替换为实际短信服务商API调用(如阿里云、腾讯云短信)
            // $smsService = new \App\Services\SmsService();
            // $smsService->sendVerifyCode($phone, $code);
    
            return response()->json(['message' => '验证码发送成功']);
        }
    }
    
三、改造认证逻辑(登录+自动注册)

用户无需提前注册,我们可以实现「登录即注册」的逻辑:

  • 创建自定义LoginController,重写核心认证方法:
    <?php
    
    namespace App\Http\Controllers\Auth;
    
    use App\Http\Controllers\Controller;
    use App\Models\User;
    use Illuminate\Http\Request;
    use Illuminate\Support\Facades\Auth;
    use Illuminate\Support\Facades\Cache;
    use Illuminate\Support\Facades\Validator;
    
    class LoginController extends Controller
    {
        public function showLoginForm()
        {
            // 返回自定义登录视图,仅包含手机号和验证码输入框
            return view('auth.login');
        }
    
        public function login(Request $request)
        {
            $validator = Validator::make($request->all(), [
                'phone' => 'required|regex:/^1[3-9]\d{9}$/',
                'code' => 'required|digits:4',
            ]);
    
            if ($validator->fails()) {
                return back()->withErrors($validator)->withInput();
            }
    
            $phone = $request->phone;
            $code = $request->code;
    
            // 验证验证码有效性
            $cacheCode = Cache::get("sms_verify_{$phone}");
            if (!$cacheCode || $cacheCode != $code) {
                return back()->withErrors(['code' => '验证码错误或已过期'])->withInput();
            }
    
            // 查找或自动创建用户
            $user = User::firstOrCreate(['phone' => $phone]);
    
            // 登录用户
            Auth::login($user);
    
            // 清除验证码,防止重复使用
            Cache::forget("sms_verify_{$phone}");
    
            // 跳转至首页或指定页面
            return redirect()->intended('/');
        }
    
        public function logout(Request $request)
        {
            Auth::logout();
            $request->session()->invalidate();
            $request->session()->regenerateToken();
            return redirect('/');
        }
    }
    
四、配置路由

替换默认认证路由,在routes/web.php中添加:

// 短信验证码发送路由
Route::post('/send-sms', [\App\Http\Controllers\SmsController::class, 'send'])->name('send.sms');

// 登录/登出路由
Route::get('/login', [\App\Http\Controllers\Auth\LoginController::class, 'showLoginForm'])->name('login');
Route::post('/login', [\App\Http\Controllers\Auth\LoginController::class, 'login']);
Route::post('/logout', [\App\Http\Controllers\Auth\LoginController::class, 'logout'])->name('logout');
五、前端登录页面示例(Blade模板)

创建resources/views/auth/login.blade.php:

<!DOCTYPE html>
<html>
<head>
    <title>手机号登录</title>
    <link href="https://cdn.jsdelivr.net/npm/bootstrap@5.0.2/dist/css/bootstrap.min.css" rel="stylesheet">
</head>
<body>
    <div class="container mt-5">
        <div class="row justify-content-center">
            <div class="col-md-6">
                <div class="card">
                    <div class="card-header">手机号登录</div>
                    <div class="card-body">
                        <form method="POST" action="{{ route('login') }}">
                            @csrf

                            <div class="mb-3">
                                <label for="phone" class="form-label">手机号</label>
                                <input id="phone" type="text" class="form-control @error('phone') is-invalid @enderror" name="phone" value="{{ old('phone') }}" required autofocus>
                                @error('phone')
                                    <span class="invalid-feedback" role="alert">
                                        <strong>{{ $message }}</strong>
                                    </span>
                                @enderror
                            </div>

                            <div class="mb-3 d-flex">
                                <div class="flex-grow-1 me-2">
                                    <label for="code" class="form-label">验证码</label>
                                    <input id="code" type="text" class="form-control @error('code') is-invalid @enderror" name="code" required>
                                    @error('code')
                                        <span class="invalid-feedback" role="alert">
                                            <strong>{{ $message }}</strong>
                                        </span>
                                    @enderror
                                </div>
                                <div class="align-self-end">
                                    <button type="button" class="btn btn-secondary" id="sendCodeBtn">发送验证码</button>
                                </div>
                            </div>

                            <div class="mb-3">
                                <button type="submit" class="btn btn-primary w-100">登录</button>
                            </div>
                        </form>
                    </div>
                </div>
            </div>
        </div>
    </div>

    <script src="https://cdn.jsdelivr.net/npm/jquery@3.6.0/dist/jquery.min.js"></script>
    <script>
        let countdown = 60;
        $('#sendCodeBtn').click(function() {
            let phone = $('#phone').val();
            if (!/^1[3-9]\d{9}$/.test(phone)) {
                alert('请输入正确的手机号');
                return;
            }

            let btn = $(this);
            btn.prop('disabled', true).text(countdown + '秒后重新发送');

            $.post("{{ route('send.sms') }}", { phone: phone, _token: "{{ csrf_token() }}" })
                .done(function(res) {
                    alert(res.message);
                })
                .fail(function(err) {
                    alert(err.responseJSON.message);
                    btn.prop('disabled', false).text('发送验证码');
                    countdown = 60;
                });

            let timer = setInterval(function() {
                countdown--;
                btn.text(countdown + '秒后重新发送');
                if (countdown <= 0) {
                    clearInterval(timer);
                    btn.prop('disabled', false).text('发送验证码');
                    countdown = 60;
                }
            }, 1000);
        });
    </script>
</body>
</html>
六、安全与优化建议
  • 给MongoDB的users集合的phone字段添加唯一索引,避免重复创建用户:
    可直接在MongoDB控制台执行:db.users.createIndex({phone: 1}, {unique: true})
  • 限制验证码的验证次数,比如同一个手机号5次错误后锁定1分钟;
  • 生产环境务必使用正规短信服务商,不要用模拟发送逻辑;
  • 如果是API项目,可替换为Sanctum/Passport生成Token的无状态认证;
  • 对手机号做脱敏处理,比如前端显示为138****1234。

内容的提问来源于stack exchange,提问作者mohsen

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.04.30 14:54:08