laravel
6. 安全
认证

简介

许多 Web 应用程序为其用户提供了一种与应用程序进行认证并“登录”的方式。在 Web 应用程序中实现此功能可能是一项复杂且具有潜在风险的工作。因此,Laravel 努力为您提供快速、安全、轻松地实现认证所需的工具。

从本质上讲,Laravel 的认证设施由“守卫”和“提供器”组成。守卫定义了如何为每个请求对用户进行认证。例如,Laravel 附带了一个session守卫,它使用会话存储和 Cookie 来维护状态。

提供器定义了如何从您的持久存储中检索用户。Laravel 支持使用 Eloquent 和数据库查询构建器来检索用户。但是,您可以根据应用程序的需要自由定义其他提供器。

您的应用程序的认证配置文件位于 config/auth.php。此文件包含几个有详细文档说明的选项,用于调整 Laravel 认证服务的行为。

[!注意]
不要将守卫和提供器与“角色”和“权限”混淆。要了解更多关于通过权限授权用户操作的信息,请参考 授权 文档。

入门套件

想要快速开始吗?在新的 Laravel 应用程序中安装 Laravel 应用程序入门套件。在迁移数据库后,在浏览器中导航到 /register 或分配给您的应用程序的任何其他 URL。入门套件将负责搭建您的整个认证系统!

即使您选择在最终的 Laravel 应用程序中不使用入门套件,安装 Laravel Breeze 入门套件也是一个很好的学习机会,可以了解如何在实际的 Laravel 项目中实现所有 Laravel 的认证功能。 由于 Laravel Breeze 为您创建了认证控制器、路由和视图,您可以检查这些文件中的代码,以了解如何实现 Laravel 的认证功能。

数据库考虑因素

默认情况下,Laravel 在您的 app/Models 目录中包含一个 App\Models\User Eloquent 模型。此模型可与默认的 Eloquent 认证驱动程序一起使用。如果您的应用程序未使用 Eloquent,则可以使用使用 Laravel 查询构建器的 database 认证提供器。

在为 App\Models\User 模型构建数据库模式时,请确保密码列的长度至少为 60 个字符。当然,新的 Laravel 应用程序中包含的 users 表迁移已经创建了一个超过此长度的列。

此外,您应该验证您的 users(或等效)表包含一个可为空的、字符串类型的 remember_token 列,长度为 100 个字符。当用户在登录您的应用程序时选择“记住我”选项时,此列将用于存储用户的令牌。同样,新的 Laravel 应用程序中包含的默认 users 表迁移已经包含此列。

生态系统概述

Laravel 提供了几个与认证相关的包。在继续之前,我们将回顾 Laravel 中的一般认证生态系统,并讨论每个包的预期用途。

首先,考虑认证是如何工作的。当使用 Web 浏览器时,用户将通过登录表单提供其用户名和密码。如果这些凭据正确,应用程序将在用户的 会话 中存储有关已认证用户的信息。发送到浏览器的 Cookie 包含会话 ID,以便后续对应用程序的请求可以将用户与会话正确关联。在收到会话 Cookie 后,应用程序将根据会话 ID 检索会话数据,注意到认证信息已存储在会话中,并将用户视为“已认证”。

当远程服务需要认证以访问 API 时,通常不会使用 Cookie 进行认证,因为没有 Web 浏览器。相反,远程服务在每个请求中向 API 发送一个 API 令牌。应用程序可以根据有效 API 令牌表验证传入的令牌,并将该请求“认证”为与该 API 令牌相关联的用户执行的请求。

Laravel 的内置浏览器认证服务

Laravel 包括内置的认证和会话服务,通常通过 AuthSession 外观来访问。这些功能为从 Web 浏览器发起的请求提供基于 Cookie 的认证。它们提供了允许您验证用户凭据并认证用户的方法。此外,这些服务将自动在用户的会话中存储正确的认证数据并为用户颁发会话 Cookie。关于如何使用这些服务的讨论包含在本文档中。

应用程序入门套件

