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
相关产品推荐
相关产品推荐

