重置密码
简介
大多数 Web 应用程序都为用户提供了一种重置忘记密码的方法。Laravel 提供了便捷的服务来发送密码重置链接和安全地重置密码,而不是强迫你为你创建的每个应用程序手动重新实现这一功能。
NOTE
想快速入门?在一个新的 Laravel 应用程序中安装一个 Laravel 应用程序启动套件。Laravel 的启动套件将负责构建你的整个认证系统,包括重置忘记的密码。
配置
你应用程序的密码重置配置文件存储在 config/auth.php 中。请务必查看此文件中可用的选项。默认情况下,Laravel 配置为使用 database 密码重置驱动。
密码重置 driver 配置选项定义了密码重置数据的存储位置。Laravel 包含两个驱动:
database- 密码重置数据存储在关系数据库中。cache- 密码重置数据存储在你基于缓存的存储之一中。
驱动前提条件
Database
使用默认的 database 驱动时,必须创建一个表来存储应用程序的密码重置令牌。通常,这包含在 Laravel 的默认 0001_01_01_000000_create_users_table.php 数据库迁移中。
Cache
还有一个可用的缓存驱动用于处理密码重置,它不需要专用的数据库表。条目使用用户的电子邮件地址作为键,因此请确保你没有在应用程序的其他地方使用电子邮件地址作为缓存键:
'passwords' => [
'users' => [
'driver' => 'cache',
'provider' => 'users',
'store' => 'passwords', // 可选...
'expire' => 60,
'throttle' => 60,
],
],为了防止调用 artisan cache:clear 刷新你的密码重置数据,你可以选择使用 store 配置键指定一个单独的缓存存储。该值应对应于你的 config/cache.php 配置值中配置的存储。
模型准备
在使用 Laravel 的密码重置功能之前,你的应用程序的 App\Models\User 模型必须使用 Illuminate\Notifications\Notifiable trait。通常,这个 trait 已经包含在随新 Laravel 应用程序创建的默认 App\Models\User 模型中。
接下来,验证你的 App\Models\User 模型是否实现了 Illuminate\Contracts\Auth\CanResetPassword 契约。框架附带的 App\Models\User 模型已经实现了这个接口,并使用 Illuminate\Auth\Passwords\CanResetPassword trait 来包含实现该接口所需的方法。
配置受信任主机
默认情况下,Laravel 将响应它收到的所有请求,无论 HTTP 请求的 Host 标头的内容如何。此外,在 Web 请求期间生成指向你应用程序的绝对 URL 时,将使用 Host 标头的值。
通常,你应该配置你的 Web 服务器(例如 Nginx 或 Apache),使其仅将匹配给定主机名的请求发送到你的应用程序。但是,如果你无法直接自定义你的 Web 服务器,并且需要指示 Laravel 仅响应某些主机名,你可以通过在应用程序的 bootstrap/app.php 文件中使用 trustHosts 中间件方法来做到这一点。当你的应用程序提供密码重置功能时,这一点尤其重要。
要了解有关此中间件方法的更多信息,请查阅 TrustHosts 中间件文档。
路由
为了正确实现对允许用户重置密码的支持,我们需要定义几个路由。首先,我们需要一对路由来处理允许用户通过其电子邮件地址请求密码重置链接。其次,一旦用户访问通过电子邮件发送给他们的密码重置链接并完成密码重置表单,我们需要一对路由来处理实际重置密码。
请求密码重置链接
密码重置链接请求表单
首先,我们将定义请求密码重置链接所需的路由。首先,我们将定义一个路由,该路由返回一个带有密码重置链接请求表单的视图:
Route::get('/forgot-password', function () {
return view('auth.forgot-password');
})->middleware('guest')->name('password.request');此路由返回的视图应包含一个包含 email 字段的表单,该字段将允许用户为给定的电子邮件地址请求密码重置链接。
处理表单提交
接下来,我们将定义一个路由,用于处理来自“忘记密码”视图的表单提交请求。此路由将负责验证电子邮件地址并将密码重置请求发送给相应的用户:
use Illuminate\Http\Request;
use Illuminate\Support\Facades\Password;
Route::post('/forgot-password', function (Request $request) {
$request->validate(['email' => 'required|email']);
$status = Password::sendResetLink(
$request->only('email')
);
return $status === Password::ResetLinkSent
? back()->with(['status' => __($status)])
: back()->withErrors(['email' => __($status)]);
})->middleware('guest')->name('password.email');在继续之前,让我们更详细地检查一下这个路由。首先,验证请求的 email 属性。接下来,我们将使用 Laravel 内置的“密码代理”(通过 Password 门面)向用户发送密码重置链接。密码代理将负责通过给定字段(在此情况下是电子邮件地址)检索用户,并通过 Laravel 内置的通知系统向用户发送密码重置链接。
sendResetLink 方法返回一个“状态”标识。此状态可以使用 Laravel 的本地化辅助函数进行翻译,以便向用户显示有关其请求状态的用户友好消息。密码重置状态的翻译由你应用程序的 lang/{lang}/passwords.php 语言文件确定。状态标识的每个可能值对应的条目都位于 passwords 语言文件中。
NOTE
默认情况下,Laravel 应用程序骨架不包含 lang 目录。如果你想自定义 Laravel 的语言文件,可以通过 lang:publish Artisan 命令发布它们。
你可能想知道当调用 Password 门面的 sendResetLink 方法时,Laravel 如何知道如何从你的应用程序数据库中检索用户记录。Laravel 密码代理利用你的认证系统的“用户提供器”来检索数据库记录。密码代理使用的用户提供器在你的 config/auth.php 配置文件的 passwords 配置数组中配置。要了解有关编写自定义用户提供器的更多信息,请查阅认证文档。
NOTE
当手动实现密码重置时,你需要自己定义视图和路由的内容。如果你想要包含所有必要认证和验证逻辑的脚手架,请查看 Laravel 应用程序启动套件。
重置密码
密码重置表单
接下来,我们将定义必要的路由,以便在用户点击通过电子邮件发送给他们的密码重置链接并提供新密码时实际重置密码。首先,让我们定义将显示重置密码表单的路由,该表单在用户点击重置密码链接时显示。此路由将接收一个 token 参数,我们稍后将使用它来验证密码重置请求:
Route::get('/reset-password/{token}', function (string $token) {
return view('auth.reset-password', ['token' => $token]);
})->middleware('guest')->name('password.reset');此路由返回的视图应显示一个包含 email 字段、password 字段、password_confirmation 字段和一个隐藏的 token 字段的表单,该字段应包含我们路由接收到的秘密 $token 的值。
处理表单提交
当然,我们需要定义一个路由来实际处理密码重置表单提交。此路由将负责验证传入请求并更新数据库中用户的密码:
use App\Models\User;
use Illuminate\Auth\Events\PasswordReset;
use Illuminate\Http\Request;
use Illuminate\Support\Facades\Hash;
use Illuminate\Support\Facades\Password;
use Illuminate\Support\Str;
Route::post('/reset-password', function (Request $request) {
$request->validate([
'token' => 'required',
'email' => 'required|email',
'password' => 'required|min:8|confirmed',
]);
$status = Password::reset(
$request->only('email', 'password', 'password_confirmation', 'token'),
function (User $user, string $password) {
$user->forceFill([
'password' => Hash::make($password)
])->setRememberToken(Str::random(60));
$user->save();
event(new PasswordReset($user));
}
);
return $status === Password::PasswordReset
? redirect()->route('login')->with('status', __($status))
: back()->withErrors(['email' => [__($status)]]);
})->middleware('guest')->name('password.update');在继续之前,让我们更详细地检查一下这个路由。首先,验证请求的 token、email 和 password 属性。接下来,我们将使用 Laravel 内置的“密码代理”(通过 Password 门面)来验证密码重置请求的凭证。
如果提供给密码代理的令牌、电子邮件地址和密码有效,则传递给 reset 方法的闭包将被调用。在这个闭包中,它接收用户实例和提供给密码重置表单的明文密码,我们可以更新数据库中用户的密码。
reset 方法返回一个“状态”标识。此状态可以使用 Laravel 的本地化辅助函数进行翻译,以便向用户显示有关其请求状态的用户友好消息。密码重置状态的翻译由你应用程序的 lang/{lang}/passwords.php 语言文件确定。状态标识的每个可能值对应的条目都位于 passwords 语言文件中。如果你的应用程序不包含 lang 目录,你可以使用 lang:publish Artisan 命令创建它。
在继续之前,你可能想知道当调用 Password 门面的 reset 方法时,Laravel 如何知道如何从你的应用程序数据库中检索用户记录。Laravel 密码代理利用你的认证系统的“用户提供器”来检索数据库记录。密码代理使用的用户提供器在你的 config/auth.php 配置文件的 passwords 配置数组中配置。要了解有关编写自定义用户提供器的更多信息,请查阅认证文档。
删除过期的令牌
如果你正在使用 database 驱动,过期的密码重置令牌仍将存在于你的数据库中。但是,你可以使用 auth:clear-resets Artisan 命令轻松删除这些记录:
php artisan auth:clear-resets如果你想自动化这个过程,可以考虑将该命令添加到你的应用程序的调度器中:
use Illuminate\Support\Facades\Schedule;
Schedule::command('auth:clear-resets')->everyFifteenMinutes();自定义
重置链接自定义
你可以使用 ResetPassword 通知类提供的 createUrlUsing 方法自定义密码重置链接 URL。此方法接受一个闭包,该闭包接收接收通知的用户实例以及密码重置链接令牌。通常,你应该在应用程序的 AppServiceProvider 的 boot 方法中调用此方法:
use App\Models\User;
use Illuminate\Auth\Notifications\ResetPassword;
/**
* 引导任何应用程序服务。
*/
public function boot(): void
{
ResetPassword::createUrlUsing(function (User $user, string $token) {
return 'https://example.com/reset-password?token='.$token;
});
}重置邮件自定义
你可以轻松地修改用于向用户发送密码重置链接的通知类。首先,在你的 App\Models\User 模型上覆盖 sendPasswordResetNotification 方法。在此方法中,你可以使用你自己创建的任何通知类发送通知。密码重置 $token 是该方法接收的第一个参数。你可以使用此 $token 构建你选择的密码重置 URL,并将你的通知发送给用户:
use App\Notifications\ResetPasswordNotification;
/**
* 向用户发送密码重置通知。
*
* @param string $token
*/
public function sendPasswordResetNotification($token): void
{
$url = 'https://example.com/reset-password?token='.$token;
$this->notify(new ResetPasswordNotification($url));
}