跳到内容

身份验证

简介

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

在其核心,Laravel 的身份验证工具由“守卫”和“提供器”组成。守卫定义了如何为每个请求验证用户身份。例如,Laravel 附带了一个 session 守卫,该守卫使用会话存储和 Cookie 来维护状态。

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

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

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

入门套件

想要快速入门?在新的 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(或等效)表是否包含一个可为空的、100 个字符的字符串 remember_token 列。此列将用于存储用户在登录应用程序时选择“记住我”选项的令牌。同样,新的 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 规范提供的复杂功能,这可能会让用户和开发人员感到困惑。此外,开发人员历来对如何使用 OAuth2 身份验证提供程序(如 Passport)验证 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 内置身份验证服务身份验证堆栈。

身份验证快速入门

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

安装入门套件

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

检索已身份验证的用户

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

1use Illuminate\Support\Facades\Auth;
2 
3// Retrieve the currently authenticated user...
4$user = Auth::user();
5 
6// Retrieve the currently authenticated user's ID...
7$id = Auth::id();

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

1<?php
2 
3namespace App\Http\Controllers;
4 
5use Illuminate\Http\RedirectResponse;
6use Illuminate\Http\Request;
7 
8class FlightController extends Controller
9{
10 /**
11 * Update the flight information for an existing flight.
12 */
13 public function update(Request $request): RedirectResponse
14 {
15 $user = $request->user();
16 
17 // ...
18 
19 return redirect('/flights');
20 }
21}

确定当前用户是否已身份验证

要确定发出传入 HTTP 请求的用户是否已通过身份验证,您可以使用 Auth 外观上的 check 方法。如果用户已通过身份验证,此方法将返回 true

1use Illuminate\Support\Facades\Auth;
2 
3if (Auth::check()) {
4 // The user is logged in...
5}

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

保护路由

路由中间件 可用于仅允许已身份验证的用户访问给定的路由。Laravel 附带了一个 auth 中间件,它是 Illuminate\Auth\Middleware\Authenticate 类的 中间件别名。由于此中间件已在 Laravel 内部别名化,因此您只需将中间件附加到路由定义即可

1Route::get('/flights', function () {
2 // Only authenticated users may access this route...
3})->middleware('auth');

重定向未身份验证的用户

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

1use Illuminate\Http\Request;
2 
3->withMiddleware(function (Middleware $middleware) {
4 $middleware->redirectGuestsTo('/login');
5 
6 // Using a closure...
7 $middleware->redirectGuestsTo(fn (Request $request) => route('login'));
8})

指定守卫

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

1Route::get('/flights', function () {
2 // Only authenticated users may access this route...
3})->middleware('auth:admin');

登录限流

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

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

手动验证用户身份

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

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

1<?php
2 
3namespace App\Http\Controllers;
4 
5use Illuminate\Http\Request;
6use Illuminate\Http\RedirectResponse;
7use Illuminate\Support\Facades\Auth;
8 
9class LoginController extends Controller
10{
11 /**
12 * Handle an authentication attempt.
13 */
14 public function authenticate(Request $request): RedirectResponse
15 {
16 $credentials = $request->validate([
17 'email' => ['required', 'email'],
18 'password' => ['required'],
19 ]);
20 
21 if (Auth::attempt($credentials)) {
22 $request->session()->regenerate();
23 
24 return redirect()->intended('dashboard');
25 }
26 
27 return back()->withErrors([
28 'email' => 'The provided credentials do not match our records.',
29 ])->onlyInput('email');
30 }
31}

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

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

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

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

指定其他条件

如果您愿意,除了用户的邮箱和密码之外,您还可以向身份验证查询添加额外的查询条件。要实现此目的,我们可以简单地将查询条件添加到传递给 attempt 方法的数组中。例如,我们可以验证用户是否标记为“active”

1if (Auth::attempt(['email' => $email, 'password' => $password, 'active' => 1])) {
2 // Authentication was successful...
3}

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

1use Illuminate\Database\Eloquent\Builder;
2 
3if (Auth::attempt([
4 'email' => $email,
5 'password' => $password,
6 fn (Builder $query) => $query->has('activeSubscription'),
7])) {
8 // Authentication was successful...
9}

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

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

1if (Auth::attemptWhen([
2 'email' => $email,
3 'password' => $password,
4], function (User $user) {
5 return $user->isNotBanned();
6})) {
7 // Authentication was successful...
8}

