Skip to content

身份验证

简介

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

Laravel 的身份验证设施由"守卫"和"提供者"组成。守卫定义了如何为每个请求认证用户。例如,Laravel 附带一个 session 守卫,它使用会话存储和 cookie 维护状态。

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

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

NOTE

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

入门套件

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

即使您选择在最终的 Laravel 应用程序中不使用入门套件,安装入门套件也是学习如何在实际 Laravel 项目中实现所有 Laravel 身份验证功能的绝佳机会。 由于 Laravel 入门套件包含身份验证控制器、路由和视图,您可以检查这些文件中的代码,以了解如何实现 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 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 应用程序,您的应用程序将使用 Laravel 内置的身份验证服务。

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

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

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

而且,如果您想快速开始,我们很高兴推荐我们的应用程序入门套件,作为启动已经使用我们首选的 Laravel 内置身份验证服务身份验证技术栈的新 Laravel 应用程序的快速方式。

身份验证快速入门

WARNING

本部分文档讨论通过 Laravel 应用程序入门套件对用户进行身份验证,其中包括 UI 脚手架,可帮助您快速开始。如果您想直接与 Laravel 的身份验证系统集成,请查看有关手动认证用户的文档。

安装入门套件

首先,您应该安装 Laravel 应用程序入门套件。我们的入门套件提供了精心设计的起点,用于将身份验证整合到您的新 Laravel 应用程序中。

检索已认证用户

从入门套件创建应用程序并允许用户注册和与您的应用程序进行身份验证后,您通常需要与当前已认证的用户进行交互。在处理传入请求时,您可以通过 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()) {
    // 用户已登录...
}

NOTE

