Laravel框架 之 ResetPassword

本文的示例代码参考resetpassword

目录

开始

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

vim database/seeds/UsersTableSeeder.php
<?php

use Illuminate\Database\Seeder;
use App\User;

class UsersTableSeeder extends Seeder
{
    public function run()
    {
        // 生成数据集合
        $users = factory(User::class)
            ->times(2)
            ->make();

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

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

        // 单独处理第一个用户的数据
        $user = User::find(1);
        $user->name = 'test';
        $user->email = 'yl33643@gmail.com';
        $user->save();
    }
}
vim database/seeds/DatabaseSeeder.php
<?php

use Illuminate\Database\Seeder;

class DatabaseSeeder extends Seeder
{
    public function run()
    {
         $this->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: yl33643@gmail.com

输入Password: secret

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

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
MAIL_USERNAME=yl33643@foxmail.com
MAIL_PASSWORD=***
MAIL_ENCRYPTION=tls
MAIL_FROM_ADDRESS=yl33643@foxmail.com
MAIL_FROM_NAME=Laravel

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

重置密码

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

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

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

输入Email: yl33643@gmail.com

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

laravel-resetpassword-02.png

此时 登录yl33643@gmail.com 即可查看到重置密码的邮件如下

laravel-resetpassword-03.png

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

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);
    }
}

参考

©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 218,036评论 6 506
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 93,046评论 3 395
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 164,411评论 0 354
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 58,622评论 1 293
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 67,661评论 6 392
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 51,521评论 1 304
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 40,288评论 3 418
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 39,200评论 0 276
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 45,644评论 1 314
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 37,837评论 3 336
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 39,953评论 1 348
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 35,673评论 5 346
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 41,281评论 3 329
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 31,889评论 0 22
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 33,011评论 1 269
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 48,119评论 3 370
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 44,901评论 2 355

推荐阅读更多精彩内容

  • 昨日早起,就是难得的预谋,经室友抚背的手而脱出的春梦,显得赤裸又寂寞,况且真正的春日尚未抵达,又何来“春梦”呢。 ...
    王春蚕旋臂阅读 280评论 0 2
  • 双十一过去了,不知道各位亲拿没拿到快递呢?反正我的保暖内衣今天就到了。 只是有些味道,本着洁癖男的素养,我洗了一下...
    怀侠阅读 1,455评论 1 1
  • 男孩与女孩相遇在街头的麦当劳店,当时是一个清爽而不冷冽的早晨,日出当照,微风正好,过往的人一个个赶着清晨的早班,脚...
    红缨阅读 277评论 2 1
  • 尘埃 ---荒原狼 当昨天的尘埃 落在我今天的肩膀上 我闭上眼睛祷告 眼泪从沟壑滑出 希望今天的我 不再去想昨...
    荒原狼1993阅读 276评论 0 1