访问特定的守卫实例

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

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

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

记住用户

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

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

1use Illuminate\Support\Facades\Auth;
2 
3if (Auth::attempt(['email' => $email, 'password' => $password], $remember)) {
4 // The user is being remembered...
5}

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

1use Illuminate\Support\Facades\Auth;
2 
3if (Auth::viaRemember()) {
4 // ...
5}

其他身份验证方法

验证用户实例的身份

如果您需要将现有用户实例设置为当前已身份验证的用户,可以将用户实例传递给 Auth 外观的 login 方法。给定的用户实例必须是 Illuminate\Contracts\Auth\Authenticatable 契约 的实现。Laravel 附带的 App\Models\User 模型已实现此接口。当您已经有有效的用户实例时,例如在用户向您的应用程序注册后立即,此身份验证方法非常有用

1use Illuminate\Support\Facades\Auth;
2 
3Auth::login($user);

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

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

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

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

通过 ID 验证用户身份

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

1Auth::loginUsingId(1);

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

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

一次性验证用户身份

您可以使用 once 方法为单个请求验证应用程序用户的身份。调用此方法时,不会使用会话或 Cookie

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

HTTP Basic Authentication

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

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

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

关于 FastCGI 的说明

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

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

无状态 HTTP Basic Authentication

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

1<?php
2 
3namespace App\Http\Middleware;
4 
5use Closure;
6use Illuminate\Http\Request;
7use Illuminate\Support\Facades\Auth;
8use Symfony\Component\HttpFoundation\Response;
9 
10class AuthenticateOnceWithBasicAuth
11{
12 /**
13 * Handle an incoming request.
14 *
15 * @param \Closure(\Illuminate\Http\Request): (\Symfony\Component\HttpFoundation\Response) $next
16 */
17 public function handle(Request $request, Closure $next): Response
18 {
19 return Auth::onceBasic() ?: $next($request);
20 }
21 
22}

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

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

注销

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

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

1use Illuminate\Http\Request;
2use Illuminate\Http\RedirectResponse;
3use Illuminate\Support\Facades\Auth;
4 
5/**
6 * Log the user out of the application.
7 */
8public function logout(Request $request): RedirectResponse
9{
10 Auth::logout();
11 
12 $request->session()->invalidate();
13 
14 $request->session()->regenerateToken();
15 
16 return redirect('/');
17}

使其他设备上的会话失效

Laravel 还提供了一种机制,用于使在其他设备上处于活动状态的用户会话失效并“注销”,而不会使当前设备上的会话失效。此功能通常在用户更改或更新密码时使用,您可能希望使其他设备上的会话失效,同时保持当前设备的身份验证状态。

在开始之前,您应该确保应接收会话身份验证的路由上包含 Illuminate\Session\Middleware\AuthenticateSession 中间件。通常,您应该将此中间件放在路由组定义中,以便将其应用于您应用程序的大多数路由。默认情况下,可以使用 auth.session 中间件别名AuthenticateSession 中间件附加到路由。

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

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

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

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

密码确认

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

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

配置

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

路由

密码确认表单

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

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

正如您可能期望的那样,此路由返回的视图应具有一个包含 password 字段的表单。此外,您可以随意在视图中包含文本,解释用户正在进入应用程序的受保护区域,并且必须确认其密码。

确认密码

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

1use Illuminate\Http\Request;
2use Illuminate\Support\Facades\Hash;
3use Illuminate\Support\Facades\Redirect;
4 
5Route::post('/confirm-password', function (Request $request) {
6 if (! Hash::check($request->password, $request->user()->password)) {
7 return back()->withErrors([
8 'password' => ['The provided password does not match our records.']
9 ]);
10 }
11 
12 $request->session()->passwordConfirmed();
13 
14 return redirect()->intended();
15})->middleware(['auth', 'throttle:6,1']);

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

保护路由

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

1Route::get('/settings', function () {
2 // ...
3})->middleware(['password.confirm']);
4 
5Route::post('/settings', function () {
6 // ...
7})->middleware(['password.confirm']);

添加自定义守卫

您可以使用 Auth facade 上的 extend 方法定义自己的身份验证守卫。您应该将对 extend 方法的调用放在 服务提供者 中。由于 Laravel 已经附带了 AppServiceProvider,我们可以将代码放在该提供者中。