尽管可以使用 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): void {
    $middleware->redirectGuestsTo('/login');

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

重定向已认证用户

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

php
use Illuminate\Http\Request;

->withMiddleware(function (Middleware $middleware): void {
    $middleware->redirectUsersTo('/panel');

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

指定守卫

auth 中间件附加到路由时,您还可以指定应该用于认证用户的"守卫"。指定的守卫应对应于您的 auth.php 配置文件中 guards 数组的键之一:

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

登录限流

如果您使用我们的应用程序入门套件之一,速率限制将自动应用于登录尝试。默认情况下,如果用户在多次尝试后未能提供正确的凭据,则将无法登录一分钟。限流对于用户的用户名/电子邮件地址及其 IP 地址是唯一的。

NOTE

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

手动认证用户

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

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

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

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

intended 方法

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

指定附加条件

如果您愿意,除了用户的电子邮件和密码之外,还可以向身份验证查询添加额外的查询条件。例如,我们可以验证用户是否标记为"active":

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

为了获得最大的灵活性,您可以将闭包作为第二个参数传递给 attempt 方法。闭包将接收查询构建器实例,允许您根据应用程序的需要自定义查询:

php
use Illuminate\Database\Eloquent\Builder;

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

WARNING

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

访问特定的守卫实例

您可以使用 Auth 门面上的 guard 方法指定要利用哪个守卫实例。这允许您使用完全不同的可认证模型或用户表来管理应用程序的各个部分的身份验证。

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

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

记住用户

如果您想在应用程序中提供"记住我"功能,可以将布尔值作为第二个参数传递给 attempt 方法。如果此值为 true,Laravel 将保持用户身份验证 indefinitely(或直到他们手动注销)。您的 users 表必须包含字符串 remember_token 列,该列将用于存储"记住我"令牌。

php
use Illuminate\Support\Facades\Auth;

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

如果您的应用程序提供"记住我"功能,您可以使用 viaRemember 方法确定用户是否正在使用"记住我" cookie 进行身份验证:

php
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 认证用户

要使用用户的 ID 将用户登录到应用程序,可以使用 loginUsingId 方法。此方法接受您要认证用户的主键:

php
Auth::loginUsingId(1);

您可以将布尔值作为第二个参数传递给 loginUsingId 方法。这指示是否需要为已认证会话启用"记住我"功能:

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

一次性认证用户

您可以使用 once 方法为单个请求将用户登录到应用程序。不会使用任何 cookie 或会话,这在构建无状态 API 时可能很有用:

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

HTTP Basic 认证

HTTP Basic 认证提供了一种快速认证应用程序用户的方式,而无需设置专用的"登录"页面。要开始,请将 auth.basic 中间件附加到路由。auth.basic 中间件包含在 Laravel 框架中,因此您无需定义它:

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

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

关于 FastCGI 的说明

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

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

无状态 HTTP Basic 认证

您也可以使用 HTTP Basic 认证而不在会话中设置用户标识符 cookie,这对于 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
{
    /**
     * 处理传入请求。
     */
    public function handle(Request $request, Closure $next): Response
    {
        return Auth::onceBasic() ?: $next($request);
    }

}

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

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

注销

要手动将用户注销应用程序,可以使用 Auth 门面上的 logout 方法。这将从用户的会话中删除身份验证信息,以便后续请求不会被认证:

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 中间件存在于您的 app/Http/Kernel.php 文件的 web 中间件组中,或者将其放置在路由定义中:

php
// 在您的应用程序服务提供者中...
use Illuminate\Session\Middleware\AuthenticateSession;

->withMiddleware(function (Middleware $middleware): void {
    $middleware->web(append: [
        AuthenticateSession::class,
    ]);
})

或者,您可以将中间件放在特定路由上:

php
use Illuminate\Session\Middleware\AuthenticateSession;

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

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

php
use Illuminate\Support\Facades\Auth;

Auth::logoutOtherDevices($currentPassword);

调用 logoutOtherDevices 方法时,用户的其他会话将完全失效,这意味着他们将被迫再次从所有其他设备登录。

密码确认

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

NOTE

以下文档讨论如何包含 Laravel 的密码确认功能;但是,如果您想更快开始,Laravel 应用程序入门套件包含此功能!

配置

确认密码后,用户在指定的时间内不会被要求再次确认密码。您可以使用 auth.password_timeout 配置选项(包含在 config/auth.php 配置文件中)定义用户在重新提示之前必须等待的时间。

路由

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

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

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

php
use Illuminate\Http\Request;
use Illuminate\Http\RedirectResponse;
use Illuminate\Support\Facades\Hash;
use Illuminate\Support\Facades\Redirect;

Route::post('/confirm-password', function (Request $request): RedirectResponse {
    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 命名路由

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

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

添加自定义守卫

您可以使用 Auth 门面上的 extend 方法定义自己的身份验证守卫。您应该在服务提供者中调用此方法。我们将此调用放在 App\Providers\AppServiceProviderboot 方法中:

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 register(): void
    {
        // ...
    }

    /**
     * 引导任何应用程序服务。
     */
    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 实例需要包含一些必要的方法。实现自定义守卫后,可以在 auth.php 配置文件的 guards 配置中引用该守卫:

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

闭包请求守卫

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

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

php
<?php

namespace App\Providers;

use Illuminate\Http\Request;
use Illuminate\Support\Facades\Auth;
use Illuminate\Support\ServiceProvider;

class AppServiceProvider extends ServiceProvider
{
    /**
     * 注册任何应用程序服务。
     */
    public function register(): void
    {
        // ...
    }

    /**
     * 引导任何应用程序服务。
     */
    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',
    ],
],

添加自定义用户提供者

如果您不是使用传统的关系数据库来存储用户,则需要使用自定义用户提供者扩展 Laravel。我们将使用 Auth 门面上的 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 register(): void
    {
        // ...
    }

    /**
     * 引导任何应用程序服务。
     */
    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' => 'database',
    ],

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

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

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

用户提供者契约

Illuminate\Contracts\Auth\UserProvider 实现负责从持久化存储系统(如 MySQL、MongoDB 等)中获取 Illuminate\Contracts\Auth\Authenticatable 实现。无论这些用户如何存储或表示什么类型的类,这两个接口都允许 Laravel 身份验证机制继续运行:

  • retrieveById 方法接受一个 $identifier,通常是一个整数 ID,并返回一个 Authenticatable 实现。
  • retrieveByToken 方法接受一个唯一的 $identifier 和一个 $token,并返回一个 Authenticatable 实现。$token 通常存储在数据库列中,如 remember_token,用于持久化"记住我"会话的用户。
  • updateRememberToken 方法使用新的 $token 更新 $userremember_token。新令牌可以是全新的令牌(用于成功的"记住我"登录尝试),也可以是 null(当用户注销时)。
  • retrieveByCredentials 方法接受传递给 Auth::attempt 方法的凭据数组,并返回一个 Authenticatable 实现。此方法不应尝试进行任何密码验证或身份验证。
  • validateCredentials 方法将给定的 $user$credentials 进行比较以对用户进行身份验证。例如,此方法通常使用 Hash::check 比较 $user->getAuthPassword()$credentials['password']。此方法应返回 truefalse,指示密码是否有效。

可认证契约

Illuminate\Contracts\Auth\Authenticatable 接口定义了 Laravel 中任何用户对象类都应实现的方法。此接口允许身份验证系统与任何"用户"类一起工作,无论使用什么 ORM 或存储抽象层。该接口定义了以下方法:

  • getAuthIdentifierName 方法返回用户主键列的名称。
  • getAuthIdentifier 方法返回用户的主键。在 MySQL 后端,这通常是一个自动递增的整数。
  • getAuthPassword 方法返回用户的哈希密码。
  • getRememberToken 方法返回用于"记住我"会话的令牌。
  • setRememberToken 方法将用户的 remember_token 设置为新值。
  • getRememberTokenName 方法返回用户"记住我"令牌列的名称。

NOTE

app/Models/User.php 中包含的 App\Models\User 类已经实现了此接口。

自动密码重哈希

Laravel 的默认密码哈希算法是 bcrypt。bcrypt 哈希的"工作因子"是可以调整的,以增加哈希的计算成本,从而提高哈希的安全性。随着时间的推移,硬件处理能力的提高,增加 bcrypt 工作因子可以使您的应用程序哈希保持安全。

当用户使用 bcrypt 或 Argon2 哈希登录您的应用程序时,Laravel 会自动检查哈希的工作因子是否低于您应用程序配置的工作因子。如果是,Laravel 将自动使用新工作因子重新哈希密码,并将新哈希存储在数据库中。

此功能不会影响您的应用程序代码,但如果您想自定义此行为,可以在应用程序的 AppServiceProvider 中调用 rehashOnLogin 方法:

php
use Illuminate\Support\Facades\Auth;

/**
 * 引导任何应用程序服务。
 */
public function boot(): void
{
    Auth::rehashOnLogin(function ($user, $password, $algorithm) {
        // 确定是否应该重新哈希密码...
    });
}

闭包应返回 truefalse,指示是否应该重新哈希密码。如果闭包返回 true,Laravel 将使用配置的工作因子重新哈希密码。

事件

Laravel 在身份验证过程中触发各种事件。您可以在 App\Providers\EventServiceProvider 中为这些事件附加监听器:

  • 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