Skip to content

认证

介绍

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

Laravel 的身份验证工具的核心是由 “守卫” 和 “提供者” 组成的。守卫定义如何针对每个请求对用户进行身份验证。例如,Laravel 附带了一个 session guard,它使用 session storage 和 cookie 来维护状态。

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

应用程序的身份验证配置文件位于 config/auth.php。此文件包含几个有据可查的选项,用于调整 Laravel 身份验证服务的行为。

NOTE

守卫和提供程序不应与 “roles” 和 “permissions” 混淆。要了解有关通过权限授权用户操作的更多信息,请参阅 授权文档.

入门套件

想要快速入门?在新的 Laravel 应用程序中安装 Laravel 应用程序初学者工具包。迁移数据库后,将浏览器导航到 /register 或分配给应用程序的任何其他 URL。入门套件将负责搭建您的整个身份验证系统的脚手架!

**即使您选择在最终的 Laravel 应用程序中不使用初学者工具包,安装 Laravel Breeze 初学者工具包也可能是一个绝佳的机会,可以学习如何在实际的 Laravel 项目中实现 Laravel 的所有身份验证功能。**由于 Laravel Breeze 为您创建了身份验证控制器、路由和视图,因此您可以检查这些文件中的代码以了解如何实现 Laravel 的身份验证功能。

数据库注意事项

默认情况下,Laravel 在您的 app/Models 目录中包含一个 App\Models\UserEloquent 模型。此模型可以与默认的 Eloquent 身份验证驱动程序一起使用。如果您的应用程序没有使用 Eloquent,您可以使用使用 Laravel 查询构建器的数据库身份验证提供程序。

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

此外,还应验证 users(或等效)表是否包含 100 个字符的可为 null 的字符串remember_token列。此列将用于为在登录应用程序时选择 “remember me” 选项的用户存储令牌。同样,新 Laravel 应用程序中包含的默认 users 表迁移已经包含此列。

生态系统概述

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

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

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

Laravel 的内置浏览器身份验证服务

Laravel 包括内置的身份验证和会话服务,通常通过 AuthSession Facade访问。这些功能为从 Web 浏览器启动的请求提供基于 Cookie 的身份验证。它们提供的方法允许您验证用户的凭证并验证用户。此外,这些服务会自动在用户的会话中存储适当的身份验证数据,并发出用户的会话 cookie。本文档中包含有关如何使用这些服务的讨论。

应用程序入门套件

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

Laravel Breeze 是 Laravel 所有身份验证功能的简单、最小实现,包括登录、注册、密码重置、电子邮件验证和密码确认。Laravel Breeze 的视图层由使用 Tailwind CSS 设置样式的简单 Blade 模板组成。要开始使用,请查看有关 Laravel 应用程序初学者工具包的文档。

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

Laravel Jetstream 是一个强大的应用程序初学者工具包,它使用和公开 Laravel Fortify 的身份验证服务,具有由 Tailwind CSS、Livewire 和/或 Inertia 提供支持的美观、现代的 UI。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 规范提供的复杂功能,这可能会让用户和开发人员感到困惑。此外,开发人员历来对如何使用 OAuth2 身份验证提供程序(如 Passport)对 SPA 应用程序或移动应用程序进行身份验证感到困惑。

Sanctum

为了应对 OAuth2 的复杂性和开发人员的困惑,我们着手构建一个更简单、更精简的身份验证包,该包可以处理来自 Web 浏览器的第一方 Web 请求和通过令牌的 API 请求。这一目标随着 Laravel Sanctum 的发布而实现,对于除了 API 之外还将提供第一方 Web UI 的应用程序,或者由独立于后端 Laravel 应用程序或提供移动客户端的应用程序提供支持的应用程序,应将其视为首选和推荐的身份验证包。

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

Laravel Sanctum 是我们选择包含在 Laravel Jetstream 应用程序初学者工具包中的 API 包,因为我们认为它最适合大多数 Web 应用程序的身份验证需求。

Summary and Choosing Your Stack

总之,如果您的应用程序将使用浏览器访问,并且您正在构建一个整体式 Laravel 应用程序,那么您的应用程序将使用 Laravel 的内置身份验证服务。

接下来,如果您的应用程序提供将由第三方使用的 API,您将在 PassportSanctum 之间进行选择,以便为您的应用程序提供 API 令牌身份验证。通常,应尽可能首选 Sanctum,因为它是 API 身份验证、SPA 身份验证和移动身份验证的简单、完整的解决方案,包括对“范围”或“能力”的支持。

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

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

