大多数 Web 应用程序提供一种方式供用户重置他们忘记的密码。与其强迫你为每个你创建的应用程序手动重新实现此,Laravel 提供便捷服务用于发送密码重置链接和安全重置密码。
[!NOTE]
想要快速开始吗?安装一个 Laravel 应用程序入门套件 到一个全新的 Laravel 应用程序中。Laravel 的入门套件将负责搭建整个认证系统,包括重置被遗忘的密码。
您的应用程序的密码重置配置文件存储在config/auth.php。请务必在此文件中查看可用的选项。默认情况下,Laravel 配置为使用database密码重置驱动程序。
密码重置 driver 配置选项定义了密码重置数据将存储在何处。Laravel 包含两个驱动:
当使用默认的database驱动程序时,必须创建一个表来存储应用程序的密码重置令牌。通常,这包含在 Laravel 的默认0001_01_01_000000_create_users_table.php数据库迁移中。
此外,还提供一个缓存驱动程序,可用于处理密码重置,该驱动程序无需专门的数据库表。条目以用户的电子邮件地址为键,因此请确保您在应用程序的其他地方没有将电子邮件地址用作缓存键:
'passwords' => [
'users' => [
'driver' => 'cache',
'provider' => 'users',
'store' => 'passwords', // Optional...
'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 头内容如何。此外,Host 头的值将在 Web 请求期间生成指向你的应用的绝对 URL 时使用。
通常, 你应该配置你的 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 方法返回一个“状态”slug。此状态可以使用 Laravel 的 本地化 助手进行翻译,以便向用户显示有关其请求状态的用户友好消息。密码重置状态的翻译由你的应用程序的 lang/{lang}/passwords.php 语言文件决定。状态 slug 的每个可能值都在 passwords 语言文件中有一个条目。
[!注意]
默认情况下,Laravel 应用程序骨架不包含lang目录。如果您想自定义 Laravel 的语言文件,您可以通过lang:publishArtisan 命令发布它们。
您可能想知道 Laravel 是如何在调用 Password 门面的 sendResetLink 方法时,从您应用程序的数据库中检索用户记录的。Laravel 密码经纪人利用您认证系统的“用户提供者”来检索数据库记录。密码经纪人使用的用户提供者是在您的 config/auth.php 配置文件中的 passwords 配置数组中配置的。要了解有关编写自定义用户提供者的更多信息,请查阅 认证文档。
[!注意]
当手动实现密码重置时,你需要自行定义视图和路由的内容。如果你希望获得包含所有必要身份验证和验证逻辑的脚手架,请查阅 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 方法返回一个“状态” slug. 此状态可以使用 Laravel 的 本地化 助手进行翻译,以便向用户显示一条关于其请求状态的用户友好消息. 密码重置状态的翻译由您的应用程序的 lang/{lang}/passwords.php 语言文件决定. 状态 slug 的每个可能值的条目都位于 passwords 语言文件中. 如果您的应用程序不包含 lang 目录,您可以使用 lang:publish Artisan 命令创建它.
在继续之前,您可能想知道 Laravel 如何知道在调用 Password 门面的 reset 方法时从您的应用程序数据库中检索用户记录。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;
/**
* Bootstrap any application services.
*/
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;
/**
* Send a password reset notification to the user.
*
* @param string $token
*/
public function sendPasswordResetNotification($token): void
{
$url = 'https://example.com/reset-password?token='.$token;
$this->notify(new ResetPasswordNotification($url));
}