如何在Laravel Passport中实现用户首次登录时MD5密码安全迁移至bcrypt
Laravel Passport MD5密码迁移方案失效排查与解决
我有个Laravel应用,刚从旧系统迁移了用户数据,旧系统用MD5哈希存储密码。已经通过扩展EloquentUserProvider,在validateCredentials()方法里判断密码是否为MD5格式,是的话自动转换为bcrypt哈希并保存,这套方案在Laravel Sanctum本地登录场景运行正常。但用Laravel Passport做API认证时,尝试扩展PassportUserProvider并绑定后完全没效果,想知道问题出在哪,求指导。
我尝试的代码
自定义PassportUserProvider扩展
<?php // Path: app/Providers/PassportUserProvider.php namespace App\Providers; use Laravel\Passport\PassportUserProvider as BasePassportUserProvider; use Illuminate\Contracts\Auth\Authenticatable; use Illuminate\Contracts\Auth\UserProvider; use Log; class PassportUserProvider extends BasePassportUserProvider implements UserProvider { /** * Validate a user against the given credentials. * * @param \Illuminate\Contracts\Auth\Authenticatable $user * @param array $credentials * @return bool */ public function validateCredentials(Authenticatable $user, array $credentials) { Log::build(['driver' => 'single', 'path' => storage_path('logs/MD5_rehashed.log'),])->info('PassportUserProvider Attempting to check MD5.'); $plain = $credentials['password']; // Check if the password is MD5 hashed if ($this->isMD5($user->getAuthPassword())) { // Retrieve the user model by ID using Laravel's default user provider $userModel = $this->retrieveById($user->getAuthIdentifier()); // If the user exists and MD5 password matches, update the password if ($userModel && $this->checkMD5Password($plain, $userModel)) { $userModel->password = bcrypt($plain); $userModel->save(); Log::build(['driver' => 'single', 'path' => storage_path('logs/MD5_rehashed.log'),])->info('#' . $userModel->id); return true; } } // If password is not MD5 hashed, or MD5 validation fails, fall back to default validation return parent::validateCredentials($user, $credentials); } /** * Check if the password is MD5 hashed. * * @param string $password * @return bool */ protected function isMD5($password) { return preg_match('/^[a-f0-9]{32}$/', $password); } /** * Check if the plain password matches the MD5 hash. * * @param string $plain * @param \Illuminate\Contracts\Auth\Authenticatable $userModel * @return bool */ protected function checkMD5Password($plain, $userModel) { // Implement your MD5 password check logic here // Example: return md5($plain) === $userModel->getAuthPassword(); } }
AuthServiceProvider中的绑定代码
$this->app->bind( \Laravel\Passport\PassportUserProvider::class, \App\Providers\PassportUserProvider::class );
问题根源与修正方案
1. 核心误区:PassportUserProvider不处理密码模式认证
Passport的PassportUserProvider只负责客户端凭证、个人访问令牌这类非密码场景的认证,密码模式获取token的流程完全走Laravel核心的Auth系统,所以你绑定自定义PassportUserProvider根本不会被调用。
2. 正确方案:复用Sanctum的用户提供者逻辑
既然Sanctum的方案已经生效,说明你的自定义EloquentUserProvider是正确的,只需要让Passport在密码验证时复用这套逻辑即可:
步骤1:配置默认用户提供者为自定义驱动
修改config/auth.php,将用户提供者的驱动指定为你的自定义驱动:
'providers' => [ 'users' => [ 'driver' => 'custom-eloquent', // 替换成你的自定义驱动名称 'model' => App\Models\User::class, ], ],
步骤2:注册自定义用户提供者驱动
在AuthServiceProvider的boot方法中注册驱动:
use Illuminate\Support\Facades\Auth; use App\Providers\CustomEloquentUserProvider; // 你给Sanctum用的那个扩展类 public function boot() { // 注册自定义用户提供者 Auth::provider('custom-eloquent', function ($app, array $config) { return new CustomEloquentUserProvider($app['hash'], $config['model']); }); // 其他Passport配置... Passport::routes(); }
3. 更简洁的备选方案:直接在User模型中重写验证逻辑
不想修改用户提供者的话,直接在User模型里重写密码验证方法即可,Laravel会自动调用这个方法:
use Illuminate\Foundation\Auth\User as Authenticatable; class User extends Authenticatable { // ...其他代码 /** * 验证密码是否匹配 * * @param string $password * @return bool */ public function validatePassword($password) { $storedPassword = $this->password; // 判断是否为MD5哈希 if (preg_match('/^[a-f0-9]{32}$/', $storedPassword)) { if (md5($password) === $storedPassword) { // 转换为bcrypt并保存 $this->password = bcrypt($password); $this->save(); return true; } return false; } // 非MD5密码走默认验证 return password_verify($password, $storedPassword); } }
4. 调试验证
- 在自定义逻辑中添加日志,测试时调用Passport的
/oauth/token密码模式接口,观察日志是否输出MD5验证相关内容。 - 验证用户表中的密码是否从32位MD5哈希自动更新为bcrypt格式。
内容的提问来源于stack exchange,提问作者gogo
相关产品推荐
相关产品推荐

