密码重置
介绍
大多数 Web 应用都会提供让用户重置忘记密码的功能。Laravel 为此提供了便捷的服务,可以发送密码重置链接并安全地重置密码,无需你在每个应用中手动实现这一功能。
配置
你应用的密码重置配置文件位于 config/auth.php。请务必查看该文件中提供的各项配置选项。默认情况下,Laravel 配置使用数据库(database)作为密码重置驱动。
密码重置驱动配置选项用于定义密码重置数据的存储位置。Laravel 内置了两种驱动:
- database —— 密码重置数据存储在关系型数据库中。
- cache —— 密码重置数据存储在你配置的缓存存储中。
驱动前提条件
Database
使用默认的 database 驱动时,必须创建一张表来存储应用的密码重置令牌。通常,这张表已包含在 Laravel 默认的 0001_01_01_000000_create_users_table.php 数据库迁移文件中。
缓存
Laravel 还提供了一个用于处理密码重置的缓存驱动,该驱动不需要专门的数据库表。缓存条目以用户的邮箱地址作为键,因此请确保在应用的其他地方没有使用邮箱地址作为缓存键:
'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 特征。通常,这个特征已经包含在新建 Laravel 应用时默认生成的 App\Models\User 模型中。
接下来,确保你的 App\Models\User 模型实现了 Illuminate\Contracts\Auth\CanResetPassword 接口。框架自带的 App\Models\User 模型已经实现了该接口,并通过使用 Illuminate\Auth\Passwords\CanResetPassword 特征,提供了实现接口所需的方法。
配置可信主机
默认情况下,Laravel 会响应它接收到的所有请求,而不管 HTTP 请求的 Host 头内容如何。此外,Host 头的值会在生成应用的绝对 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');
该路由返回的视图应包含一个电子邮件字段表单,用户可以通过它为指定的邮箱请求密码重置链接。
表单提交处理
接下来,我们需要定义一个路由,用于处理“忘记密码”视图的表单提交请求。此路由将负责验证电子邮件地址,并向相应用户发送密码重置请求:
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语言文件中。
注意事项:
- 默认情况下,Laravel 应用骨架不包含
lang目录。如需自定义语言文件,可通过lang:publishArtisan 命令发布。 - Laravel 在调用
sendResetLink方法时,如何从数据库中获取用户记录?密码代理使用身份验证系统的“用户提供者”来检索数据库记录。密码代理使用的用户提供者配置在config/auth.php配置文件的passwords数组中。 - 如果手动实现密码重置,需要自行定义视图和路由内容。若希望快速构建完整的身份验证和验证逻辑,可使用 Laravel 应用起始套件(starter kits)。
重置密码
密码重置表单
接下来,我们需要定义路由,以便在用户点击邮件中的密码重置链接并提交新密码后,实际完成密码重置。首先,定义显示密码重置表单的路由。当用户点击重置链接时,该表单将显示。此路由会接收一个 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方法的闭包。在闭包中,我们可以获取用户实例和表单提交的明文密码,并更新数据库中的用户密码,同时生成新的记住令牌 (remember_token)。 reset方法返回一个“状态”标识符(status slug),可通过 Laravel 本地化助手翻译,以向用户显示友好的提示信息。状态翻译存放在应用的lang/{lang}/passwords.php语言文件中。如应用中不存在lang目录,可使用lang:publishArtisan 命令创建。
注意事项:
- Laravel 在调用
Password::reset方法时,如何从数据库获取用户记录?密码代理使用身份验证系统的“用户提供者”来检索数据库记录。密码代理使用的用户提供者配置在config/auth.php的passwords配置数组中。 - 如需编写自定义用户提供者,可参考 Laravel 身份验证文档。
删除过期令牌
如果你使用的是数据库驱动,已过期的密码重置令牌仍会保留在数据库中。不过,你可以使用 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 会作为方法的第一个参数传入,你可以使用它构建自定义的密码重置 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));
}