本文的示例代码参考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"按钮即可登录成功 效果如下
配置邮箱
当重置密码时 该账号的邮箱将会收到一份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"按钮即可向账号邮箱发送重置密码的邮件 效果如下
此时 登录[email protected] 即可查看到重置密码的邮件如下
点击重置密码链接 即可重置密码 效果如下
默认配置下 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. 邮件通知