而且,如果您想快速入门,我们很高兴推荐 Laravel Breeze 作为启动新 Laravel 应用程序的快速方法,该应用程序已经使用我们首选的 Laravel 内置身份验证服务和 Laravel Sanctum 身份验证堆栈。

身份验证快速入门

WARNING

文档的这一部分讨论了通过 Laravel 应用程序初学者工具包对用户进行身份验证,其中包括 UI 基架,可帮助您快速入门。如果您想直接与 Laravel 的身份验证系统集成,请查看有关手动验证用户的文档。authenticating users](#authenticating-users).

安装初学者套件

首先,您应该安装 Laravel 应用程序初学者工具包。我们当前的入门工具包 Laravel Breeze 和 Laravel Jetstream 为将身份验证整合到您的新 Laravel 应用程序中提供了设计精美的起点。

Laravel Breeze 是 Laravel 所有身份验证功能的最小、简单的实现,包括登录、注册、密码重置、电子邮件验证和密码确认。Laravel Breeze 的视图层由使用 Tailwind CSS 设置样式的简单 Blade 模板组成。此外,Breeze 提供了基于 LivewireInertia 的脚手架选项,可以选择使用 Vue 或 React 进行基于 Inertia 的脚手架。

Laravel Jetstream 是一个更强大的应用程序初学者工具包,包括对使用 LivewireInertia 和 Vue 构建应用程序基架的支持。此外,Jetstream 还提供对双因素身份验证、团队、配置文件管理、浏览器会话管理、通过 Laravel Sanctum 的 API 支持、帐户删除等的可选支持。

检索经过身份验证的用户

在安装身份验证初学者工具包并允许用户注册和验证您的应用程序后,您通常需要与当前经过身份验证的用户进行交互。在处理传入请求时,您可以通过 Auth Facade的 user 方法访问经过身份验证的用户:

php
    use Illuminate\Support\Facades\Auth;

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

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

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

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 Facade上使用 check 方法。如果用户经过身份验证,此方法将返回 true:

php
    use Illuminate\Support\Facades\Auth;

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

NOTE

即使可以使用 check 方法确定用户是否经过身份验证,您通常会在允许用户访问某些路由/控制器之前使用中间件来验证用户是否经过身份验证。要了解更多信息,请查看有关保护路由的文档。

路由保护

路由中间件可用于仅允许经过身份验证的用户访问给定的路由。Laravel 附带了一个 auth 中间件,它是 Illuminate\Auth\Middleware\Authenticate 类的中间件别名。由于这个 middleware 已经被 Laravel 内部别名了,你需要做的就是将 middleware 附加到路由定义上:

php
    Route::get('/flights', function () {
        // 只有经过身份验证的用户才能访问此路由...
    })->middleware('auth');

重定向未经身份验证的用户

auth 中间件检测到未经身份验证的用户时,它会将用户重定向到名为 route登录名。您可以使用应用程序的 bootstrap/app.php 文件的 redirectGuestsTo 方法修改此行为:

php
    use Illuminate\Http\Request;

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

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

指定守卫

当将 auth middleware 附加到路由时,你还可以指定应该使用哪个 “guard” 来验证用户。指定的 guard 应该对应于 auth.php 配置文件的 guards 数组中的一个键:

php
    Route::get('/flights', function () {
        // 只有经过身份验证的用户才能访问此路由...
    })->middleware('auth:admin');

登录限制

如果您使用的是 Laravel Breeze 或 Laravel Jetstream 初学者工具包,则速率限制将自动应用于登录尝试。默认情况下,如果用户在多次尝试后仍未提供正确的凭证,则一分钟内将无法登录。限制对于用户的用户名/电子邮件地址及其 IP 地址是唯一的。

NOTE

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

手动验证用户

您不需要使用 Laravel 应用程序初学者工具包中包含的身份验证基架。如果您选择不使用此基架,则需要直接使用 Laravel 身份验证类来管理用户身份验证。别担心,这很容易!

我们将通过 Authfacade访问 Laravel 的身份验证服务,因此我们需要确保在类的顶部导入 Auth Facade。接下来,让我们看看 attempt 方法。attempt 方法通常用于处理来自应用程序的 “login” 表单的身份验证尝试。如果身份验证成功,则应重新生成用户的会话以防止会话固定:

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' => 'The provided credentials do not match our records.',
            ])->onlyInput('email');
        }
    }

attempt 方法接受键/值对数组作为其第一个参数。数组中的值将用于在数据库表中查找用户。因此,在上面的示例中,将按 email 列的值检索用户。如果找到用户,则存储在数据库中的哈希密码将与通过数组传递给方法的密码值进行比较。您不应该对传入请求的 password 值进行哈希处理,因为框架会在将其与数据库中的哈希密码进行比较之前自动对该值进行哈希处理。如果两个经过哈希处理的密码匹配,将为用户启动经过身份验证的会话。

