Laravel API中如何使用手机号+OTP替代密码实现用户认证
Laravel 无密码 OTP 认证实现方案
核心问题解答
1. 如何禁用密码字段
不需要做额外的"禁用"操作,按以下步骤调整即可完全绕开密码逻辑:
- 数据表层面:
users表迁移文件中删除password字段,或者设置为->nullable()可选 - 模型层面:
User模型的$fillable属性中移除password字段,也可将password加入$hidden属性避免返回 - 逻辑层面:认证过程不调用默认带密码校验的方法,完全绕开密码相关逻辑
2. 最优实现方案
核心思路是OTP单独存储、校验通过后手动登录用户,不要依赖Laravel默认的密码校验逻辑:
- 不要将OTP存在
users表中,单独建otps表存储临时OTP记录,包含phone、code、expired_at字段即可,方便过期清理和发送历史追溯 - OTP校验通过后直接手动登录用户,不需要调用
auth()->attempt()方法(该方法默认会校验密码字段) - 生成OTP时先判断手机号对应的用户是否存在,不存在再创建,避免同一个手机号重复生成用户
3. 是否可以用OTP字段替代密码完成认证?
不建议这么做,原因如下:
- OTP是临时有效期凭证,频繁更新
users表存储临时数据不合理 - 无法保留OTP发送和校验历史,也没法处理同一时间多个有效OTP的场景
- 后续如果需要扩展密码登录、第三方登录等能力,改造成本极高
现有代码问题&优化实现
你当前的代码存在几个明显问题:
- 每次生成OTP都调用
User::create(),同一个手机号多次请求会重复创建用户报错 auth()->attempt()只传phone参数会默认校验失败,Laravel该方法默认要求传入password做哈希校验- 登录逻辑中
$user = new UserResource(User::find($user->id))在$user未定义的情况下调用,会直接报错 - 生成OTP接口返回OTP明文,生产环境存在严重安全风险
优化后代码
生成OTP方法
public function generateOtp(Request $request) { $request->validate([ 'phone' => 'required|regex:/^1[3-9]\d{9}$/' // 补充手机号格式校验 ]); $otp = new Otp(); $code = $otp->generate($request->phone, 6, 15); // 手机号不存在则创建用户,存在则直接返回 User::firstOrCreate( ['phone' => $request->phone] // 其他用户默认字段可补充在第二个参数数组中 ); // 补充短信发送逻辑 return response()->json(['message' => 'OTP已发送至您的手机']); // 生产环境禁止返回OTP明文 }
登录方法
public function login(Request $request) { $validator = Validator::make($request->all(), [ 'phone' => 'required', 'otp' => 'required|digits:6', ]); if ($validator->fails()) { return response()->json(['success' => false, 'data' => $validator->errors()], 400); } $otp = new Otp(); $otpResult = $otp->validate($request->phone, $request->otp); if (!$otpResult->status) { return response()->json(['code'=>2, 'message'=>'OTP已过期或不正确,请重新获取']); } // OTP校验通过后直接查询用户,手动登录绕开密码校验 $user = User::where('phone', $request->phone)->first(); if (!$user) { return ResponseHelper::notSuccessful(1502, '登录凭证错误'); } auth()->login($user); $this->revokeTokens($user->id); $token = $user->createToken('AccessToken')->accessToken; $userResource = new UserResource($user); return response()->json([ 'success' => true, 'code' => 1500, 'user' => $userResource, 'token' => $token ]); }
额外注意事项
- OTP校验成功后要标记为已使用,避免同一个OTP被多次调用登录
- 生成OTP接口加频率限制,避免被恶意刷短信接口
- 所有短信下发的OTP不要在接口响应中返回明文
内容的提问来源于stack exchange,提问作者Fateh Alrabeai
相关产品推荐
相关产品推荐

