Laravel 通过电子邮件或手机重置密码

Password reset in Laravel by email or mobile

默认情况下 Laravel 5.5 的密码重置系统适用于电子邮件,但我需要添加对手机号码的支持(通过 OTP 验证并生成令牌并重定向到密码重置页面)。我正在做所有这一部分,并且在 password_resets table 上创建了一个移动专栏。

但问题是 \Illuminate\Auth\Passwords\DatabaseTokenRepository && \Illuminate\Auth\Passwords\TokenRepositoryInterface ON exist 方法,它似乎不可配置。

public function exists(CanResetPasswordContract $user, $token)
{
    $record = (array) $this->getTable()->where(
        'email', $user->getEmailForPasswordReset()
    )->first();

    return $record &&
           ! $this->tokenExpired($record['created_at']) &&
             $this->hasher->check($token, $record['token']);
}

我需要覆盖那个方法。有太多的遗产在发生。 类 我需要扩展什么以及如何覆盖该方法。

如果您想覆盖 \Illuminate\Auth\Passwords\DatabaseTokenRepository 方法的行为,您将必须构建自己的令牌存储库,覆盖现有存储库中目前仅检查数据库中 'email' 列的方法.确保您已创建迁移以添加适当的列:

<?php

use Illuminate\Database\Migrations\Migration;
use Illuminate\Database\Schema\Blueprint;
use Illuminate\Support\Facades\Schema;

return new class extends Migration
{
    public function up(): void
    {
        $tname = config("auth.passwords.users.table");
        Schema::table($tname, fn (Blueprint $t) => $t->string("mobile", 16));
    }

    public function down(): void
    {
        $tname = config("auth.passwords.users.table");
        Schema::table($tname, fn (Blueprint $t) => $t->dropColumn("mobile"));
    }
}

然后创建您的自定义存储库:

app/Auth/DatabaseTokenRepository.php

<?php

namespace App\Auth;

use Illuminate\Auth\Passwords\DatabaseTokenRepository as DatabaseTokenRepositoryBase;
use Illuminate\Contracts\Auth\CanResetPassword as CanResetPasswordContract;
use Illuminate\Support\Carbon;

class DatabaseTokenRepository extends DatabaseTokenRepositoryBase;
{
    //
    // Override these methods to use mobile as well as email
    //
    public function create(CanResetPasswordContract $user)
    {
        $email = $user->getEmailForPasswordReset();
        $mobile = $user->getMobileForPasswordReset();
        $this->deleteExisting($user);
        $token = $this->createNewToken();
        $this->getTable()->insert($this->getPayload($email, $mobile, $token));
        return $token;
    }

    protected function deleteExisting(CanResetPasswordContract $user)
    {
        return $this->getTable()
            ->where("email", $user->getEmailForPasswordReset())
            ->orWhere("mobile", $user->getMobileForPasswordReset())
            ->delete();
    }

    protected function getPayload($email, $mobile, $token): array
    {
        return [
            "email" => $email,
            "mobile" => $mobile,
            "token" => $this->hasher->make($token),
            "created_at" => new Carbon(),
        ];
    }

    public function exists(CanResetPasswordContract $user, $token)
    {
        $record = (array) $this->getTable()
            ->where("email", $user->getEmailForPasswordReset())
            ->orWhere("mobile", $user->getMobileForPasswordReset())
            ->first();
        return $record &&
               ! $this->tokenExpired($record["created_at"]) &&
                 $this->hasher->check($token, $record["token"]);
    }

    public function recentlyCreatedToken(CanResetPasswordContract $user)
    {
        $record = (array) $this->getTable()
            ->where("email", $user->getEmailForPasswordReset())
            ->orWhere("mobile", $user->getMobileForPasswordReset())
            ->first();

        return $record && $this->tokenRecentlyCreated($record['created_at']);
    }
}

现在您将需要使用此自定义令牌存储库而不是默认存储库。所以你必须覆盖另一个 class.

app/Auth/PasswordBrokerManager.php

<?php
namespace App\Auth;

use Illuminate\Support\Str;
use Illuminate\Auth\Passwords\PasswordBrokerManager as PasswordBrokerManagerBase;

class PasswordBrokerManager extends PasswordBrokerManagerBase
{
    protected function createTokenRepository(array $config)
    {
        $key = $this->app['config']['app.key'];
        if (Str::startsWith($key, 'base64:')) {
            $key = base64_decode(substr($key, 7));
        }
        $connection = $config['connection'] ?? null;
        // return an instance of your new repository
        // it's in the same namespace, no need to alias it
        return new DatabaseTokenRepository(
            $this->app['db']->connection($connection),
            $this->app['hash'],
            $config['table'],
            $key,
            $config['expire']
        );
    }
}

现在您已经创建了一个自定义代理来使用您的自定义存储库。您需要一个新的服务提供商才能使用它。

app/Providers/PasswordResetServiceProvider.php

<?php
namespace App\Providers;

use App\Auth\PasswordBrokerManager;
use Illuminate\Auth\Passwords\PasswordResetServiceProvider as PasswordResetServiceProviderBase;

class PasswordResetServiceProvider extends PasswordResetServiceProviderBase
{
    protected function registerPasswordBroker()
    {
        $this->app->singleton('auth.password', function ($app) {
            // reference your new broker
            // the rest of the method code is unchanged
            return new PasswordBrokerManager($app);
        });
        $this->app->bind('auth.password.broker', function ($app) {
            return $app->make('auth.password')->broker();
        });
    }
}

接下来,将默认密码重置服务提供商替换为应用程序配置中的自定义服务提供商: app/config/app.php

<?php

return [
    "providers" => [
        ...
        // Illuminate\Auth\Passwords\PasswordResetServiceProvider::class,
        App\Providers\PasswordResetServiceProvider::class,
        ...
    ],
];

最后,在您的用户模型上定义 getMobileForPasswordReset() 方法:

<?php

namespace App;

use Illuminate\Foundation\Auth\User as Authenticatable;
...

class User extends Authenticatable
{
    ...
    public method getMobileForPasswordReset()
    {
        return $this->mobile;
    }
}