请记住,Laravel 的身份验证服务将根据身份验证守卫的 “provider” 配置从数据库中检索用户。在默认的 config/auth.php 配置文件中,指定了 Eloquent 用户提供程序,并指示它在检索用户时使用 App\Models\User 模型。您可以根据应用程序的需要在配置文件中更改这些值。

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

Laravel 的重定向器提供的预期方法会将用户重定向到他们尝试访问的 URL,然后被身份验证中间件拦截。如果预期目标不可用,则可以为此方法提供回退 URI。

指定附加条件

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

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

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

php
    use Illuminate\Database\Eloquent\Builder;

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

WARNING

在这些示例中,email不是必需的选项,它仅用作示例。您应该使用与数据库表中的 “username” 对应的任何列名。

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

php
    if (Auth::attemptWhen([
        'email' => $email,
        'password' => $password,
    ], function (User $user) {
        return $user->isNotBanned();
    })) {
        // Authentication was successful...
    }

访问特定 Guard 实例

通过 Auth Facade的 guard 方法,你可以指定在对用户进行身份验证时想要使用的 guard 实例。这允许您使用完全独立的可验证模型或用户表来管理应用程序不同部分的身份验证。

传递给 guard 方法的 guard name 应该对应于 auth.php 配置文件中配置的 guards 之一:

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

记住用户

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

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

php
    use Illuminate\Support\Facades\Auth;

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

如果您的应用程序提供“记住我”功能,则可以使用 viaRemember 方法来确定当前经过身份验证的用户是否使用“记住我”Cookie 进行身份验证:

php
    use Illuminate\Support\Facades\Auth;

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

其他身份验证方法

对用户实例进行身份验证

如果需要将现有用户实例设置为当前经过身份验证的用户,则可以将用户实例传递给 Auth Facade 的 login 方法。给定的用户实例必须是 ContractIlluminate\Contracts\Auth\Authenticatable 实现。Laravel 附带的 App\Models\User 模型已经实现了这个接口。当您已经拥有有效的 user 实例时,例如在用户注册到您的应用程序之后,这种身份验证方法非常有用:

php
    use Illuminate\Support\Facades\Auth;

    Auth::login($user);

您可以将布尔值作为第二个参数传递给 login 方法。此值指示已验证的会话是否需要 “remember me” 功能。请记住,这意味着会话将无限期地进行身份验证,或者直到用户手动注销应用程序:

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

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

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

按 ID 对用户进行身份验证

要使用用户数据库记录的主键对用户进行身份验证,您可以使用 loginUsingId 方法。此方法接受您要进行身份验证的用户的主密钥:

php
    Auth::loginUsingId(1);

您可以将布尔值传递给 loginUsingId 方法的 remember 参数。此值指示已验证的会话是否需要 “remember me” 功能。请记住,这意味着会话将无限期地进行身份验证,或者直到用户手动注销应用程序:

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

对用户进行身份验证一次

您可以使用 once 方法通过应用程序对单个请求的用户进行身份验证。调用此方法时,不会使用任何会话或 Cookie:

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

HTTP 基本身份验证

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

php
    Route::get('/profile', function () {
        // Only authenticated users may access this route...
    })->middleware('auth.basic');

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

关于 FastCGI 的说明

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

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

无状态 HTTP 基本身份验证

您也可以使用 HTTP 基本身份验证,而无需在会话中设置用户标识符 cookie。如果您选择使用 HTTP 身份验证来验证对应用程序 API 的请求,则这非常有用。为此,请定义一个调用 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);
        }

    }

接下来,将 middleware 附加到路由:

php
    Route::get('/api/user', function () {
        // Only authenticated users may access this route...
    })->middleware(AuthenticateOnceWithBasicAuth::class);

注销

要手动将用户从您的应用程序中注销,您可以使用 Auth Facade提供的 logout 方法。这将从用户的会话中删除身份验证信息,以便不对后续请求进行身份验证。

除了调用 logout 方法外,还建议您使用户的会话失效并重新生成其 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 中间件包含在应该接收会话身份验证的路由中。通常,您应该将此中间件放在 Route Group 定义上,以便它可以应用于应用程序的大多数路由。默认情况下,AuthenticateSession 中间件可以使用 auth.session中间件别名附加到路由:

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

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

php
    use Illuminate\Support\Facades\Auth;

    Auth::logoutOtherDevices($currentPassword);

