> ## Documentation Index
> Fetch the complete documentation index at: https://kawax.biz/llms.txt
> Use this file to discover all available pages before exploring further.

# 密码重置

> 在 Laravel 中实现密码重置功能的方法。

## 简介

密码重置是 Web 应用不可或缺的认证流程。使用启动套件时该功能会自动构建，但对于纯 API 项目或自定义 UI 的项目需要手动实现。

**需要手动实现的场景：**

* 仅 API 的后端（前端为 SPA / 移动应用）
* 不使用启动套件，自行构建认证 UI
* 需要完全自定义邮件通知或重置 URL 设计

<Info>
  如果通过启动套件（`laravel new`）创建项目，密码重置功能已就绪。本页说明不使用启动套件时的实现方式。
</Info>

密码重置的整体流程如下：

```mermaid theme={null}
flowchart TD
    A["用户"] --> B["输入邮箱<br>GET /forgot-password"]
    B --> C["发送重置链接<br>POST /forgot-password"]
    C --> D["收到邮件<br>包含令牌 URL"]
    D --> E["显示重置表单<br>GET /reset-password/{token}"]
    E --> F["修改密码<br>POST /reset-password"]
    F --> G["重定向到登录页"]
```

## 配置

密码重置配置在 `config/auth.php` 的 `passwords` 键中。

```php theme={null}
// 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`）中。

```php theme={null}
'passwords' => [
    'users' => [
        'driver' => 'database',
        'provider' => 'users',
        'table' => env('AUTH_PASSWORD_RESET_TOKEN_TABLE', 'password_reset_tokens'),
        'expire' => 60,
        'throttle' => 60,
    ],
],
```

### cache 驱动

<Tip>
  Laravel 11 及以后可用的新选项。无需数据库表，便于以简洁配置实现密码重置。
</Tip>

`cache` 驱动将令牌保存到缓存中，无需 `password_reset_tokens` 表迁移。令牌以用户邮箱为键存储，请注意不要在应用其他位置以邮箱作为缓存键。

```php theme={null}
'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 theme={null}
<?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` — 提供生成、校验密码重置令牌的方法

<Info>
  Laravel 默认 `User` 模型已经包含这些 trait。全新安装无需再加。
</Info>

## 路由实现

密码重置需要 4 个路由。

### 1. 请求重置链接的表单

显示输入邮箱的表单。

```php theme={null}
// routes/web.php

Route::get('/forgot-password', function () {
    return view('auth.forgot-password');
})->middleware('guest')->name('password.request');
```

对应的 Blade 视图：

```blade theme={null}
{{-- 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()` 发送重置邮件。

```php theme={null}
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. 密码重置表单

点击邮件链接后向用户展示新密码输入表单。

```php theme={null}
Route::get('/reset-password/{token}', function (string $token) {
    return view('auth.reset-password', ['token' => $token]);
})->middleware('guest')->name('password.reset');
```

对应的 Blade 视图：

```blade theme={null}
{{-- 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()` 更新密码。

```php theme={null}
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 分钟。

```php theme={null}
'passwords' => [
    'users' => [
        'driver' => 'database',
        'expire' => 60, // 60 分钟后过期
        'throttle' => 60,
    ],
],
```

若使用 `database` 驱动，过期令牌会残留在数据库中。可用以下命令定期清理：

```shell theme={null}
php artisan auth:clear-resets
```

建议用调度器自动化执行。

```php theme={null}
use Illuminate\Support\Facades\Schedule;

Schedule::command('auth:clear-resets')->everyFifteenMinutes();
```

## 自定义

### 使用自定义通知

要自定义密码重置邮件，可以在 `User` 模型中重写 `sendPasswordResetNotification`。

```php theme={null}
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 的前端时非常有用。

```php theme={null}
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` 中配置可信主机。

```php theme={null}
->withMiddleware(function (Middleware $middleware) {
    $middleware->trustHosts(at: ['example.com']);
})
```

<Warning>
  实现密码重置功能时，请务必确认 Trusted Hosts 配置。否则可能存在 Host header 注入攻击风险。
</Warning>

## 小结

| 想做的事      | 方法                                             |
| --------- | ---------------------------------------------- |
| 发送重置链接    | `Password::sendResetLink(['email' => $email])` |
| 重置密码      | `Password::reset($credentials, $callback)`     |
| 不使用数据库表   | 配置 `cache` 驱动                                  |
| 删除过期令牌    | `php artisan auth:clear-resets`                |
| 自定义邮件通知   | 重写 `sendPasswordResetNotification`             |
| 自定义重置 URL | `ResetPassword::createUrlUsing()`              |

## 下一步

<Columns cols={2}>
  <Card title="认证入门" icon="lock" href="/zh/authentication">
    学习 Laravel 认证系统整体机制。
  </Card>

  <Card title="通知" icon="bell" href="/zh/notifications">
    详细了解如何自定义邮件通知。
  </Card>
</Columns>


## Related topics

- [从 laravel/ui 迁移到 Fortify 的指南](/zh/blog/ui-to-fortify.md)
- [Laravel Fortify 与 Starter Kit](/zh/advanced/fortify.md)
- [启动套件](/zh/starter-kits.md)
- [认证入门](/zh/authentication.md)
- [使用 Inertia.js 构建 SPA](/zh/blog/inertia-introduction.md)
