Skip to content

认证

介绍

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

从本质上讲,Laravel的认证功能由“守卫”和“提供者”组成。守卫定义了如何为每个请求认证用户。例如,Laravel提供了一个session守卫,它使用会话存储和cookie来维护状态。

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

您的应用程序的认证配置文件位于config/auth.php。该文件包含多个文档良好的选项,用于调整Laravel认证服务的行为。

lightbulb

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

启动工具包

想要快速入门吗?在一个新的Laravel应用程序中安装Laravel应用程序启动工具包。在迁移数据库后,导航到/register或分配给您的应用程序的任何其他URL。启动工具包将处理您整个认证系统的脚手架!

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

数据库注意事项

默认情况下,Laravel在您的app/Models目录中包含一个App\Models\User Eloquent模型。该模型可以与默认的Eloquent认证驱动程序一起使用。

如果您的应用程序不使用Eloquent,您可以使用database认证提供者,该提供者使用Laravel查询构建器。如果您的应用程序使用MongoDB,请查看MongoDB的官方Laravel用户认证文档

在为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的视图层由简单的Blade模板组成,使用Tailwind CSS进行样式。要开始,请查看Laravel的应用程序启动工具包文档。

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

Laravel Jetstream 是一个强大的应用程序启动工具包,消费和暴露Laravel Fortify的认证服务,具有由Tailwind CSSLivewire和/或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规范提供的复杂功能,这可能会让用户和开发人员感到困惑。此外,开发人员在使用像Passport这样的OAuth2认证提供者对SPA应用程序或移动应用程序进行身份验证时,历史上也感到困惑。

Sanctum

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

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

Laravel Sanctum是我们选择与Laravel Jetstream应用程序启动工具包一起包含的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的认证栈。

认证快速入门

exclamation

本部分文档讨论通过Laravel应用程序启动工具包认证用户,该工具包包括UI脚手架以帮助您快速入门。如果您希望直接与Laravel的认证系统集成,请查看有关手动认证用户的文档。

安装启动工具包

首先,您应该安装Laravel应用程序启动工具包。我们当前的启动工具包,Laravel Breeze和Laravel Jetstream,提供了美观设计的起始点,以将认证纳入您的新Laravel应用程序。

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

Laravel Jetstream是一个更强大的应用程序启动工具包,支持使用LivewireInertia和Vue为您的应用程序提供脚手架。此外,Jetstream具有对双因素认证、团队、个人资料管理、浏览器会话管理、通过Laravel Sanctum的API支持、账户删除等的可选支持。

检索已认证用户

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

php
use Illuminate\Support\Facades\Auth;

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

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

或者,一旦用户被认证,您可以通过Illuminate\Http\Request实例访问已认证用户。请记住,类型提示的类将自动注入到您的控制器方法中。通过类型提示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()) {
    // 用户已登录...
}
lightbulb

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

保护路由

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

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

重定向未认证用户

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

php
use Illuminate\Http\Request;

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

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

指定守卫

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

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

登录节流

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

lightbulb

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

手动认证用户

您不必使用Laravel的应用程序启动工具包中包含的认证脚手架。如果您选择不使用此脚手架,您将需要直接使用Laravel认证类管理用户认证。别担心,这很简单!

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

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

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

Laravel的重定向器提供的intended方法将用户重定向到他们在被认证中间件拦截之前试图访问的URL。如果未提供意图的目标,则可以给此方法一个后备URI。

指定附加条件

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

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

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

php
use Illuminate\Database\Eloquent\Builder;

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

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

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

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

访问特定守卫实例

通过Auth门面的guard方法,您可以指定在认证用户时希望使用哪个守卫实例。这使您能够使用完全不同的可认证模型或用户表管理应用程序的不同部分的认证。

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

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

记住用户

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

当此值为true时,Laravel将使用户无限期保持认证,直到他们手动注销。您的users表必须包含字符串remember_token列,该列将用于存储“记住我”令牌。新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门面的login方法。给定的用户实例必须实现Illuminate\Contracts\Auth\Authenticatable 契约。Laravel中包含的App\Models\User模型已经实现了此接口。这种认证方法在您已经拥有有效用户实例时非常有用,例如在用户注册后:

php
use Illuminate\Support\Facades\Auth;

Auth::login($user);

您可以将布尔值作为第二个参数传递给login方法。此值指示是否希望为已认证会话提供“记住我”功能。请记住,这意味着会话将无限期认证,直到用户手动注销应用程序:

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

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

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

通过ID认证用户

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

php
Auth::loginUsingId(1);

您可以将布尔值传递给loginUsingId方法的remember参数。此值指示是否希望为已认证会话提供“记住我”功能。请记住,这意味着会话将无限期认证,直到用户手动注销应用程序:

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 () {
    // 只有已认证用户可以访问此路由...
})->middleware('auth.basic');

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

关于FastCGI的说明

如果您使用PHP FastCGI和Apache来提供Laravel应用程序,HTTP基本认证可能无法正常工作。要解决这些问题,可以将以下行添加到应用程序的.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);
    }

}

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

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

注销

要手动注销用户,您可以使用Auth门面提供的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中间件。通常,您应将此中间件放置在路由组定义中,以便可以应用于大多数应用程序的路由。默认情况下,可以使用auth.session 中间件别名AuthenticateSession中间件附加到路由:

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

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

php
use Illuminate\Support\Facades\Auth;

Auth::logoutOtherDevices($currentPassword);

当调用logoutOtherDevices方法时,用户的其他会话将完全失效,这意味着他们将“注销”所有他们之前已认证的守卫。

密码确认

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

lightbulb

以下文档讨论如何直接与Laravel的密码确认功能集成;但是,如果您希望更快入门,Laravel应用程序启动工具包包括对该功能的支持!

配置

在确认密码后,用户在三小时内不会被要求再次确认其密码。但是,您可以通过更改应用程序的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;
use Illuminate\Support\Facades\Redirect;

Route::post('/confirm-password', function (Request $request) {
    if (! Hash::check($request->password, $request->user()->password)) {
        return back()->withErrors([
            'password' => ['提供的密码与我们的记录不匹配。']
        ]);
    }

    $request->session()->passwordConfirmed();

    return redirect()->intended();
})->middleware(['auth', 'throttle:6,1']);

在继续之前,让我们更详细地检查此路由。首先,确定请求的password字段确实与已认证用户的密码匹配。如果密码有效,我们需要通知Laravel的会话用户已确认其密码。passwordConfirmed方法将在用户的会话中设置一个时间戳,Laravel可以使用该时间戳来确定用户上次确认密码的时间。最后,我们可以将用户重定向到其意图的目的地。

保护路由

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

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

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

添加自定义守卫

您可以使用Auth门面的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) {
            // 返回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方法。此方法允许您使用单个闭包快速定义认证过程。

要开始,请在应用程序的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配置中的驱动程序:

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

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

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

添加自定义用户提供者

如果您不使用传统关系数据库来存储用户,则需要使用自己的认证用户提供者扩展Laravel。我们将使用provider方法在Auth门面上定义自定义用户提供者。用户提供者解析器应返回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”条件的查询,搜索与$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
<?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将在用户通过Laravel的启动工具包进行身份验证时,或在您通过attempt方法手动认证用户时,优雅地自动重新哈希用户密码。

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

shell
php artisan config:publish hashing

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

php
'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