Laravel框架 之 ResetPassword

本文的示例代码参考resetpassword

目录

  • 开始

  • Auth模块

  • 配置邮箱

  • 重置密码

  • 源码剖析

    • 生成重置密码token

    • 校验重置密码token

开始

composer create-project laravel/laravel resetpassword --prefer-dist "5.5.*"
# 创建数据库表
php artisan migrate
php artisan make:seed UsersTableSeeder

vim database/seeds/UsersTableSeeder.php
times(2)
            ->make();

        // 让隐藏字段可见,并将数据集合转换为数组
        $user_array = $users->makeVisible(['password', 'remember_token'])->toArray();

        // 插入到数据库中
        User::insert($user_array);

        // 单独处理第一个用户的数据
        $user = User::find(1);
        $user->name = 'test';
        $user->email = '[email protected]';
        $user->save();
    }
}
vim database/seeds/DatabaseSeeder.php
call(UsersTableSeeder::class);
    }
}
# 填充表假数据
php artisan db:seed

生产假数据的工厂方法在database/factories/UserFactory.php中

$factory->define(App\User::class, function (Faker $faker) {
    return [
        'name' => $faker->name,
        'email' => $faker->unique()->safeEmail,
        'password' => '$2y$10$TKh8H1.PfQx37YgCzwiKb.KjNyWgaHb9cbcoQgdIVFlYg7B77UdFm', // secret (假数据的密码)
        'remember_token' => str_random(10),
    ];
});

Auth模块

Laravel框架自带用户认证和鉴权功能 只需要简单一步即可获得完整的Auth功能

php artisan make:auth

该命令会生成布局、注册和登录视图以及所有的认证接口的路由

  • 视图
find resources/views/auth
resources/views/auth
resources/views/auth/register.blade.php
resources/views/auth/passwords
resources/views/auth/passwords/email.blade.php
resources/views/auth/passwords/reset.blade.php
resources/views/auth/login.blade.php
  • 控制器
find app/Http/Controllers/Auth
app/Http/Controllers/Auth
app/Http/Controllers/Auth/ForgotPasswordController.php
app/Http/Controllers/Auth/LoginController.php
app/Http/Controllers/Auth/ResetPasswordController.php
app/Http/Controllers/Auth/RegisterController.php
  • 路由
cat routes/web.php| grep Auth
Auth::routes();

浏览器打开http://resetpassword.test/login

注意不要忘记配置hosts: sudo sh -c "echo '192.168.10.10 resetpassword.test' >> /etc/hosts"

输入Email: [email protected]

输入Password: secret

点击"Login"按钮即可登录成功 效果如下

Laravel框架 之 ResetPassword_第1张图片
laravel-resetpassword-01.png

配置邮箱

当重置密码时 该账号的邮箱将会收到一份Email邮件

Laravel框架的通知系统默认支持邮件频道的通知方式 这里只需稍作配置即可

本文 将以QQ邮箱为例 开启QQ的SMTP功能 并配置项目的SMTP邮件发送功能

其他邮箱的开启和配置方法与QQ邮箱基本相同 所以不做重复解释

首先 我们需要在QQ邮箱的账号设置里开启POP3和SMTP服务

详细可以参考QQ邮箱的官方文档如何打开POP3/SMTP/IMAP功能?

接着 申请授权码做为邮箱的密码

最终 .env文件详细配置效果如下

APP_URL=http://resetpassword.test

MAIL_DRIVER=smtp
MAIL_HOST=smtp.qq.com
MAIL_PORT=25
[email protected]
MAIL_PASSWORD=***
MAIL_ENCRYPTION=tls
[email protected]
MAIL_FROM_NAME=Laravel

默认配置下 发送邮件是同步的 想要使用异步队列方式发送邮件 可以参考Laravel框架 之 队列

重置密码

完成了上述准备工作后 就可以正式使用重置密码功能了

浏览器打开http://resetpassword.test/password/reset

注意不要忘记点击"Logout"按钮先退出登录

输入Email: [email protected]

点击"Send Password Reset Link"按钮即可向账号邮箱发送重置密码的邮件 效果如下

Laravel框架 之 ResetPassword_第2张图片
laravel-resetpassword-02.png

此时 登录[email protected] 即可查看到重置密码的邮件如下

Laravel框架 之 ResetPassword_第3张图片
laravel-resetpassword-03.png

点击重置密码链接 即可重置密码 效果如下

Laravel框架 之 ResetPassword_第4张图片
laravel-resetpassword-04.png

默认配置下 token有效期是1小时 配置文件具体如下

// config/auth.php
return [
    'passwords' => [
        'users' => [
            'provider' => 'users',
            'table' => 'password_resets', // 重置密码所依赖的数据库表
            'expire' => 60, // token有效期 60分钟即1小时
        ],
    ],
];

注意 这里重置密码url中的token如下

75f7d7d31133fc0755f8bcfb633d704b494125c0a5d65e96f2d3eba32f59237f

