密码重置是 Web 应用不可或缺的认证流程。使用启动套件时该功能会自动构建,但对于纯 API 项目或自定义 UI 的项目需要手动实现。
需要手动实现的场景:
- 仅 API 的后端(前端为 SPA / 移动应用)
- 不使用启动套件,自行构建认证 UI
- 需要完全自定义邮件通知或重置 URL 设计
如果通过启动套件(laravel new)创建项目,密码重置功能已就绪。本页说明不使用启动套件时的实现方式。
密码重置的整体流程如下:
密码重置配置在 config/auth.php 的 passwords 键中。
// config/auth.php
'passwords' => [
'users' => [
'driver' => 'database',
'provider' => 'users',
'table' => env('AUTH_PASSWORD_RESET_TOKEN_TABLE', 'password_reset_tokens'),
'expire' => 60,
'throttle' => 60,
],
],
driver — 数据存储方式(database 或 cache)
expire — 令牌有效期(分钟),默认 60 分钟
throttle — 重发受限前的等待时间(秒)
database 驱动
默认驱动。将密码重置令牌保存到数据库表 password_reset_tokens。该表包含在 Laravel 默认迁移(0001_01_01_000000_create_users_table.php)中。
'passwords' => [
'users' => [
'driver' => 'database',
'provider' => 'users',
'table' => env('AUTH_PASSWORD_RESET_TOKEN_TABLE', 'password_reset_tokens'),
'expire' => 60,
'throttle' => 60,
],
],
cache 驱动
Laravel 11 及以后可用的新选项。无需数据库表,便于以简洁配置实现密码重置。
cache 驱动将令牌保存到缓存中,无需 password_reset_tokens 表迁移。令牌以用户邮箱为键存储,请注意不要在应用其他位置以邮箱作为缓存键。
'passwords' => [
'users' => [
'driver' => 'cache',
'provider' => 'users',
'store' => 'passwords', // 可选:专用缓存存储
'expire' => 60,
'throttle' => 60,
],
],
通过 store 指定专用缓存,可避免 php artisan cache:clear 时把重置数据一并清除。该值需对应 config/cache.php 中配置的存储名称。
准备模型
要使用密码重置功能,App\Models\User 模型需要两个 trait:
<?php
namespace App\Models;
use Illuminate\Auth\Passwords\CanResetPassword;
use Illuminate\Foundation\Auth\User as Authenticatable;
use Illuminate\Notifications\Notifiable;
class User extends Authenticatable
{
use Notifiable, CanResetPassword;
// ...
}
Notifiable — 发送邮件通知所必需
CanResetPassword — 提供生成、校验密码重置令牌的方法
Laravel 默认 User 模型已经包含这些 trait。全新安装无需再加。
路由实现
密码重置需要 4 个路由。
1. 请求重置链接的表单
显示输入邮箱的表单。
// routes/web.php
Route::get('/forgot-password', function () {
return view('auth.forgot-password');
})->middleware('guest')->name('password.request');
对应的 Blade 视图:
{{-- resources/views/auth/forgot-password.blade.php --}}
<form method="POST" action="/forgot-password">
@csrf
<div>
<label for="email">邮箱</label>
<input id="email" type="email" name="email" value="{{ old('email') }}" required autofocus>
@error('email')
<span>{{ $message }}</span>
@enderror
</div>
@if (session('status'))
<div>{{ session('status') }}</div>
@endif
<button type="submit">发送重置链接</button>
</form>
2. 发送重置链接的处理
接收表单提交,通过 Password::sendResetLink() 发送重置邮件。
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');
Password::sendResetLink() 返回状态字符串:
| 状态常量 | 说明 |
|---|
Password::ResetLinkSent | 已发送重置链接 |
Password::INVALID_USER | 未找到该邮箱对应的用户 |
Password::RESET_THROTTLED | 发送受限(限流) |
3. 密码重置表单
点击邮件链接后向用户展示新密码输入表单。
Route::get('/reset-password/{token}', function (string $token) {
return view('auth.reset-password', ['token' => $token]);
})->middleware('guest')->name('password.reset');
对应的 Blade 视图:
{{-- resources/views/auth/reset-password.blade.php --}}
<form method="POST" action="/reset-password">
@csrf
<input type="hidden" name="token" value="{{ $token }}">
<div>
<label for="email">邮箱</label>
<input id="email" type="email" name="email" value="{{ old('email') }}" required autofocus>
@error('email')
<span>{{ $message }}</span>
@enderror
</div>
<div>
<label for="password">新密码</label>
<input id="password" type="password" name="password" required>
@error('password')
<span>{{ $message }}</span>
@enderror
</div>
<div>
<label for="password_confirmation">确认密码</label>
<input id="password_confirmation" type="password" name="password_confirmation" required>
</div>
<button type="submit">重置密码</button>
</form>
4. 执行密码重置
接收表单并通过 Password::reset() 更新密码。
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');
Password::reset() 的状态常量:
| 状态常量 | 说明 |
|---|
Password::PasswordReset | 密码重置成功 |
Password::INVALID_TOKEN | 令牌无效或已过期 |
Password::INVALID_USER | 未找到该邮箱对应的用户 |
令牌有效期
通过 config/auth.php 的 expire 选项以分钟为单位设置令牌有效期,默认 60 分钟。
'passwords' => [
'users' => [
'driver' => 'database',
'expire' => 60, // 60 分钟后过期
'throttle' => 60,
],
],
若使用 database 驱动,过期令牌会残留在数据库中。可用以下命令定期清理:
php artisan auth:clear-resets
建议用调度器自动化执行。
use Illuminate\Support\Facades\Schedule;
Schedule::command('auth:clear-resets')->everyFifteenMinutes();
自定义
使用自定义通知
要自定义密码重置邮件,可以在 User 模型中重写 sendPasswordResetNotification。
use App\Notifications\ResetPasswordNotification;
class User extends Authenticatable
{
use Notifiable, CanResetPassword;
/**
* 发送密码重置通知
*/
public function sendPasswordResetNotification($token): void
{
$url = 'https://example.com/reset-password?token='.$token;
$this->notify(new ResetPasswordNotification($url));
}
}
自定义重置链接 URL
在 AppServiceProvider 的 boot 方法中使用 ResetPassword::createUrlUsing() 可以修改重置链接 URL。这在需要重定向到 SPA 等另一 origin 的前端时非常有用。
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;
});
}
配置可信主机(Trusted Hosts)
密码重置链接使用 HTTP 请求的 Host 头生成。为防止不良主机导致的问题,建议在 bootstrap/app.php 中配置可信主机。
->withMiddleware(function (Middleware $middleware) {
$middleware->trustHosts(at: ['example.com']);
})
实现密码重置功能时,请务必确认 Trusted Hosts 配置。否则可能存在 Host header 注入攻击风险。
| 想做的事 | 方法 |
|---|
| 发送重置链接 | Password::sendResetLink(['email' => $email]) |
| 重置密码 | Password::reset($credentials, $callback) |
| 不使用数据库表 | 配置 cache 驱动 |
| 删除过期令牌 | php artisan auth:clear-resets |
| 自定义邮件通知 | 重写 sendPasswordResetNotification |
| 自定义重置 URL | ResetPassword::createUrlUsing() |
下一步