简介
许多 Web 应用程序都为用户提供了向应用程序进行身份验证并“登录”的方式。在 Web 应用程序中实现此功能可能是一项复杂且存在潜在风险的工作。因此,Laravel 致力于为你提供所需工具,助你快速、安全且轻松地实现身份验证。
从本质上讲,Laravel 的身份验证机制由“守卫(guards)”和“提供者(providers)”组成。守卫定义了针对每个请求对用户进行身份验证的方式。例如,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,可以使用基于 Laravel 查询构造器的 database 身份验证提供者。若应用程序使用 MongoDB,请查看 MongoDB 官方提供的 Laravel 用户身份验证文档。
在为 App\Models\User 模型构建数据库架构时,确保密码列的长度至少为 60 个字符。当然,新创建的 Laravel 应用程序中自带的 users 表迁移文件已创建了长度超过此限制的列。
此外,你还应验证你的 users(或等效)表中是否包含一个可为空、长度为 100 个字符的字符串类型 remember_token 列。当用户在登录应用程序时勾选“记住我”选项,此列将用于存储对应的令牌。同样,新创建的 Laravel 应用程序中自带的默认 users 表迁移文件已包含此列。
生态系统概览
Laravel 提供了几个与身份验证相关的包。在继续之前,我们将回顾 Laravel 中身份验证生态系统的总体情况,并讨论每个包的预期用途。
首先,了解一下身份验证的工作原理。当使用网页浏览器时,用户会通过登录表单提供其用户名和密码。如果凭据正确,应用程序会将已认证用户的相关信息存储在该用户的 会话(session)中。向浏览器发出的 Cookie 包含会话 ID,以便后续对应用程序的请求能将用户与正确的会话关联起来。收到会话 Cookie 后,应用程序会根据会话 ID 检索会话数据,注意到身份验证信息已存储在会话中,并将该用户视为“已认证”。
当远程服务需要通过身份验证来访问 API 时,由于没有网页浏览器,通常不会使用 Cookie 进行身份验证。相反,远程服务会在每次请求时向 API 发送一个 API 令牌。应用程序可以将传入的令牌与有效 API 令牌表进行验证,并将该请求“认证”为由与该 API 令牌关联的用户所发起。
Laravel 内置的浏览器身份验证服务
Laravel 包含内置的身份验证与会话服务,通常通过 Auth 和 Session 门面(Facade)来访问。这些功能为从网页浏览器发起的请求提供基于 Cookie 的身份验证。它们提供了用于验证用户凭据并对用户进行身份验证的方法。此外,这些服务会自动将正确的身份验证数据存储到用户的会话中,并向用户发送会话 Cookie。如何使用这些服务的说明包含在本文档中。
应用程序入门套件
正如本文档所述,你可以手动与这些身份验证服务交互,来构建应用程序自身的身份验证层。不过,为了帮助你更快上手,我们发布了免费的入门套件,可为整个身份验证层提供稳健且现代化的脚手架。
Laravel 的 API 身份验证服务
Laravel 提供了两个可选包,可协助你管理 API 令牌,并对使用 API 令牌发起的请求进行身份验证:Passport 和 Sanctum。请注意,这些库与 Laravel 内置的基于 Cookie 的身份验证库并非互斥。这些库主要侧重于 API 令牌身份验证,而内置的身份验证服务侧重于基于 Cookie 的浏览器身份验证。许多应用程序会同时使用 Laravel 内置的基于 Cookie 的身份验证服务以及 Laravel 的一个 API 身份验证包。
Passport
Passport 是一个 OAuth2 身份验证提供者,提供多种 OAuth2“授权类型(grant types)”,允许你签发不同类型的令牌。总体而言,这是一个功能强大但较为复杂的 API 身份验证包。然而,大多数应用程序不需要 OAuth2 规范所提供的复杂功能,这些功能对用户和开发者来说可能会造成困惑。此外,历史上,开发者一直对如何使用像 Passport 这样的 OAuth2 身份验证提供者来验证单页应用程序(SPA)或移动应用程序感到困惑。
Sanctum
针对 OAuth2 的复杂性以及开发者的困惑,我们着手构建了一个更简单、更精简的身份验证包,以同时处理来自网页浏览器的一方 Web 请求和通过令牌发起的 API 请求。随着 Laravel Sanctum 的发布,这一目标得以实现。对于在提供 API 的同时还将提供一方 Web 界面的应用程序,或是由独立于后端 Laravel 应用程序的单页应用程序(SPA)驱动的应用程序,或是提供移动客户端的应用程序,Sanctum 应被视为首选且推荐的身份验证包。
Laravel Sanctum 是一个混合的 Web/API 身份验证包,可以管理应用程序的整个身份验证过程。这是因为当基于 Sanctum 的应用程序接收到请求时,Sanctum 会先判断该请求是否包含指向已认证会话的会话 Cookie。Sanctum 通过调用我们之前讨论过的 Laravel 内置身份验证服务来实现这一点。如果请求不是通过会话 Cookie 进行身份验证,Sanctum 会检查请求中是否包含 API 令牌。如果存在 API 令牌,Sanctum 将使用该令牌对请求进行身份验证。若想进一步了解此过程,请参阅 Sanctum 的“工作原理”文档。
总结与技术栈选择
综上所述,如果应用程序是通过浏览器访问,并且你正在构建一个单体 Laravel 应用程序,那么应用程序将使用 Laravel 内置的身份验证服务。
其次,如果你的应用程序提供了一个供第三方调用的 API,你需要在 Passport 或 Sanctum 之间进行选择,以实现应用程序的 API 令牌身份验证。一般而言,Sanctum 应作为首选,因为它是针对 API 身份验证、SPA 身份验证以及移动身份验证的简单且完整的解决方案,还支持“作用域(scopes)”或“能力(abilities)”。
如果你要构建以 Laravel 后端为驱动的单页应用(SPA),应使用 Laravel Sanctum。使用 Sanctum 时,你要么需要手动实现自己的后端认证路由,要么使用 Laravel Fortify 作为无头认证后端服务,它会为注册、密码重置、邮箱验证等功能提供路由和控制器。
当你的应用确实需要 OAuth2 规范提供的全部功能时,可以选择 Passport。此外,如果你要构建一个供 AI 客户端访问的 MCP 服务器,应使用 Passport,因为 MCP 客户端通常期望通过 使用 OAuth 进行认证。
如果你想快速上手,我们很高兴推荐 我们的应用入门套件,这是快速启动一个新的 Laravel 应用的方式,该应用已经采用我们首选的认证技术栈——Laravel 内置的认证服务。
认证快速入门
本文档这一部分介绍通过 Laravel 应用入门套件对用户进行认证的方法,其中包含界面脚手架,可帮助你快速上手。如果你想直接集成 Laravel 的认证系统,请查阅关于手动认证用户的文档。
安装入门套件
首先,你应该 安装一个 Laravel 应用入门套件。我们的入门套件为你的新 Laravel 应用集成认证功能提供了设计精美的起点。
获取已认证用户
使用入门套件创建应用并允许用户注册、认证之后,你通常需要与当前已认证的用户进行交互。在处理传入请求时,你可以通过 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()) {
// 用户已登录……
} 尽管可以使用 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): void {
$middleware->redirectGuestsTo('/login');
// 使用闭包……
$middleware->redirectGuestsTo(fn (Request $request) => route('login'));
})重定向已认证用户
当 guest 中间件检测到已认证用户时,会将用户重定向到 dashboard 或 home 命名路由。你可以在应用的 bootstrap/app.php 文件中,使用 redirectUsersTo 方法修改此行为:
use Illuminate\Http\Request;
->withMiddleware(function (Middleware $middleware): void {
$middleware->redirectUsersTo('/panel');
// 使用闭包……
$middleware->redirectUsersTo(fn (Request $request) => route('panel'));
})指定守卫
将 auth 中间件附加到路由时,你也可以指定应使用哪个“守卫”来认证用户。指定的守卫应与你的 auth.php 配置文件中 guards 数组的一个键对应:
Route::get('/flights', function () {
// 仅已认证用户可访问此路由……
})->middleware('auth:admin');登录限流
如果你使用的是我们的 应用入门套件之一,登录尝试会自动应用速率限制。默认情况下,用户在多次尝试仍未提供正确凭据时,一分钟内无法登录。限流针对用户的用户名 / 邮箱地址以及其 IP 地址单独生效。
如果你想为应用中的其他路由设置速率限制,请查阅 速率限制文档。
手动认证用户
你不必使用 Laravel 的 应用入门套件中包含的认证脚手架。如果你选择不使用该脚手架,就需要直接使用 Laravel 认证类来管理用户认证。别担心,这很简单!
我们将通过 Auth 门面访问 Laravel 的认证服务,因此需要在类顶部导入 Auth 门面。接下来,我们看看 attempt 方法。attempt 方法通常用于处理应用“登录”表单发起的认证尝试。若认证成功,应重新生成用户的 会话,以防止 会话固定攻击:
<?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 的身份验证服务会根据身份验证守卫(authentication guard)的 “provider” 配置,从你的数据库中检索用户。在默认的 config/auth.php 配置文件中,指定了 Eloquent 用户提供器,并指示其在检索用户时使用 App\Models\User 模型。你可以根据应用程序的需求,在配置文件中更改这些值。
attempt 方法在身份验证成功时会返回 true。否则,将返回 false。
Laravel 重定向器提供的 intended 方法会将用户重定向到他们在被身份验证中间件拦截之前试图访问的 URL。若目标地址不可用,可为此方法提供一个备用 URI。
指定额外条件
如果需要,除了用户的电子邮件和密码之外,你还可以为身份验证查询添加额外的查询条件。要实现这一点,只需将查询条件添加到传递给 attempt 方法的数组中即可。例如,我们可以验证该用户是否被标记为 “已激活”:
if (Auth::attempt(['email' => $email, 'password' => $password, 'active' => 1])) {
// Authentication was successful...
}对于复杂的查询条件,你可以在凭据数组中提供一个闭包。该闭包将使用查询实例进行调用,让你能够根据应用程序的需求自定义查询:
use Illuminate\Database\Eloquent\Builder;
if (Auth::attempt([
'email' => $email,
'password' => $password,
fn (Builder $query) => $query->has('activeSubscription'),
])) {
// Authentication was successful...
} 在这些示例中,email 并非必选参数,仅作为示例使用。你应该使用数据库表中与 “用户名” 对应的任意列名。
attemptWhen 方法以闭包作为其第二个参数,可用于在实际验证用户之前对潜在用户进行更全面的检查。该闭包接收潜在用户,应返回 true 或 false,以表示是否允许该用户通过验证:
if (Auth::attemptWhen([
'email' => $email,
'password' => $password,
], function (User $user) {
return $user->isNotBanned();
})) {
// Authentication was successful...
}访问特定的守卫实例
通过 Auth 门面(facade)的 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)) {
// The user is being remembered...
}如果你的应用程序提供“记住我”功能,可以使用 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,也不会触发 Login 事件:
if (Auth::once($credentials)) {
// ...
}HTTP 基础认证
HTTP 基础认证提供了一种无需设置专门的“登录”页面即可快速验证应用程序用户的方法。要开始使用,请将 auth.basic 中间件附加到路由。auth.basic 中间件已包含在 Laravel 框架中,因此你无需自行定义:
Route::get('/profile', function () {
// Only authenticated users may access this route...
})->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
{
/**
* Handle an incoming request.
*
* @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 () {
// Only authenticated users may access this route...
})->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 方法被调用时,用户的其他会话将被完全失效,这意味着他们将从之前已认证的所有守卫(guard)中“登出”。
密码确认
在开发应用时,你偶尔会遇到需要在执行操作前,或在用户被重定向到应用的敏感区域之前,要求用户确认密码的情况。Laravel 内置了相应的中间件,可使该流程变得轻而易举。要实现此功能,你需要定义两个路由:一个路由用于显示要求用户确认密码的视图,另一个路由用于验证密码是否有效,并将用户重定向到其目标位置。
以下文档将介绍如何直接集成 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;
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) {
// Return an instance of 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 方法。该方法允许你通过单个闭包快速定义认证流程。
开始时,在应用 AppServiceProvider 的 boot 方法中调用 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 方法用于将 $user 实例的 remember_token 更新为新的 $token。在“记住我”认证成功时,或用户注销时,都会为用户分配一个新的令牌。
retrieveByCredentials 方法接收尝试登录应用时传递给 Auth::attempt 方法的凭据数组。然后,该方法应在底层的持久化存储中“查询”匹配这些凭据的用户。通常,此方法会执行带有“where”条件的查询,以搜索“username”与 $credentials['username'] 的值匹配的用户记录。该方法应返回 Authenticatable 的实现。此方法不应尝试进行任何密码验证或身份认证。
validateCredentials 方法应比较给定的 $user 与 $credentials,从而对用户进行身份认证。例如,该方法通常会使用 Hash::check 方法来比较 $user->getAuthPassword() 的值与 $credentials['password'] 的值。该方法应返回 true 或 false,表示密码是否有效。
rehashPasswordIfRequired 方法应在需要且受支持的情况下,对给定 $user 的密码进行重新哈希处理。例如,该方法通常会使用 Hash::needsRehash 方法来判断 $credentials['password'] 的值是否需要重新哈希。如果需要重新哈希密码,该方法应使用 Hash::make 方法对密码进行重新哈希,并更新底层持久化存储中的用户记录。
Authenticatable 契约
在了解了 UserProvider 上的各个方法后,我们来看看 Authenticatable 契约。请记住,用户提供程序应从 retrieveById、retrieveByToken 和 retrieveByCredentials 方法中返回此接口的实现:
<?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 的入门套件登录您的应用程序,或通过 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 |
Illuminate\Auth\Events\PasswordResetLinkSent |