如本文档所述,您可以手动与这些认证服务进行交互,以构建您的应用程序自己的认证层。但是,为了帮助您更快地开始,我们发布了 免费包,为整个认证层提供了强大、现代的搭建。这些包是 Laravel BreezeLaravel JetstreamLaravel Fortify

Laravel Breeze 是 Laravel 所有认证功能的简单、最小化实现,包括登录、注册、密码重置、电子邮件验证和密码确认。Laravel Breeze 的视图层由使用 Tailwind CSS (opens in a new tab) 样式的简单 Blade 模板 组成。要开始使用,请查看关于 Laravel 的 应用程序入门套件 的文档。

Laravel Fortify 是 Laravel 的无头认证后端,实现了本文档中发现的许多功能,包括基于 Cookie 的认证以及其他功能,如双因素认证和电子邮件验证。Fortify 为 Laravel Jetstream 提供认证后端,或者可以与 Laravel Sanctum 独立结合使用,为需要与 Laravel 进行认证的 SPA 提供认证。

Laravel Jetstream (opens in a new tab) 是一个强大的应用程序入门套件,使用 Tailwind CSS (opens in a new tab)Livewire (opens in a new tab) 和 / 或 Inertia (opens in a new tab) 提供的美丽、现代的 UI 来使用和展示 Laravel Fortify 的认证服务。Laravel Jetstream 包括对双因素认证、团队支持、浏览器会话管理、配置文件管理的可选支持,以及与 Laravel Sanctum 的内置集成,以提供 API 令牌认证。下面将讨论 Laravel 的 API 认证产品。

Laravel 的 API 认证服务

Laravel 提供了两个可选的包来帮助您管理 API 令牌并认证使用 API 令牌进行的请求:PassportSanctum。请注意,这些库和 Laravel 的内置基于 Cookie 的认证库不是相互排斥的。这些库主要关注 API 令牌认证,而内置的认证服务则关注基于 Cookie 的浏览器认证。许多应用程序将同时使用 Laravel 的内置基于 Cookie 的认证服务和 Laravel 的一个 API 认证包。

Passport

Passport 是一个 OAuth2 认证提供器,提供了各种 OAuth2“授权类型”,允许您发布各种类型的令牌。总的来说,这是一个用于 API 认证的强大而复杂的包。然而,大多数应用程序不需要 OAuth2 规范提供的复杂功能,这对用户和开发人员来说都可能会造成困惑。此外,开发人员在历史上一直对如何使用像 Passport 这样的 OAuth2 认证提供器来认证 SPA 应用程序或移动应用程序感到困惑。

Sanctum

为了应对 OAuth2 的复杂性和开发人员的困惑,我们着手构建一个更简单、更精简的认证包,能够处理来自 Web 浏览器的第一方 Web 请求和通过令牌的 API 请求。随着 Laravel Sanctum 的发布,这个目标得以实现,对于除了提供 API 之外还将提供第一方 Web UI 的应用程序,或者由与后端 Laravel 应用程序分开存在的单页应用程序(SPA)驱动的应用程序,或者提供移动客户端的应用程序,Sanctum 应该被视为首选和推荐的认证包。

Laravel Sanctum 是一个混合的 Web / API 认证包,可以管理您的应用程序的整个认证过程。这是可能的,因为当基于 Sanctum 的应用程序收到请求时,Sanctum 将首先确定请求是否包含引用已认证会话的会话 Cookie。Sanctum 通过调用我们之前讨论过的 Laravel 的内置认证服务来实现这一点。如果请求不是通过会话 Cookie 进行认证的,Sanctum 将检查请求中的 API 令牌。如果存在 API 令牌,Sanctum 将使用该令牌对请求进行认证。要了解更多关于此过程的信息,请查阅 Sanctum 的 "它是如何工作的" 文档。

Laravel Sanctum 是我们选择包含在 Laravel Jetstream (opens in a new tab) 应用程序入门套件中的 API 包,因为我们相信它最适合大多数 Web 应用程序的认证需求。