同时 当我们查看数据库中的password_resets表时 可以看到该账户邮箱对应的token如下

$2y$10$H8q67cnX3z1sbZAzujd8EO03.P8rWlHC.qDcZ0VosfNx6zcOEWBKe

那么 两者到底是何关系呢? 最后 我们就来看一看重置密码的代码流程

源码剖析

生成重置密码token

// vendor/laravel/framework/src/Illuminate/Foundation/Auth/SendsPasswordResetEmails.php
trait SendsPasswordResetEmails
{
    public function sendResetLinkEmail(Request $request)
    {
        $this->validateEmail($request);

        // 发送重置密码链接
        $response = $this->broker()->sendResetLink(
            $request->only('email')
        );

        return $response == Password::RESET_LINK_SENT
                    ? $this->sendResetLinkResponse($response)
                    : $this->sendResetLinkFailedResponse($request, $response);
    }
}
// vendor/laravel/framework/src/Illuminate/Auth/Passwords/PasswordBroker.php
class PasswordBroker implements PasswordBrokerContract
{
    public function sendResetLink(array $credentials)
    {
        $user = $this->getUser($credentials);

        // 发送重置密码通知 这里发送邮件
        $user->sendPasswordResetNotification(
            $this->tokens->create($user)
        );

        return static::RESET_LINK_SENT;
    }
}
// vendor/laravel/framework/src/Illuminate/Auth/Passwords/DatabaseTokenRepository.php
class DatabaseTokenRepository implements TokenRepositoryInterface
{
    public function create(CanResetPasswordContract $user)
    {
        // 获取需要重置密码账号的邮箱
        $email = $user->getEmailForPasswordReset();

        $this->deleteExisting($user);

        // 生成重置密码token
        $token = $this->createNewToken();

        // 将token插入数据库表password_resets
        $this->getTable()->insert($this->getPayload($email, $token));

        return $token;
    }

    public function createNewToken()
    {
        return hash_hmac('sha256', Str::random(40), $this->hashKey);
    }

    protected function getPayload($email, $token)
    {
        return ['email' => $email, 'token' => $this->hasher->make($token), 'created_at' => new Carbon];
    }
}
// vendor/laravel/framework/src/Illuminate/Hashing/BcryptHasher.php
class BcryptHasher implements HasherContract
{
    public function make($value, array $options = [])
    {
        $hash = password_hash($value, PASSWORD_BCRYPT, [
            'cost' => $this->cost($options),
        ]);

        return $hash;
    }
}

校验重置密码token

// vendor/laravel/framework/src/Illuminate/Foundation/Auth/ResetsPasswords.php
trait ResetsPasswords
{
    public function reset(Request $request)
    {
        $this->validate($request, $this->rules(), $this->validationErrorMessages());

        $response = $this->broker()->reset(
            $this->credentials($request), function ($user, $password) {
                $this->resetPassword($user, $password);
            }
        );

        return $response == Password::PASSWORD_RESET
                    ? $this->sendResetResponse($response)
                    : $this->sendResetFailedResponse($request, $response);
    }
}
// vendor/laravel/framework/src/Illuminate/Auth/Passwords/PasswordBroker.php
class PasswordBroker implements PasswordBrokerContract
{
    public function reset(array $credentials, Closure $callback)
    {
        // 校验有效性: 账户, 密码, 重置密码token
        $user = $this->validateReset($credentials);

        if (! $user instanceof CanResetPasswordContract) {
            return $user;
        }

        $password = $credentials['password'];

        $callback($user, $password);

        $this->tokens->delete($user);

        return static::PASSWORD_RESET;
    }

    protected function validateReset(array $credentials)
    {
        // 校验有效性: 账户
        if (is_null($user = $this->getUser($credentials))) {
            return static::INVALID_USER;
        }

        // 校验有效性: 密码
        if (! $this->validateNewPassword($credentials)) {
            return static::INVALID_PASSWORD;
        }

        // 校验有效性: 重置密码token
        if (! $this->tokens->exists($user, $credentials['token'])) {
            return static::INVALID_TOKEN;
        }

        return $user;
    }
}
// vendor/laravel/framework/src/Illuminate/Auth/Passwords/DatabaseTokenRepository.php
class DatabaseTokenRepository implements TokenRepositoryInterface
{
    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']);
    }

    // 判断重置密码token是否过期
    protected function tokenExpired($createdAt)
    {
        return Carbon::parse($createdAt)->addSeconds($this->expires)->isPast();
    }
}
// vendor/laravel/framework/src/Illuminate/Hashing/BcryptHasher.php
class BcryptHasher implements HasherContract
{
    // 判断重置密码token是否有效
    public function check($value, $hashedValue, array $options = [])
    {
        if (strlen($hashedValue) === 0) {
            return false;
        }

        return password_verify($value, $hashedValue);
    }
}

参考

  • Laravel 的密码重置功能

  • 6.1. 用户认证

  • 6.4. 邮件通知

你可能感兴趣的:(Laravel框架 之 ResetPassword)