1<?php
2 
3namespace App\Providers;
4 
5use App\Services\Auth\JwtGuard;
6use Illuminate\Contracts\Foundation\Application;
7use Illuminate\Support\Facades\Auth;
8use Illuminate\Support\ServiceProvider;
9 
10class AppServiceProvider extends ServiceProvider
11{
12 // ...
13 
14 /**
15 * Bootstrap any application services.
16 */
17 public function boot(): void
18 {
19 Auth::extend('jwt', function (Application $app, string $name, array $config) {
20 // Return an instance of Illuminate\Contracts\Auth\Guard...
21 
22 return new JwtGuard(Auth::createUserProvider($config['provider']));
23 });
24 }
25}

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

1'guards' => [
2 'api' => [
3 'driver' => 'jwt',
4 'provider' => 'users',
5 ],
6],

Closure Request Guards

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

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

1use App\Models\User;
2use Illuminate\Http\Request;
3use Illuminate\Support\Facades\Auth;
4 
5/**
6 * Bootstrap any application services.
7 */
8public function boot(): void
9{
10 Auth::viaRequest('custom-token', function (Request $request) {
11 return User::where('token', (string) $request->token)->first();
12 });
13}

定义自定义身份验证驱动程序后,您可以在 auth.php 配置文件的 guards 配置中将其配置为驱动程序。

1'guards' => [
2 'api' => [
3 'driver' => 'custom-token',
4 ],
5],

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

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

添加自定义用户提供器

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

1<?php
2 
3namespace App\Providers;
4 
5use App\Extensions\MongoUserProvider;
6use Illuminate\Contracts\Foundation\Application;
7use Illuminate\Support\Facades\Auth;
8use Illuminate\Support\ServiceProvider;
9 
10class AppServiceProvider extends ServiceProvider
11{
12 // ...
13 
14 /**
15 * Bootstrap any application services.
16 */
17 public function boot(): void
18 {
19 Auth::provider('mongo', function (Application $app, array $config) {
20 // Return an instance of Illuminate\Contracts\Auth\UserProvider...
21 
22 return new MongoUserProvider($app->make('mongo.connection'));
23 });
24 }
25}

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

1'providers' => [
2 'users' => [
3 'driver' => 'mongo',
4 ],
5],

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

1'guards' => [
2 'web' => [
3 'driver' => 'session',
4 'provider' => 'users',
5 ],
6],

The User Provider Contract

Illuminate\Contracts\Auth\UserProvider 实现负责从持久存储系统(例如 MySQL、MongoDB 等)中获取 Illuminate\Contracts\Auth\Authenticatable 实现。这两个接口允许 Laravel 身份验证机制继续运行,而无需考虑用户数据的存储方式或用于表示经过身份验证的用户的类类型。

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

1<?php
2 
3namespace Illuminate\Contracts\Auth;
4 
5interface UserProvider
6{
7 public function retrieveById($identifier);
8 public function retrieveByToken($identifier, $token);
9 public function updateRememberToken(Authenticatable $user, $token);
10 public function retrieveByCredentials(array $credentials);
11 public function validateCredentials(Authenticatable $user, array $credentials);
12 public function rehashPasswordIfRequired(Authenticatable $user, array $credentials, bool $force = false);
13}

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'] 的值进行比较。此方法应返回 truefalse,指示密码是否有效。

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

The Authenticatable Contract

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

1<?php
2 
3namespace Illuminate\Contracts\Auth;
4 
5interface Authenticatable
6{
7 public function getAuthIdentifierName();
8 public function getAuthIdentifier();
9 public function getAuthPasswordName();
10 public function getAuthPassword();
11 public function getRememberToken();
12 public function setRememberToken($value);
13 public function getRememberTokenName();
14}

此接口很简单。getAuthIdentifierName 方法应返回用户的“主键”列的名称,getAuthIdentifier 方法应返回用户的“主键”。当使用 MySQL 后端时,这可能是分配给用户记录的自动递增主键。getAuthPasswordName 方法应返回用户的密码列的名称。getAuthPassword 方法应返回用户的哈希密码。

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

自动密码重新哈希

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

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

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

1php artisan config:publish hashing

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

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

Laravel 是构建、部署和监控软件的最有效方式。
构建、部署和监控软件。