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

如何在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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.06.27 20:24:54