When the logoutOtherDevices method is invoked, the user's other sessions will be invalidated entirely, meaning they will be "logged out" of all guards they were previously authenticated by.

密码确认

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

NOTE

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

配置

确认密码后,系统在 3 小时内不会要求用户再次确认密码。但是,您可以通过更改应用程序的 config/auth.php 配置文件中的 password_timeout 配置值的值来配置重新提示用户输入密码之前的时间长度。

路由

密码确认

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

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

如您所料,此路由返回的视图应具有包含 password 字段的表单。此外,请随意在视图中包含文本,说明用户正在进入应用程序的受保护区域,并且必须确认其密码。

确认密码

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

php
    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 的默认安装中,它会自动将用户的预期目的地存储在会话中,以便在确认密码后将用户重定向到该位置。在 session 中存储用户的预期目的地后,中间件会将用户重定向到 password.confirm命名路由:

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

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

添加自定义守卫

您可以使用 Auth Facade上的 extend 方法定义自己的身份验证守卫。您应该在服务提供商中调用 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) {
                // Return an instance of Illuminate\Contracts\Auth\Guard...

                return new JwtGuard(Auth::createUserProvider($config['provider']));
            });
        }
    }

如上例所示,传递给 extend 方法的回调应返回 Illuminate\Contracts\Auth\Guard .此接口包含一些定义自定义守卫需要实现的方法。定义自定义守卫后,您可以在 auth.php 配置文件的 guards 配置中引用守卫:

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

请求守卫

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

要开始使用,请在应用程序的 AppServiceProviderboot 方法中调用 Auth::viaRequest 方法。viaRequest 方法接受身份验证驱动程序名称作为其第一个参数。此名称可以是描述自定义守卫的任何字符串。传递给该方法的第二个参数应该是一个闭包,它接收传入的 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',
        ],
    ],

最后,你可以在将身份验证中间件分配给路由时引用守卫:

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

添加自定义用户提供程序

如果您不使用传统的关系数据库来存储用户,则需要使用自己的身份验证用户提供程序来扩展 Laravel。我们将在 Auth Facade 上使用 provider 方法来定义自定义用户提供程序。用户提供程序解析程序应返回以下 Illuminate\Contracts\Auth\UserProvider implementation :

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) {
                // Return an instance of Illuminate\Contracts\Auth\UserProvider...

                return new MongoUserProvider($app->make('mongo.connection'));
            });
        }
    }

使用 provider 方法注册提供程序后,您可以在 auth.php 配置文件中切换到新的用户提供程序。首先,定义使用新驱动程序的提供程序:

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

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

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

用户提供者协定

Illuminate\Contracts\Auth\UserProvider implementations 负责从持久存储系统(如 MySQL、MongoDB 等)中获取 Illuminate\Contracts\Auth\Authenticatable implementation。这两个接口允许 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。在成功的 “remember me” 身份验证尝试或用户注销时,将向用户分配新令牌。

在尝试对应用程序进行身份验证时,retrieveByCredentials 方法接收传递给 Auth::attempt 方法的凭据数组。然后,该方法应 “查询” 与这些凭证匹配的用户的底层持久性存储。通常,此方法将运行具有 “where” 条件的查询,该查询搜索 “username” 与 $credentials['username'] 值匹配的用户记录。该方法应返回 Authenticatable 的实现。此方法不应尝试执行任何密码验证或身份验证。

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

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

可验证的 Contract

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

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 方法应返回用户的 password 列的名称。getAuthPassword 方法应返回用户的哈希密码。

此接口允许身份验证系统使用任何“用户”类,无论您使用什么 ORM 或存储抽象层。默认情况下,Laravel 在 app/Models 目录中包含一个 App\Models\User 类,该类实现了此接口。

自动密码重新哈希

Laravel 的默认密码哈希算法是 bcrypt。bcrypt 哈希的 “work factor” 可以通过应用程序的 config/hashing.php 配置文件或 BCRYPT_ROUNDS 环境变量进行调整。

通常,随着 CPU/GPU 处理能力的增加,bcrypt 工作因子应随着时间的推移而增加。如果您增加应用程序的 bcrypt 工作因子,则当用户通过 Laravel 的初学者工具包使用您的应用程序进行身份验证时,或者当您通过 attempt 方法手动验证用户时,Laravel 将正常地自动重新处理用户密码。

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

shell
php artisan config:publish hashing

配置文件发布后,您可以将 rehash_on_login 配置值设置为 false:

php
'rehash_on_login' => false,

事件

Laravel 在身份验证过程中调度各种事件。您可以为以下任何事件定义侦听器:

Event Name
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