
# 身份认证

- [介绍](#introduction)
   - [入门套件](#starter-kits)
   - [数据库注意事项](#introduction-database-considerations)
   - [生态系统概览](#ecosystem-overview)
- [身份认证快速入门](#authentication-quickstart)
   - [安装入门套件](#install-a-starter-kit)
   - [获取已认证用户](#retrieving-the-authenticated-user)
   - [保护路由](#protecting-routes)
   - [登录限流](#login-throttling)
- [手动认证用户](#authenticating-users)
   - [记住用户](#remembering-users)
   - [其他认证方式](#other-authentication-methods)
- [HTTP 基本认证](#http-basic-authentication)
   - [无状态 HTTP 基本认证](#stateless-http-basic-authentication)
- [退出登录](#logging-out)
   - [使其他设备上的会话失效](#invalidating-sessions-on-other-devices)
- [密码确认](#password-confirmation)
   - [配置](#password-confirmation-configuration)
   - [路由](#password-confirmation-routing)
   - [保护路由](#password-confirmation-protecting-routes)
- [添加自定义 Guard](#adding-custom-guards)
   - [闭包请求 Guard](#closure-request-guards)
- [添加自定义用户 Provider](#adding-custom-user-providers)
   - [用户 Provider 契约](#the-user-provider-contract)
   - [Authenticatable 契约](#the-authenticatable-contract)
- [自动密码重新哈希](#automatic-password-rehashing)
- [社交认证](/docs/laravel/13.x/socialite)
- [事件](#events)

## 介绍

许多 Web 应用程序都提供了一种让用户对应用程序进行身份验证并“登录”的方式。在 Web 应用程序中实现此功能可能是一项复杂且具有潜在风险的工作。因此，Laravel 致力于为你提供快速、安全且简单实现身份认证所需的工具。

Laravel 的身份认证功能核心由 **“guards（守卫）”** 和 **“providers（用户提供器）”** 组成。

Guard 定义了每个请求中用户的认证方式。例如，Laravel 自带一个 `session` Guard，它通过会话存储和 Cookie 来维护用户状态。

Provider 定义了如何从持久化存储中获取用户。Laravel 支持通过 [Eloquent](/docs/laravel/13.x/eloquent) 和数据库查询构建器来获取用户。不过，你也可以根据应用程序的需求自由定义额外的 Provider。



你的应用程序身份认证配置文件位于 `config/auth.php`。该文件包含多个详细说明的配置选项，用于调整 Laravel 身份认证服务的行为。

> [!注意]
> Guard（守卫）和 Provider（用户提供器）不应与“角色（roles）”和“权限（permissions）”混淆。有关如何通过权限对用户操作进行授权的更多信息，请参阅 [授权](/docs/laravel/13.x/authorization) 文档。

### 入门套件

想快速开始吗？在一个全新的 Laravel 应用程序中安装一个 [Laravel 应用程序入门套件](/docs/laravel/13.x/starter-kits)。完成数据库迁移后，在浏览器中访问 `/register` 或应用程序中配置的其他任意 URL。入门套件会帮你搭建完整的身份认证系统！

**即使你最终的 Laravel 应用程序选择不使用入门套件，安装一个 [入门套件](/docs/laravel/13.x/starter-kits) 仍然是学习如何在实际 Laravel 项目中实现 Laravel 全部身份认证功能的绝佳机会。** 由于 Laravel 入门套件已经为你提供了身份认证控制器、路由和视图，你可以查看这些文件中的代码，从而了解 Laravel 的身份认证功能是如何实现的。

### 数据库注意事项

默认情况下，Laravel 会在你的 `app/Models` 目录中包含一个 `App\Models\User` [Eloquent 模型](/docs/laravel/13.x/eloquent)。该模型可以与默认的 Eloquent 身份认证驱动一起使用。

如果你的应用程序没有使用 Eloquent，则可以使用基于 Laravel 查询构建器的 `database` 身份认证 Provider。

如果你的应用程序使用 MongoDB，请查看 MongoDB 官方的 [Laravel 用户身份认证文档](https://www.mongodb.com/docs/drivers/php/laravel-mongodb/current/user-authentication/)。



在为 `App\Models\User` 模型构建数据库结构时，请确保密码字段（password column）的长度至少为 60 个字符。当然，新 Laravel 应用程序中自带的 `users` 表迁移文件已经创建了一个超过此长度限制的字段。

此外，你还应该确保 `users` 表（或等效的数据表）包含一个可为空（nullable）的字符串类型 `remember_token` 字段，并且长度为 100 个字符。该字段用于存储选择“记住我（remember me）”选项的用户登录令牌。

同样，新 Laravel 应用程序中默认包含的 `users` 表迁移文件已经包含了该字段。

### 生态系统概览

Laravel 提供了多个与身份认证相关的软件包。在继续之前，我们先了解 Laravel 身份认证生态系统的整体情况，并讨论每个软件包的设计用途。

首先，我们来了解身份认证的工作方式。

当使用 Web 浏览器时，用户会通过登录表单提交用户名和密码。如果这些凭证正确，应用程序会将已认证用户的信息存储到用户的 [会话（session）](/docs/laravel/13.x/session) 中。

发送给浏览器的 Cookie 中包含会话 ID，因此后续向应用程序发起的请求可以通过该 ID 将用户关联到正确的会话。

当应用程序接收到会话 Cookie 后，会根据会话 ID 获取会话数据，发现身份认证信息已经存储在会话中，然后将该用户视为“已认证”。

当远程服务需要进行身份认证以访问 API 时，通常不会使用 Cookie 进行认证，因为远程服务不存在 Web 浏览器环境。

相反，远程服务会在每次请求中向 API 发送一个 API Token。

应用程序可以将收到的 Token 与数据库中有效的 API Token 表进行验证，并将该请求“认证”为由与该 API Token 关联的用户所发起。



#### Laravel 内置的浏览器身份认证服务

Laravel 包含了内置的身份认证和会话服务，这些服务通常通过 `Auth` 和 `Session` 门面进行访问。

这些功能为从 Web 浏览器发起的请求提供基于 Cookie 的身份认证方式。它们提供了一些方法，可以让你验证用户凭据并对用户进行身份认证。

此外，这些服务还会自动在用户的会话中存储正确的身份认证数据，并为用户签发会话 Cookie。

本文档中将详细介绍如何使用这些服务。

**应用程序入门套件**

正如本文档中所讨论的，你可以手动与这些身份认证服务进行交互，以构建应用程序自己的身份认证层。

不过，为了帮助你更快速地开始开发，我们发布了[免费的入门套件](/docs/laravel/13.x/starter-kits)，它们提供了完整身份认证层的强大且现代化的脚手架代码。

#### Laravel 的 API 身份认证服务

Laravel 提供了两个可选的软件包，用于帮助你管理 API Token，并对使用 API Token 发起的请求进行身份认证：[Passport](/docs/laravel/13.x/passport) 和 [Sanctum](/docs/laravel/13.x/sanctum)。

请注意，这些库与 Laravel 内置的基于 Cookie 的身份认证库并不是互相排斥的。

这些库主要用于 API Token 身份认证，而 Laravel 内置的身份认证服务则主要用于基于 Cookie 的浏览器身份认证。

许多应用程序会同时使用 Laravel 内置的基于 Cookie 的身份认证服务，以及 Laravel 提供的其中一个 API 身份认证软件包。

**Passport**

Passport 是一个 OAuth2 身份认证提供器，它提供了多种 OAuth2 “授权类型（grant types）”，允许你签发各种类型的 Token。

总体来说，这是一个功能强大但较为复杂的 API 身份认证软件包。

不过，大多数应用程序并不需要 OAuth2 规范所提供的复杂功能，因为这些功能可能会让用户和开发者都感到困惑。

此外，开发者过去经常对如何使用 Passport 这类 OAuth2 身份认证提供器为 SPA（单页应用程序）或移动应用程序进行身份认证感到困惑。



**Sanctum**

针对 OAuth2 的复杂性以及开发者在使用过程中的困惑，我们着手构建一个更简单、更精简的身份认证软件包，它能够同时处理来自 Web 浏览器的第一方 Web 请求，以及通过 Token 进行的 API 请求。

这一目标通过发布 [Laravel Sanctum](/docs/laravel/13.x/sanctum) 得以实现。

对于以下类型的应用程序，Laravel Sanctum 应被视为首选并推荐使用的身份认证软件包：

- 除 API 外，还提供第一方 Web 用户界面的应用程序；
- 后端 Laravel 应用程序之外，独立存在的单页应用程序（SPA）驱动的应用程序；
- 提供移动客户端的应用程序。

Laravel Sanctum 是一个混合型 Web / API 身份认证软件包，可以管理应用程序完整的身份认证流程。

这是因为，当基于 Sanctum 的应用程序接收到请求时，Sanctum 首先会判断该请求是否包含一个引用已认证会话的会话 Cookie。

Sanctum 通过调用 Laravel 内置的身份认证服务来完成这一判断，我们之前已经介绍过这些服务。

如果请求不是通过会话 Cookie 进行身份认证的，Sanctum 会检查请求中是否包含 API Token。

如果存在 API Token，Sanctum 将使用该 Token 对请求进行身份认证。

要了解此过程的更多信息，请参考 Sanctum 的 ["工作原理"](/docs/laravel/13.x/sanctum#how-it-works) 文档。

#### 总结与技术栈选择

总结来说，如果你的应用程序将通过浏览器访问，并且你正在构建一个单体 Laravel 应用程序，那么你的应用程序将使用 Laravel 内置的身份认证服务。

其次，如果你的应用程序提供一个供第三方使用的 API，那么你需要在 [Passport](/docs/laravel/13.x/passport) 或 [Sanctum](/docs/laravel/13.x/sanctum) 之间进行选择，为你的应用程序提供 API Token 身份认证功能。

通常情况下，只要条件允许，应优先选择 Sanctum，因为它为 API 身份认证、SPA 身份认证以及移动端身份认证提供了一个简单而完整的解决方案，同时还支持“作用域（scopes）”或“能力（abilities）”功能。



如果你正在构建一个由 Laravel 后端驱动的单页应用程序（SPA），你应该使用 [Laravel Sanctum](/docs/laravel/13.x/sanctum)。

使用 Sanctum 时，你可以选择以下两种方式之一：

- [手动实现自己的后端身份认证路由](#authenticating-users)；
- 使用 [Laravel Fortify](/docs/laravel/13.x/fortify) 作为无头（Headless）身份认证后端服务，它可以为注册、密码重置、邮箱验证等功能提供路由和控制器。

当你的应用程序确实需要 OAuth2 规范提供的全部功能时，可以选择 Passport。

另外，如果你希望快速开始开发，我们很高兴向你推荐[我们的应用程序入门套件](/docs/laravel/13.x/starter-kits)，它可以帮助你快速创建一个新的 Laravel 应用程序，并且该应用程序已经使用了我们推荐的 Laravel 内置身份认证服务技术栈。

## 身份认证快速入门

> [!警告]
> 本部分文档介绍如何通过 [Laravel 应用程序入门套件](/docs/laravel/13.x/starter-kits) 对用户进行身份认证，其中包含帮助你快速开始开发的 UI 脚手架。
>
> 如果你希望直接集成 Laravel 的身份认证系统，请查看[手动认证用户](#authenticating-users)相关文档。

### 安装入门套件

首先，你应该[安装 Laravel 应用程序入门套件](/docs/laravel/13.x/starter-kits)。

我们的入门套件提供了设计精美的起点方案，可以帮助你在全新的 Laravel 应用程序中快速集成身份认证功能。

### 获取已认证用户

在通过入门套件创建应用程序，并允许用户注册以及完成身份认证后，你通常需要与当前已认证用户进行交互。

在处理传入请求时，你可以通过 `Auth` 门面的 `user` 方法访问已认证用户：

```php
use Illuminate\Support\Facades\Auth;

// 获取当前已认证用户...
$user = Auth::user();

// 获取当前已认证用户的 ID...
$id = Auth::id();
```


或者，当用户完成身份认证后，你也可以通过 `Illuminate\Http\Request` 实例访问已认证用户。
请记住，类型提示（type-hinted）的类会自动注入到你的控制器方法中。
通过对 `Illuminate\Http\Request` 对象进行类型提示，你可以在应用程序的任意控制器方法中，通过请求对象的 `user` 方法方便地访问已认证用户：

```php
<?php

namespace App\Http\Controllers;

use Illuminate\Http\RedirectResponse;
use Illuminate\Http\Request;

class FlightController extends Controller
{
    /**
     * 更新现有航班的信息。
     */
    public function update(Request $request): RedirectResponse
    {
        $user = $request->user();

        // ...

        return redirect('/flights');
    }
}
```

#### 判断当前用户是否已认证

要判断发起当前 HTTP 请求的用户是否已经完成身份认证，可以使用 `Auth` 门面的 `check` 方法。
如果用户已经通过身份认证，该方法将返回 `true`：

```php
use Illuminate\Support\Facades\Auth;

if (Auth::check()) {
    // 用户已登录...
}
```

> [!注意]
> 虽然可以通过 `check` 方法判断用户是否已认证，但通常情况下，你会使用中间件来验证用户身份，然后再允许用户访问特定的路由 / 控制器。有关更多信息，请查看[保护路由](/docs/laravel/13.x/authentication#protecting-routes)文档。

### 保护路由

[路由中间件](/docs/laravel/13.x/middleware)可用于限制只有已认证用户才能访问指定路由。

Laravel 自带一个 `auth` 中间件，它是 `Illuminate\Auth\Middleware\Authenticate` 类的一个[中间件别名](/docs/laravel/13.x/middleware#middleware-aliases)。

由于该中间件已经在 Laravel 内部完成别名注册，因此你只需要将该中间件附加到路由定义上即可：
```php
Route::get('/flights', function () {
    // 只有已认证用户才能访问该路由...
})->middleware('auth');
```



#### 重定向未认证用户

当 `auth` 中间件检测到未认证用户时，它会将用户重定向到 `login` [命名路由](/docs/laravel/13.x/routing#named-routes)。

你可以通过应用程序 `bootstrap/app.php` 文件中的 `redirectGuestsTo` 方法修改此行为：

```php
use Illuminate\Http\Request;

->withMiddleware(function (Middleware $middleware): void {
    $middleware->redirectGuestsTo('/login');

    // 使用闭包...
    $middleware->redirectGuestsTo(fn (Request $request) => route('login'));
})
```

#### 重定向已认证用户

当 `guest` 中间件检测到用户已经完成身份认证时，它会将用户重定向到 `dashboard` 或 `home` 命名路由。

你可以通过应用程序 `bootstrap/app.php` 文件中的 `redirectUsersTo` 方法修改此行为：

```php
use Illuminate\Http\Request;

->withMiddleware(function (Middleware $middleware): void {
    $middleware->redirectUsersTo('/panel');

    // 使用闭包...
    $middleware->redirectUsersTo(fn (Request $request) => route('panel'));
})
```

#### 指定 Guard

当你将 `auth` 中间件附加到路由时，也可以指定用于认证用户的“Guard（守卫）”。
指定的 Guard 应该对应 `auth.php` 配置文件中 `guards` 数组里的某个键：

```php
Route::get('/flights', function () {
    // 只有已认证用户才能访问该路由...
})->middleware('auth:admin');
```

### 登录限流

如果你正在使用我们的某个[应用程序入门套件](/docs/laravel/13.x/starter-kits)，登录尝试的频率限制会自动启用。默认情况下，如果用户多次尝试登录但未能提供正确的凭据，系统会禁止该用户在一分钟内再次登录。该限流机制会根据用户的用户名 / 邮箱地址以及 IP 地址进行唯一限制。

> [!注意]
> 如果你希望限制应用程序中其他路由的访问频率，请查看[限流文档](/docs/laravel/13.x/routing#rate-limiting)。



## 手动认证用户

你不需要必须使用 Laravel [应用程序入门套件](/docs/laravel/13.x/starter-kits) 中包含的身份认证脚手架。

如果你选择不使用这些脚手架，那么你需要直接使用 Laravel 的身份认证类来管理用户身份认证。

不用担心，这非常简单！

我们将通过 `Auth` [门面](/docs/laravel/13.x/facades)访问 Laravel 的身份认证服务，因此需要确保在类的顶部引入 `Auth` 门面。

接下来，让我们看看 `attempt` 方法。

`attempt` 方法通常用于处理应用程序“登录”表单提交的身份认证请求。

如果身份认证成功，你应该重新生成用户的 [会话（session）](/docs/laravel/13.x/session)，以防止[会话固定攻击（session fixation）](https://en.wikipedia.org/wiki/Session_fixation)：

```php
<?php

namespace App\Http\Controllers;

use Illuminate\Http\Request;
use Illuminate\Http\RedirectResponse;
use Illuminate\Support\Facades\Auth;

class LoginController extends Controller
{
    /**
     * 处理身份认证请求。
     */
    public function authenticate(Request $request): RedirectResponse
    {
        $credentials = $request->validate([
            'email' => ['required', 'email'],
            'password' => ['required'],
        ]);

        if (Auth::attempt($credentials)) {
            $request->session()->regenerate();

            return redirect()->intended('dashboard');
        }

        return back()->withErrors([
            'email' => '提供的凭据与我们的记录不匹配。',
        ])->onlyInput('email');
    }
}
```

`attempt` 方法的第一个参数接收一个键 / 值对数组。数组中的值将用于从你的数据库表中查找用户。
因此，在上面的示例中，系统会通过 `email` 字段的值获取用户。如果找到用户，数据库中存储的哈希密码将会与通过数组传递给该方法的 `password` 值进行比较。你不应该对传入请求中的 `password` 值进行哈希处理，因为框架会在将该值与数据库中的哈希密码进行比较之前自动完成哈希处理。如果两个哈希密码匹配，则会为该用户启动一个已认证会话。



请记住，Laravel 的身份认证服务会根据你的身份认证 Guard 中的 **Provider（用户提供器）** 配置，从数据库中获取用户。在默认的 `config/auth.php` 配置文件中，指定使用 Eloquent 用户 Provider，并且配置其在获取用户时使用 `App\Models\User` 模型。你可以根据应用程序的需求，在配置文件中修改这些配置项。如果身份认证成功，`attempt` 方法将返回 `true`；否则将返回 `false`。Laravel 重定向器提供的 `intended` 方法会将用户重定向到他们在被身份认证中间件拦截之前尝试访问的 URL。如果目标地址不可用，可以向该方法提供一个备用 URI。

#### 指定额外条件

如果需要，你还可以在身份认证查询中添加额外的查询条件，除了用户的邮箱和密码之外。实现这一功能非常简单，只需要将额外的查询条件添加到传递给 `attempt` 方法的数组中即可。例如，我们可以验证用户是否被标记为“已启用（active）”：

```php
if (Auth::attempt(['email' => $email, 'password' => $password, 'active' => 1])) {
    // 身份认证成功...
}
```

对于复杂的查询条件，你可以在凭据数组中提供一个闭包（Closure）。该闭包会接收查询实例作为参数，从而允许你根据应用程序的需求自定义查询：


```php
use Illuminate\Database\Eloquent\Builder;

if (Auth::attempt([
    'email' => $email,
    'password' => $password,
    fn (Builder $query) => $query->has('activeSubscription'),
])) {
    // 身份认证成功...
}
```

> [!警告]
> 在这些示例中，`email` 并不是必需的选项，它只是作为示例使用。你应该使用数据库表中对应“用户名（username）”的字段名称。



`attemptWhen` 方法接收一个闭包（Closure）作为第二个参数，它可以用于在真正对用户进行身份认证之前，对潜在用户进行更深入的检查。该闭包会接收潜在用户对象，并且应该返回 `true` 或 `false`，用于指示该用户是否可以通过身份认证：

```php
if (Auth::attemptWhen([
    'email' => $email,
    'password' => $password,
], function (User $user) {
    return $user->isNotBanned();
})) {
    // 身份认证成功...
}
```

#### 访问指定的 Guard 实例

通过 `Auth` 门面的 `guard` 方法，你可以指定在用户身份认证时需要使用的 Guard 实例。
这允许你在应用程序的不同部分中，使用完全独立的可认证模型（Authenticatable Model）或用户数据表来管理身份认证。传递给 `guard` 方法的 Guard 名称应该对应 `auth.php` 配置文件中配置的某个 Guard：

```php
if (Auth::guard('admin')->attempt($credentials)) {
    // ...
}
```

### 记住用户

许多 Web 应用程序都会在登录表单中提供一个“记住我（remember me）”复选框。如果你希望在应用程序中提供“记住我”功能，可以将一个布尔值作为 `attempt` 方法的第二个参数传递。当该值为 `true` 时，Laravel 会保持用户持续登录状态，直到用户手动退出登录。你的 `users` 表必须包含一个字符串类型的 `remember_token` 字段，该字段用于存储“记住我”Token。新 Laravel 应用程序中包含的 `users` 表迁移文件已经默认包含该字段：


```php
use Illuminate\Support\Facades\Auth;

if (Auth::attempt(['email' => $email, 'password' => $password], $remember)) {
    // 用户正在被记住...
}
```



如果你的应用程序提供“记住我（remember me）”功能，你可以使用 `viaRemember` 方法来判断当前已认证用户是否是通过“记住我” Cookie 完成身份认证的：

```php
use Illuminate\Support\Facades\Auth;

if (Auth::viaRemember()) {
    // ...
}
```

### 其他身份认证方式

#### 认证一个用户实例

如果你需要将一个已有的用户实例设置为当前已认证用户，可以将该用户实例传递给 `Auth` 门面的 `login` 方法。传入的用户实例必须实现 `Illuminate\Contracts\Auth\Authenticatable`  [契约](/docs/laravel/13.x/contracts)。Laravel 自带的 `App\Models\User` 模型已经实现了该接口。这种身份认证方式适用于你已经拥有一个有效用户实例的情况，例如用户刚刚完成注册之后：

```php
use Illuminate\Support\Facades\Auth;

Auth::login($user);
```

你可以将一个布尔值作为 `login` 方法的第二个参数传递。该值用于表示当前已认证会话是否需要启用“记住我”功能。请记住，这意味着该会话将会一直保持认证状态，直到用户手动退出应用程序：

```php
Auth::login($user, $remember = true);
```

如果需要，你可以在调用 `login` 方法之前指定身份认证 Guard：

```php
Auth::guard('admin')->login($user);
```

#### 根据用户 ID 认证用户

如果你希望通过用户数据库记录的主键对用户进行身份认证，可以使用 `loginUsingId` 方法。该方法接收你希望进行身份认证的用户主键：

```php
Auth::loginUsingId(1);
```



你可以向 `loginUsingId` 方法的 `remember` 参数传递一个布尔值。该值用于表示当前已认证会话是否需要启用“记住我”功能。请记住，这意味着该会话将会一直保持认证状态，直到用户手动退出应用程序：

```php
Auth::loginUsingId(1, remember: true);
```

#### 仅认证用户一次

你可以使用 `once` 方法让用户在单个请求中完成应用程序身份认证。调用该方法时，不会使用任何会话或 Cookie，并且不会触发 `Login` 事件：

```php
if (Auth::once($credentials)) {
    // ...
}
```

## HTTP 基本认证

[HTTP 基本认证](https://en.wikipedia.org/wiki/Basic_access_authentication) 提供了一种快速认证应用程序用户的方式，无需创建专门的“登录”页面。开始使用时，只需要将 `auth.basic`  [中间件](/docs/laravel/13.x/middleware)附加到路由即可。`auth.basic` 中间件已经包含在 Laravel 框架中，因此你无需自行定义：


```php
Route::get('/profile', function () {
    // 只有已认证用户才能访问该路由...
})->middleware('auth.basic');
```

将中间件附加到路由后，当你通过浏览器访问该路由时，系统会自动提示你输入身份认证凭据。默认情况下，`auth.basic` 中间件会假设你的 `users` 数据表中的 `email` 字段就是用户的“用户名”。

#### 关于 FastCGI 的说明

如果你使用 [PHP FastCGI](https://www.php.net/manual/en/install.fpm.php) 和 Apache 来运行 Laravel 应用程序，HTTP 基本认证可能无法正常工作。

要解决这些问题，可以将以下代码添加到应用程序的 `.htaccess` 文件中：

```apache
RewriteCond %{HTTP:Authorization} ^(.+)$
RewriteRule .* - [E=HTTP_AUTHORIZATION:%{HTTP:Authorization}]
```



### 无状态 HTTP 基本认证

你也可以使用 HTTP 基本认证，而无需在会话中设置用户标识 Cookie。这种方式主要适用于你选择使用 HTTP 认证来验证访问应用程序 API 的请求。要实现这一点，可以[定义一个中间件](/docs/laravel/13.x/middleware)，并在其中调用 `onceBasic` 方法。如果 `onceBasic` 方法没有返回响应，则可以将请求继续传递到应用程序：

```php
<?php

namespace App\Http\Middleware;

use Closure;
use Illuminate\Http\Request;
use Illuminate\Support\Facades\Auth;
use Symfony\Component\HttpFoundation\Response;

class AuthenticateOnceWithBasicAuth
{
    /**
     * 处理传入请求。
     *
     * @param  \Closure(\Illuminate\Http\Request): (\Symfony\Component\HttpFoundation\Response)  $next
     */
    public function handle(Request $request, Closure $next): Response
    {
        return Auth::onceBasic() ?: $next($request);
    }

}
```

接下来，将该中间件附加到路由：

```php
Route::get('/api/user', function () {
    // 只有已认证用户才能访问该路由...
})->middleware(AuthenticateOnceWithBasicAuth::class);
```

## 退出登录

要手动让用户退出应用程序，可以使用 `Auth` 门面提供的 `logout` 方法。该方法会从用户的会话中移除身份认证信息，因此后续请求将不再被视为已认证请求。除了调用 `logout` 方法之外，还建议使用户的会话失效，并重新生成其 [CSRF Token](/docs/laravel/13.x/csrf)。用户退出登录后，通常会将用户重定向到应用程序的根路径：

```php
use Illuminate\Http\Request;
use Illuminate\Http\RedirectResponse;
use Illuminate\Support\Facades\Auth;

/**
 * 用户退出应用程序。
 */
public function logout(Request $request): RedirectResponse
{
    Auth::logout();

    $request->session()->invalidate();

    $request->session()->regenerateToken();

    return redirect('/');
}
```



### 使其他设备上的会话失效

Laravel 还提供了一种机制，可以让用户在其他设备上处于活动状态的会话失效并“退出登录”，同时不会使当前设备上的会话失效。该功能通常用于用户修改或更新密码时，你希望让其他设备上的会话失效，但保持当前设备仍处于已认证状态。在开始之前，你应该确保 `Illuminate\Session\Middleware\AuthenticateSession` 中间件已经包含在需要接收会话身份认证的路由中。通常情况下，你应该将此中间件放置在路由组定义中，以便将其应用到应用程序的大部分路由。默认情况下，可以通过 `auth.session` [中间件别名](/docs/laravel/13.x/middleware#middleware-aliases) 将 `AuthenticateSession` 中间件附加到路由：

```php
Route::middleware(['auth', 'auth.session'])->group(function () {
    Route::get('/', function () {
        // ...
    });
});
```

然后，你可以使用 `Auth` 门面提供的 `logoutOtherDevices` 方法。该方法要求用户确认当前密码，你的应用程序应该通过输入表单接收该密码：

```php
use Illuminate\Support\Facades\Auth;

Auth::logoutOtherDevices($currentPassword);
```

调用 `logoutOtherDevices` 方法后，用户在其他设备上的会话将会被完全失效。这意味着用户将从之前通过所有 Guard 完成身份认证的会话中全部“退出登录”。

## 密码确认

在构建应用程序时，你可能偶尔会遇到一些操作，需要用户在执行操作之前确认密码，或者在用户被重定向到应用程序的敏感区域之前确认密码。Laravel 提供了内置中间件，使这一过程变得非常简单。
实现此功能需要定义两个路由：

-   一个路由用于显示要求用户确认密码的页面；
-   另一个路由用于确认密码是否有效，并将用户重定向到其目标页面。

> [!注意]
> 以下文档介绍如何直接集成 Laravel 的密码确认功能。但是，如果你希望更快速地开始使用，[Laravel 应用程序入门套件](/docs/laravel/13.x/starter-kits)已经包含了对该功能的支持！



### 配置

用户确认密码后，接下来的三小时内，系统不会再次要求用户确认密码。不过，你可以通过修改应用程序 `config/auth.php` 配置文件中的 `password_timeout` 配置项，来调整再次要求用户输入密码之前的等待时间。

### 路由

#### 密码确认表单

首先，我们定义一个路由，用于显示要求用户确认密码的视图：

```php
Route::get('/confirm-password', function () {
    return view('auth.confirm-password');
})->middleware('auth')->name('password.confirm');
```

正如你所预期的那样，该路由返回的视图应包含一个带有 `password` 字段的表单。此外，你还可以在视图中添加说明文字，告知用户当前正在访问应用程序的受保护区域，因此必须确认密码。

#### 确认密码

接下来，我们定义一个路由，用于处理“确认密码”视图提交的表单请求。该路由负责验证密码，并将用户重定向到其原本想要访问的页面：

```php
use Illuminate\Http\Request;
use Illuminate\Support\Facades\Hash;

Route::post('/confirm-password', function (Request $request) {
    if (! Hash::check($request->password, $request->user()->password)) {
        return back()->withErrors([
            'password' => ['The provided password does not match our records.']
        ]);
    }

    $request->session()->passwordConfirmed();

    return redirect()->intended();
})->middleware(['auth', 'throttle:6,1']);
```

在继续之前，我们来详细了解一下这个路由。首先，系统会检查请求中的 `password` 字段是否与当前已认证用户的密码匹配。

如果密码有效，我们需要告知 Laravel 的 Session，该用户已经确认了密码。`passwordConfirmed` 方法会在用户的 Session 中设置一个时间戳，Laravel 可以通过该时间戳判断用户上一次确认密码的时间。

最后，我们可以将用户重定向到其原本想要访问的页面。

### 保护路由

你应确保所有执行需要近期密码确认的操作的路由，都配置了 `password.confirm` 中间件。该中间件已包含在 Laravel 的默认安装中，它会自动将用户原本想要访问的目标地址存储在 Session 中，以便用户确认密码后能够重定向到该地址。

将用户的目标地址存储在 Session 中后，中间件会将用户重定向到 `password.confirm` [命名路由](/docs/laravel/13.x/routing#named-routes)：

```php
Route::get('/settings', function () {
    // ...
})->middleware(['password.confirm']);

Route::post('/settings', function () {
    // ...
})->middleware(['password.confirm']);
```

## 添加自定义 Guard

你可以使用 `Auth` Facade 上的 `extend` 方法定义自己的身份验证 Guard。你应该在 [服务提供者](/docs/laravel/13.x/providers) 中调用 `extend` 方法。由于 Laravel 已经自带了 `AppServiceProvider`，因此我们可以将代码放在该服务提供者中：

```php
<?php

namespace App\Providers;

use App\Services\Auth\JwtGuard;
use Illuminate\Contracts\Foundation\Application;
use Illuminate\Support\Facades\Auth;
use Illuminate\Support\ServiceProvider;

class AppServiceProvider extends ServiceProvider
{
    // ...

    /**
     * 初始化所有应用程序服务。
     */
    public function boot(): void
    {
        Auth::extend('jwt', function (Application $app, string $name, array $config) {
            // 返回一个 Illuminate\Contracts\Auth\Guard 实例……

            return new JwtGuard(Auth::createUserProvider($config['provider']));
        });
    }
}
```

如上面的示例所示，传递给 `extend` 方法的回调函数应返回一个 `Illuminate\Contracts\Auth\Guard` 接口的实现。该接口包含一些你需要实现的方法，以便定义自定义 Guard。

定义好自定义 Guard 后，你就可以在 `auth.php` 配置文件的 `guards` 配置项中引用它：

```php
'guards' => [
    'api' => [
        'driver' => 'jwt',
        'provider' => 'users',
    ],
],
```

### 闭包请求 Guard

实现自定义的、基于 HTTP 请求的身份验证系统，最简单的方法是使用 `Auth::viaRequest` 方法。该方法允许你通过一个闭包快速定义身份验证流程。

首先，在应用程序 `AppServiceProvider` 的 `boot` 方法中调用 `Auth::viaRequest` 方法。`viaRequest` 方法的第一个参数是身份验证驱动的名称。该名称可以是任何能够描述自定义 Guard 的字符串。传递给该方法的第二个参数应为一个闭包，该闭包接收传入的 HTTP 请求，并返回一个用户实例；如果身份验证失败，则返回 `null`：

```php
use App\Models\User;
use Illuminate\Http\Request;
use Illuminate\Support\Facades\Auth;

/**
 * 初始化所有应用程序服务。
 */
public function boot(): void
{
    Auth::viaRequest('custom-token', function (Request $request) {
        return User::where('token', (string) $request->token)->first();
    });
}
```

定义好自定义身份验证驱动后，你可以在 `auth.php` 配置文件的 `guards` 配置项中将其配置为一个驱动：

```php
'guards' => [
    'api' => [
        'driver' => 'custom-token',
    ],
],
```

最后，你可以在为路由分配身份验证中间件时引用该 Guard：

```php
Route::middleware('auth:api')->group(function () {
    // ...
});
```

## 添加自定义用户提供者

如果你没有使用传统的关系型数据库存储用户，就需要通过自定义身份验证用户提供者来扩展 Laravel。我们将使用 `Auth` Facade 上的 `provider` 方法来定义自定义用户提供者。用户提供者解析器应返回一个 `Illuminate\Contracts\Auth\UserProvider` 接口的实现：

```php
<?php

namespace App\Providers;

use App\Extensions\MongoUserProvider;
use Illuminate\Contracts\Foundation\Application;
use Illuminate\Support\Facades\Auth;
use Illuminate\Support\ServiceProvider;

class AppServiceProvider extends ServiceProvider
{
    // ...

    /**
     * 初始化所有应用程序服务。
     */
    public function boot(): void
    {
        Auth::provider('mongo', function (Application $app, array $config) {
            // 返回一个 Illuminate\Contracts\Auth\UserProvider 实例……

            return new MongoUserProvider($app->make('mongo.connection'));
        });
    }
}
```

使用 `provider` 方法注册提供者后，你可以在 `auth.php` 配置文件中切换到新的用户提供者。首先，定义一个使用新驱动的 `provider`：

```php
'providers' => [
    'users' => [
        'driver' => 'mongo',
    ],
],
```

最后，你可以在 `guards` 配置项中引用该提供者：

```php
'guards' => [
    'web' => [
        'driver' => 'session',
        'provider' => 'users',
    ],
],
```

### 用户提供者契约

`Illuminate\Contracts\Auth\UserProvider` 的实现负责从持久化存储系统（例如 MySQL、MongoDB 等）中获取一个 `Illuminate\Contracts\Auth\Authenticatable` 接口的实现。这两个接口使 Laravel 的身份验证机制能够继续正常运行，而不受用户数据存储方式或用于表示已认证用户的类类型的影响。

让我们来看看 `Illuminate\Contracts\Auth\UserProvider` 契约：

```php
<?php

namespace Illuminate\Contracts\Auth;

interface UserProvider
{
    public function retrieveById($identifier);
    public function retrieveByToken($identifier, $token);
    public function updateRememberToken(Authenticatable $user, $token);
    public function retrieveByCredentials(array $credentials);
    public function validateCredentials(Authenticatable $user, array $credentials);
    public function rehashPasswordIfRequired(Authenticatable $user, array $credentials, bool $force = false);
}
```

`retrieveById` 函数通常接收一个用于标识用户的键，例如 MySQL 数据库中的自增 ID。该方法应检索并返回与该 ID 匹配的 `Authenticatable` 实现。

`retrieveByToken` 函数根据用户唯一的 `$identifier` 和“记住我”功能使用的 `$token` 检索用户。这些信息通常存储在数据库的 `remember_token` 等字段中。与前一个方法一样，该方法应返回令牌值匹配的 `Authenticatable` 实现。


`updateRememberToken` 方法会使用新的 `$token` 更新 `$user` 实例的 `remember_token`。当用户成功通过“记住我”功能进行身份验证，或者用户退出登录时，系统会为用户分配一个新的令牌。

`retrieveByCredentials` 方法接收在尝试通过应用程序进行身份验证时传递给 `Auth::attempt` 方法的凭据数组。然后，该方法应该在底层持久化存储中“查询”与这些凭据匹配的用户。通常，此方法会执行一个带有 "where" 条件的查询，查找“username”与 `$credentials['username']` 的值匹配的用户记录。该方法应该返回一个 `Authenticatable` 接口的实现。**此方法不应该尝试执行任何密码验证或身份验证。**

`validateCredentials` 方法应该将给定的 `$user` 与 `$credentials` 进行比较，以验证用户身份。例如，此方法通常会使用 `Hash::check` 方法，将 `$user->getAuthPassword()` 的值与 `$credentials['password']` 的值进行比较。该方法应该返回 `true` 或 `false`，以指示密码是否有效。

`rehashPasswordIfRequired` 方法应该在需要且支持的情况下，对给定 `$user` 的密码重新进行哈希处理。例如，此方法通常会使用 `Hash::needsRehash` 方法来判断 `$credentials['password']` 的值是否需要重新进行哈希处理。如果密码需要重新进行哈希处理，该方法应该使用 `Hash::make` 方法重新对密码进行哈希处理，并更新底层持久化存储中的用户记录。

### Authenticatable 契约

现在我们已经了解了 `UserProvider` 中的每个方法，接下来让我们看看 `Authenticatable` 契约。请记住，用户提供者应该从 `retrieveById`、`retrieveByToken` 和 `retrieveByCredentials` 方法中返回此接口的实现：

```php
<?php

namespace Illuminate\Contracts\Auth;

interface Authenticatable
{
    public function getAuthIdentifierName();
    public function getAuthIdentifier();
    public function getAuthPasswordName();
    public function getAuthPassword();
    public function getRememberToken();
    public function setRememberToken($value);
    public function getRememberTokenName();
}
```



这个接口很简单。`getAuthIdentifierName` 方法应该返回用户的“主键”列的名称，而 `getAuthIdentifier` 方法应该返回用户的“主键”。如果使用 MySQL 后端，这通常是分配给用户记录的自增主键。`getAuthPasswordName` 方法应该返回用户密码列的名称。`getAuthPassword` 方法应该返回用户经过哈希处理的密码。

此接口允许身份验证系统与任何“用户”类协同工作，无论你使用的是哪种 ORM 或存储抽象层。默认情况下，Laravel 在 `app/Models` 目录中包含一个实现了此接口的 `App\Models\User` 类。

## 自动重新哈希密码

Laravel 默认的密码哈希算法是 bcrypt。可以通过应用程序的 `config/hashing.php` 配置文件或 `BCRYPT_ROUNDS` 环境变量来调整 bcrypt 哈希的“工作因子”。

通常，随着 CPU / GPU 处理能力的提高，bcrypt 的工作因子也应该随时间逐渐增加。如果你提高了应用程序的 bcrypt 工作因子，当用户通过 Laravel 的入门套件进行身份验证，或者通过 `attempt` 方法[手动验证用户身份](#authenticating-users)时，Laravel 会平滑且自动地重新对用户密码进行哈希处理。

通常，自动重新哈希密码不应该影响应用程序的正常运行；但是，你可以通过发布 `hashing` 配置文件来禁用此行为：

```shell
php artisan config:publish hashing
```

配置文件发布后，你可以将 `rehash_on_login` 配置值设置为 `false`：

```php
'rehash_on_login' => false,
```

## 事件

Laravel 会在身份验证过程中分发各种[事件](/docs/laravel/13.x/events)。你可以为以下任何事件[定义监听器](/docs/laravel/13.x/events)：

<div class="overflow-auto">

| 事件名称 |
| ---------------------------------------------- |
| `Illuminate\Auth\Events\Registered` |
| `Illuminate\Auth\Events\Attempting` |
| `Illuminate\Auth\Events\Authenticated` |
| `Illuminate\Auth\Events\Login` |
| `Illuminate\Auth\Events\Failed` |
| `Illuminate\Auth\Events\Validated` |
| `Illuminate\Auth\Events\Verified` |
| `Illuminate\Auth\Events\Logout` |
| `Illuminate\Auth\Events\CurrentDeviceLogout` |
| `Illuminate\Auth\Events\OtherDeviceLogout` |
| `Illuminate\Auth\Events\Lockout` |
| `Illuminate\Auth\Events\PasswordReset` |
| `Illuminate\Auth\Events\PasswordResetLinkSent` |

</div>