总结和选择您的栈

总之,如果您的应用程序将使用浏览器访问,并且您正在构建一个单片 Laravel 应用程序,您的应用程序将使用 Laravel 的内置认证服务。

接下来,如果您的应用程序提供将被第三方使用的 API,您将在 PassportSanctum 之间进行选择,为您的应用程序提供 API 令牌认证。一般来说,在可能的情况下,Sanctum 应该是首选,因为它是一个简单、完整的 API 认证、SPA 认证和移动认证的解决方案,包括对“范围”或“能力”的支持。

如果您正在构建一个将由 Laravel 后端驱动的单页应用程序(SPA),您应该使用 Laravel Sanctum。当使用 Sanctum 时,您将需要 手动实现自己的后端认证路由 或使用 Laravel Fortify 作为无头认证后端服务,为注册、密码重置、电子邮件验证等功能提供路由和控制器。

当您的应用程序绝对需要 OAuth2 规范提供的所有功能时,可以选择 Passport。

并且,如果您想快速开始,我们很高兴推荐 Laravel Breeze 作为一种快速启动新的 Laravel 应用程序的方法,该应用程序已经使用了我们首选的认证栈,即 Laravel 的内置认证服务和 Laravel Sanctum。

认证快速入门

[!警告]
本部分文档讨论了通过 Laravel 应用程序入门套件 对用户进行认证,其中包括 UI 搭建,以帮助您快速开始。如果您想直接与 Laravel 的认证系统集成,请查看关于 手动认证用户 的文档。

安装入门套件

首先,您应该 安装 Laravel 应用程序入门套件。我们目前的入门套件,Laravel Breeze 和 Laravel Jetstream,为将认证功能集成到您的新 Laravel 应用程序中提供了设计精美的起点。

Laravel Breeze 是 Laravel 所有认证功能的最小化、简单实现,包括登录、注册、密码重置、电子邮件验证和密码确认。Laravel Breeze 的视图层由使用 Tailwind CSS (opens in a new tab) 样式的简单 Blade 模板 组成。此外,Breeze 提供了基于 Livewire (opens in a new tab)Inertia (opens in a new tab) 的搭建选项,对于基于 Inertia 的搭建,可以选择使用 Vue 或 React。

Laravel Jetstream (opens in a new tab) 是一个更强大的应用程序入门套件,包括支持使用 Livewire (opens in a new tab)Inertia 和 Vue (opens in a new tab) 来搭建您的应用程序。此外,Jetstream 具有对双因素认证、团队、配置文件管理、浏览器会话管理、通过 Laravel Sanctum 的 API 支持、账户删除等的可选支持。

检索已认证用户

在安装认证入门套件并允许用户在您的应用程序中注册和认证后,您经常需要与当前已认证的用户进行交互。在处理传入请求时,您可以通过 Auth 外观的 user 方法访问已认证的用户:

use Illuminate\Support\Facades\Auth;

// 检索当前已认证的用户...
$user = Auth::user();

// 检索当前已认证的用户的 ID...
$id = Auth::id();

或者,一旦用户通过认证,您可以通过 Illuminate\Http\Request 实例访问已认证的用户。请记住,类型提示的类将自动注入到您的控制器方法中。通过对 Illuminate\Http\Request 对象进行类型提示,您可以从应用程序的任何控制器方法中通过请求的 user 方法方便地访问已认证的用户:

<?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

use Illuminate\Support\Facades\Auth;

if (Auth::check()) {
    // 用户已登录...
}

[!NOTE]
尽管可以使用 check 方法确定用户是否已认证,但通常您会使用中间件在允许用户访问某些路由/控制器之前验证用户是否已认证。要了解更多信息,请查看关于保护路由的文档。

保护路由

路由中间件可用于仅允许已认证用户访问给定路由。Laravel 附带一个 auth 中间件,它是 Illuminate\Auth\Middleware\Authenticate 类的中间件别名。由于此中间件已在 Laravel 内部进行了别名设置,您只需要将中间件附加到路由定义中:

Route::get('/flights', function () {
    // 只有已认证用户可以访问此路由...
})->middleware('auth');

重定向未认证用户

auth 中间件检测到未认证用户时,它会将用户重定向到 login 命名路由。您可以使用应用程序的 bootstrap/app.php 文件中的 redirectGuestsTo 方法来修改此行为:

use Illuminate\Http\Request;

->withMiddleware(function (Middleware $middleware) {
    $middleware->redirectGuestsTo('/login');

    // 使用闭包...
    $middleware->redirectGuestsTo(fn (Request $request) => route('login'));
})

指定守卫

在将 auth 中间件附加到路由时,您还可以指定应使用哪个“守卫”来认证用户。指定的守卫应与您的 auth.php 配置文件的 guards 数组中的一个键相对应:

Route::get('/flights', function () {
    // 只有已认证用户可以访问此路由...
})->middleware('auth:admin');

登录节流

如果您正在使用 Laravel Breeze 或 Laravel Jetstream 入门套件,则会自动对登录尝试应用速率限制。默认情况下,如果用户在多次尝试后未能提供正确的凭据,他们将在一分钟内无法登录。节流是针对用户的用户名/电子邮件地址及其 IP 地址的唯一标识。

[!NOTE]
如果您想对应用程序中的其他路由进行速率限制,请查看速率限制文档

手动认证用户

您不需要使用 Laravel 的应用程序入门套件中包含的认证脚手架。如果您选择不使用此脚手架,则需要直接使用 Laravel 认证类来管理用户认证。别担心,这很简单!

我们将通过 Auth 外观访问 Laravel 的认证服务,因此我们需要确保在类的顶部导入 Auth 外观。接下来,让我们看看 attempt 方法。attempt 方法通常用于处理来自应用程序“登录”表单的认证尝试。如果认证成功,您应该重新生成用户的会话以防止会话固定 (opens in a new tab)

<?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 的认证服务将根据您的认证守卫的“提供程序”配置从您的数据库中检索用户。在默认的 config/auth.php 配置文件中,指定了 Eloquent 用户提供程序,并指示在检索用户时使用 App\Models\User 模型。您可以根据应用程序的需要在配置文件中更改这些值。

如果认证成功,attempt 方法将返回 true。否则,将返回 false

Laravel 的重定向器提供的 intended 方法将用户重定向到他们在被认证中间件拦截之前试图访问的 URL。如果预期目标不可用,可以为此方法提供一个回退 URI。

指定其他条件

如果需要,您还可以在用户的电子邮件和密码之外,向认证查询添加额外的查询条件。要实现此目的,我们可以简单地将查询条件添加到传递给 attempt 方法的数组中。例如,我们可以验证用户是否被标记为“活跃”:

if (Auth::attempt(['email' => $email, 'password' => $password, 'active' => 1])) {
    // 认证成功...
}

对于复杂的查询条件,您可以在凭据数组中提供一个闭包。此闭包将与查询实例一起调用,允许您根据应用程序的需要自定义查询:

use Illuminate\Database\Eloquent\Builder;

if (Auth::attempt([
    'email' => $email,
    'password' => $password,
    fn (Builder $query) => $query->has('activeSubscription'),
])) {
    // 认证成功...
}

[!WARNING]
在这些示例中,email 不是必需的选项,它只是作为一个示例使用。您应该使用与您的数据库表中的“用户名”相对应的任何列名。

attemptWhen 方法,它将一个闭包作为其第二个参数,可用于在实际认证用户之前对潜在用户进行更广泛的检查。闭包接收潜在用户,并应返回 truefalse 以指示用户是否可以进行认证:

if (Auth::attemptWhen([
    'email' => $email,
    'password' => $password,
], function (User $user) {
    return $user->isNotBanned();
})) {
    // 认证成功...
}

访问特定的守卫实例

通过 Auth 外观的 guard 方法,您可以指定在认证用户时要使用的守卫实例。这允许您使用完全独立的可认证模型或用户表来管理应用程序的不同部分的认证:

传递给 guard 方法的守卫名称应与您的 auth.php 配置文件中配置的守卫之一相对应:

if (Auth::guard('admin')->attempt($credentials)) {
    //...
}

记住用户

许多 Web 应用程序在其登录表单上提供了“记住我”复选框。如果您想在应用程序中提供“记住我”功能,您可以将一个布尔值作为第二个参数传递给 attempt 方法。

当此值为 true 时,Laravel 将无限期地保持用户认证状态,或者直到他们手动注销。您的 users 表必须包含字符串 remember_token 列,该列将用于存储“记住我”令牌。新的 Laravel 应用程序中包含的 users 表迁移已经包含了此列:

use Illuminate\Support\Facades\Auth;

if (Auth::attempt(['email' => $email, 'password' => $password], $remember)) {
    // 用户将被记住...
}

如果您的应用程序提供“记住我”功能,您可以使用 viaRemember 方法来确定当前认证用户是否使用“记住我”cookie 进行了认证:

use Illuminate\Support\Facades\Auth;

if (Auth::viaRemember()) {
    //...
}

其他认证方法

认证用户实例

如果您需要将现有的用户实例设置为当前认证用户,您可以将用户实例传递给 Auth 外观的 login 方法。给定的用户实例必须是 Illuminate\Contracts\Auth\Authenticatable 契约的实现。Laravel 中包含的 App\Models\User 模型已经实现了此接口。当您已经有一个有效的用户实例时,例如在用户在您的应用程序中注册后直接使用此方法,这种认证方法很有用:

use Illuminate\Support\Facades\Auth;

Auth::login($user);

您可以将一个布尔值作为第二个参数传递给 login 方法。此值表示认证会话是否需要“记住我”功能。请记住,这意味着会话将无限期地保持认证状态,或者直到用户手动从应用程序中注销:

Auth::login($user, $remember = true);

如果需要,您可以在调用 login 方法之前指定一个认证守卫:

Auth::guard('admin')->login($user);

通过 ID 认证用户

要使用其数据库记录的主键认证用户,您可以使用 loginUsingId 方法。此方法接受您希望认证的用户的主键:

Auth::loginUsingId(1);

您可以将一个布尔值传递给 loginUsingId 方法的 remember 参数。此值表示认证会话是否需要“记住我”功能。请记住,这意味着会话将无限期地保持认证状态,或者直到用户手动从应用程序中注销:

Auth::loginUsingId(1, remember: true);

一次性认证用户

您可以使用 once 方法为应用程序的单个请求认证用户。调用此方法时不会使用会话或 cookie:

if (Auth::once($credentials)) {
    //...
}

HTTP 基本认证

HTTP 基本认证 (opens in a new tab)为您的应用程序的用户提供了一种无需设置专用“登录”页面即可进行快速认证的方法。要开始使用,将 auth.basic 中间件附加到路由上。auth.basic 中间件包含在 Laravel 框架中,因此您无需定义它:

Route::get('/profile', function () {
    // 只有已认证用户可以访问此路由...
})->middleware('auth.basic');

一旦将中间件附加到路由上,在您的浏览器中访问该路由时,系统将自动提示您输入凭据。默认情况下,auth.basic 中间件将假定您的 users 数据库表中的 email 列是用户的“用户名”。

关于 FastCGI 的注意事项

如果您使用 PHP FastCGI 和 Apache 来服务您的 Laravel 应用程序,HTTP 基本认证可能无法正常工作。要纠正这些问题,可以将以下行添加到您的应用程序的 .htaccess 文件中:

RewriteCond %{HTTP:Authorization} ^(.+)$
RewriteRule.* - [E=HTTP_AUTHORIZATION:%{HTTP:Authorization}]

无状态 HTTP 基本认证

您还可以使用 HTTP 基本认证而不在会话中设置用户标识符 cookie。如果您选择使用 HTTP 认证来认证对您的应用程序的 API 的请求,这主要是有帮助的。要实现此目的,定义一个中间件,该中间件调用 onceBasic 方法。如果 onceBasic 方法未返回响应,则请求可以进一步传递到应用程序中:

<?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);
    }

}

接下来,将中间件附加到路由上:

Route::get('/api/user', function () {
    // 只有已认证用户可以访问此路由...
})->middleware(AuthenticateOnceWithBasicAuth::class);

注销

要手动将用户从您的应用程序中注销,您可以使用 Auth 外观提供的 logout 方法。这将从用户的会话中删除认证信息,以便后续请求不会被认证。

除了调用 logout 方法外,建议您使用户的会话无效并重新生成他们的CSRF 令牌。在用户注销后,您通常会将用户重定向到您的应用程序的根目录:

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 中间件别名AuthenticateSession 中间件附加到路由上:

Route::middleware(['auth', 'auth.session'])->group(function () {
    Route::get('/', function () {
        //...
    });
});

然后,您可以使用 Auth 门面提供的 logoutOtherDevices 方法。此方法要求用户确认其当前密码,您的应用程序应通过输入表单接受该密码:

use Illuminate\Support\Facades\Auth;
 
Auth::logoutOtherDevices($currentPassword);

当调用 logoutOtherDevices 方法时,用户的其他会话将完全失效,这意味着他们将从之前通过认证的所有守卫中“登出”。

密码确认

在构建应用程序时,您可能偶尔会有一些操作,这些操作应该要求用户在执行操作之前或在用户被重定向到应用程序的敏感区域之前确认他们的密码。Laravel 包含内置的中间件,使这个过程变得轻松。实现此功能需要您定义两条路由:一条路由用于显示一个视图,要求用户确认他们的密码,另一条路由用于确认密码是否有效,并将用户重定向到他们的预期目的地。

[!NOTE]
以下文档讨论了如何直接与 Laravel 的密码确认功能集成;但是,如果您想更快地开始,Laravel 应用程序启动套件包含对此功能的支持!

配置

在用户确认密码后,他们在三个小时内不会再次被要求确认密码。但是,您可以通过更改应用程序的 config/auth.php 配置文件中的 password_timeout 配置值来配置用户再次被提示输入密码的时间间隔。

路由

密码确认表单

首先,我们将定义一个路由来显示一个视图,该视图请求用户确认他们的密码:

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

如您所料,此路由返回的视图应该有一个包含 password 字段的表单。此外,您可以在视图中随意包含一些文本,说明用户正在进入应用程序的受保护区域,必须确认他们的密码。

确认密码

接下来,我们将定义一个路由,该路由将处理来自“确认密码”视图的表单请求。此路由将负责验证密码并将用户重定向到他们的预期目的地:

use Illuminate\Http\Request;
use Illuminate\Support\Facades\Hash;
use Illuminate\Support\Facades\Redirect;
 
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 的会话,用户已经确认了他们的密码。passwordConfirmed 方法将在用户的会话中设置一个时间戳,Laravel 可以使用该时间戳来确定用户最后确认密码的时间。最后,我们可以将用户重定向到他们的预期目的地。

保护路由

您应该确保任何需要最近密码确认才能执行操作的路由都被分配了 password.confirm 中间件。此中间件在 Laravel 的默认安装中包含,它将自动在会话中存储用户的预期目的地,以便用户在确认密码后可以被重定向到该位置。在会话中存储用户的预期目的地后,中间件将用户重定向到 password.confirm 命名路由

Route::get('/settings', function () {
    //...
})->middleware(['password.confirm']);
 
Route::post('/settings', function () {
    //...
})->middleware(['password.confirm']);

添加自定义守卫

您可以使用 Auth 门面的 extend 方法定义自己的认证守卫。您应该在 服务提供者 内调用 extend 方法。由于 Laravel 已经附带了一个 AppServiceProvider,我们可以将代码放在该提供者中:

<?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 的实现。此接口包含一些您需要实现的方法,以定义自定义守卫。一旦定义了自定义守卫,您可以在 auth.php 配置文件的 guards 配置中引用该守卫:

'guards' => [
    'api' => [
        'driver' => 'jwt',
        'provider' => 'users',
    ],
],

闭包请求守卫

实现自定义的、基于 HTTP 请求的认证系统的最简单方法是使用 Auth::viaRequest 方法。此方法允许您使用单个闭包快速定义您的认证过程。

要开始使用,在应用程序的 AppServiceProviderboot 方法中调用 Auth::viaRequest 方法。viaRequest 方法接受一个认证驱动程序名称作为其第一个参数。此名称可以是描述您的自定义守卫的任何字符串。传递给该方法的第二个参数应该是一个闭包,该闭包接收传入的 HTTP 请求并返回一个用户实例,如果认证失败,则返回 null

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 配置中将其配置为驱动程序:

'guards' => [
    'api' => [
        'driver' => 'custom-token',
    ],
],

最后,您可以在为路由分配认证中间件时引用该守卫:

Route::middleware('auth:api')->group(function () {
    //...
});

添加自定义用户提供程序

如果您没有使用传统的关系数据库来存储用户,您将需要使用自己的认证用户提供程序来扩展 Laravel。我们将使用 Auth 门面的 provider 方法来定义自定义用户提供程序。用户提供程序解析器应该返回 Illuminate\Contracts\Auth\UserProvider 的实现:

<?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

'providers' => [
    'users' => [
        'driver' => 'mongo',
    ],
],

最后,您可以在 guards 配置中引用此提供程序:

'guards' => [
    'web' => [
        'driver' => 'session',
        'provider' => 'users',
    ],
],

用户提供程序契约

Illuminate\Contracts\Auth\UserProvider 实现负责从持久存储系统(如 MySQL、MongoDB 等)中获取 Illuminate\Contracts\Auth\Authenticatable 实现。这两个接口允许 Laravel 认证机制无论用户数据如何存储或用于表示经过身份验证的用户的类的类型如何,都能继续正常工作:

让我们看一下 Illuminate\Contracts\Auth\UserProvider 契约:

<?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”条件的查询,该查询搜索具有与 $credentials['username'] 值匹配的“用户名”的用户记录。该方法应返回 Authenticatable 的实现。此方法不应尝试进行任何密码验证或认证。

validateCredentials 方法应将给定的 $user$credentials 进行比较以对用户进行认证。例如,此方法通常会使用 Hash::check 方法将 $user->getAuthPassword() 的值与 $credentials['password'] 的值进行比较。此方法应返回 truefalse,表示密码是否有效。

rehashPasswordIfRequired 方法应在需要且支持的情况下重新哈希给定的 $user 的密码。例如,此方法通常会使用 Hash::needsRehash 方法来确定 $credentials['password'] 值是否需要重新哈希。如果密码需要重新哈希,该方法应使用 Hash::make 方法重新哈希密码并更新底层持久存储中的用户记录。

可认证契约

现在我们已经探讨了 UserProvider 上的每个方法,让我们看一下 Authenticatable 契约。请记住,用户提供程序应从 retrieveByIdretrieveByTokenretrieveByCredentials 方法中返回此接口的实现:

<?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。bcrypt 哈希的“工作因子”可以通过应用程序的 config/hashing.php 配置文件或 BCRYPT_ROUNDS 环境变量进行调整。

通常,随着 CPU / GPU 处理能力的提高,bcrypt 工作因子应随着时间的推移而增加。如果您为应用程序增加 bcrypt 工作因子,当用户通过 Laravel 的启动套件或当您通过 attempt 方法手动认证用户 与您的应用程序进行认证时,Laravel 将优雅地自动重新哈希用户密码。

通常,自动密码重新哈希不应中断您的应用程序;但是,您可以通过发布 hashing 配置文件来禁用此行为:

php artisan config:publish hashing

一旦发布了配置文件,您可以将 rehash_on_login 配置值设置为 false

'rehash_on_login' => false,

事件

Laravel 在认证过程中会调度各种事件。您可以为以下任何事件定义监听器

事件名称
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