认证
简介
许多 Web 应用会为用户提供一种方式,让他们能够在系统中进行身份验证并 “登录”。在应用中实现这一功能可能复杂且具有潜在风险。为此,Laravel 致力于为你提供快速、安全、简单地实现身份验证所需的各种工具。
从本质上说,Laravel 的身份验证机制由 “guards(守卫)” 和 “providers(提供者)” 组成。Guard 定义了每个请求的用户应如何被认证。例如,Laravel 内置了一个基于会话的 guard,它使用会话存储和 cookies 来维护状态。
Provider 定义了如何从持久化存储中检索用户。Laravel 默认提供了基于 Eloquent 和 数据库查询构建器 的用户检索方式。当然,你也可以根据应用需求自定义更多 provider。
应用的身份验证配置文件位于 config/auth.php,其中包含大量文档化良好的选项,可用于调整 Laravel 身份验证服务的行为。
入门套件
想要快速上手?在一个全新的 Laravel 应用中安装一个 Laravel 入门套件 吧。迁移数据库后,在浏览器中访问 /register 或应用中分配的任意相关 URL,入门套件会自动为你生成完整的认证系统!
即使你不打算在最终的 Laravel 应用中使用入门套件,安装一次 入门套件 仍然是一个极佳的学习机会。你可以通过一个真实的 Laravel 项目来了解如何实现完整的认证功能。由于 Laravel 入门套件已经为你准备好了认证控制器、路由和视图,你可以直接阅读这些文件中的代码,学习 Laravel 的认证机制是如何实现的。
数据库注意事项
默认情况下,Laravel 会在 app/Models 目录中为你生成一个 App\Models\User 的 Eloquent 模型。该模型可直接用于默认的 Eloquent 身份验证驱动。
如果你的应用不使用 Eloquent,你也可以使用 基于 Laravel 查询构建器的数据库身份验证提供者(database authentication provider)。如果你的应用使用 MongoDB,请参考 MongoDB 官方的 Laravel 用户认证文档。
在为 App\Models\User 模型构建数据库结构时,请确保 password 字段长度至少为 60 个字符。当然,新建的 Laravel 应用中内置的用户表迁移文件已经为你创建了一个超过该长度的字段。
此外,你还应确保用户表(users 或相应的表)包含一个 可为空的、长度为 100 字符的字符串字段 remember_token。该字段用于存储用户在登录时勾选 “记住我” 选项后所对应的令牌。同样,Laravel 默认生成的用户表迁移文件已经包含该字段。
生态系统概览
Laravel 提供了多个与身份验证相关的包。在继续之前,我们先回顾一下 Laravel 中的整体身份验证生态系统,并介绍每个包的用途。
首先,理解身份验证的工作原理。在使用网页浏览器时,用户会通过登录表单提供用户名和密码。如果这些凭证正确,应用会将用户的认证信息存储在用户的 会话 中。浏览器收到的 cookie 会包含会话 ID,以便后续请求能够将用户与正确的会话关联起来。收到会话 cookie 后,应用会根据会话 ID 获取会话数据,识别出认证信息已存储,并将用户视为“已认证”。
当远程服务需要进行身份验证以访问 API 时,通常不会使用 cookie,因为没有浏览器环境。相反,远程服务会在每次请求时向 API 发送一个 API 令牌。应用可以将该令牌与有效的 API 令牌表进行校验,并将请求“认证”为与该 API 令牌关联的用户所发起的操作。
Laravel 内置的浏览器身份验证服务
Laravel 内置了身份验证和会话服务,通常通过 Auth 和 Session 门面访问。这些功能为来自浏览器的请求提供基于 cookie 的身份验证。它们提供了验证用户凭证和认证用户的方法,同时会自动将认证数据存储到用户会话中,并发送用户会话 cookie。有关如何使用这些服务的详细说明,请参考官方文档。
应用入门套件
正如本篇文档所述,你可以手动操作这些身份验证服务来构建自己的认证层。不过,为了帮助你更快上手,Laravel 提供了 免费的入门套件,它们可以为整个认证层生成现代化、完善的脚手架。
Laravel 的 API 身份验证服务
Laravel 提供了两个可选包,用于管理 API 令牌并对使用 API 令牌的请求进行认证:Passport 和 Sanctum。请注意,这些库与 Laravel 内置的基于 cookie 的身份验证服务并不互斥。前者主要用于 API 令牌认证,而内置服务则专注于基于浏览器的 cookie 认证。很多应用会同时使用这两类服务。
Passport
Passport 是一个 OAuth2 认证提供者,支持多种 OAuth2 授权类型(grant types),可发行不同类型的令牌。总体来说,它是一个功能强大但相对复杂的 API 认证包。不过,大多数应用并不需要 OAuth2 规范提供的复杂功能,这对用户和开发者都可能造成困惑。同时,开发者在使用 Passport 或类似 OAuth2 认证提供者对 SPA 或移动应用进行认证时,历史上也常常感到困惑。
Sanctum
针对 OAuth2 的复杂性及开发者的困惑,Laravel 推出了一个更简单、精简的认证包——Laravel Sanctum,它既能处理来自浏览器的第一方 Web 请求,也能处理通过令牌发起的 API 请求。对于既提供 Web UI 又提供 API 的应用,或使用独立 SPA 前端的应用,或提供移动客户端的应用,Sanctum 是首选且推荐的认证方案。
Sanctum 是一个 Web/API 混合认证包,能够管理应用的完整认证流程。当 Sanctum 接收到请求时,它会首先判断请求是否包含引用已认证会话的 session cookie。Sanctum 通过调用前面介绍的 Laravel 内置认证服务来实现这一功能。如果请求未通过 session cookie 认证,Sanctum 会检查请求中是否包含 API 令牌。如果存在 API 令牌,Sanctum 会使用该令牌对请求进行认证。更多详细内容,请参考 Sanctum 的官方 “工作原理” 文档。
总结与栈选择
总的来说,如果你的应用主要通过浏览器访问,并且是一个单体 Laravel 应用,你可以直接使用 Laravel 内置的认证服务。
如果你的应用提供 API 给第三方使用,则需要选择 Passport 或 Sanctum 来实现 API 令牌认证。一般情况下,优先推荐 Sanctum,因为它提供了简单、完整的解决方案,可支持 API 认证、SPA 认证和移动端认证,并支持 “作用域(scopes)” 或 “能力(abilities)”。
如果你正在构建由 Laravel 后端驱动的 SPA,应使用 Laravel Sanctum。在使用 Sanctum 时,你可以选择 手动实现后台认证路由,或者使用 Laravel Fortify 作为无界面(headless)认证服务,它提供注册、密码重置、邮箱验证等功能的路由和控制器。
当应用确实需要 OAuth2 规范提供的所有功能时,可以选择 Passport。
如果希望快速上手,推荐使用 应用入门套件,它可以帮助你快速启动一个新 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');
}
}
判断当前用户是否已认证
要判断发起请求的用户是否已认证,可以使用 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'));
})
指定 Guard
在将 auth 中间件 附加到路由时,你还可以指定用于认证用户的 guard。指定的 guard 应对应于 auth.php 配置文件中 guards 数组的某个键:
Route::get('/flights', function () {
// 只有已认证用户可以访问此路由...
})->middleware('auth:admin');
登录限流
如果你使用的是我们的 应用入门套件,登录尝试将会自动应用 速率限制。默认情况下,如果用户在多次尝试后未能提供正确的凭证,将会被暂时禁止登录 一分钟。该限制会根据用户的用户名/邮箱地址以及 IP 地址进行唯一识别。
手动验证用户
你并不必须使用 Laravel 应用入门套件中包含的认证脚手架。如果你选择不使用这些脚手架,就需要直接使用 Laravel 的认证类来管理用户认证。别担心,这其实非常简单!
我们将通过 Auth 门面 访问 Laravel 的认证服务,因此需要在类的顶部导入 Auth 门面。接下来,我们来看 attempt 方法。attempt 方法通常用于处理应用登录表单的认证尝试。如果认证成功,应重新生成用户会话以防止 会话固定攻击(session fixation):
<?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 方法详解
attempt方法接收一个 键值对数组 作为第一个参数,数组中的值用于在数据库表中查找用户。例如上例中,会通过email列查找用户。- 如果找到用户,会将数据库中存储的哈希密码与传入数组中的密码进行比对。无需手动哈希请求中的密码,框架会在比对前自动哈希。
- 如果哈希密码匹配,将为用户启动认证会话。
- Laravel 的认证服务会根据 认证 guard 的 provider 配置 从数据库中检索用户。默认的
config/auth.php配置文件中,使用的是 Eloquent 用户提供者,并指定使用App\Models\User模型。你可以根据应用需求修改这些配置。 - 如果认证成功,
attempt方法返回true;否则返回false。 - Laravel 提供的
intended方法会将用户重定向到认证中间件拦截前尝试访问的 URL。如果目标 URL 不可用,可传入备用 URI。
指定额外条件
除了用户的邮箱和密码,你还可以在认证查询中添加额外条件。例如,可以验证用户是否被标记为“active”:
if (Auth::attempt(['email' => $email, 'password' => $password, 'active' => 1])) {
// 认证成功...
}
对于复杂查询条件,你可以在凭证数组中提供闭包(closure),闭包会接收查询实例,从而根据应用需求自定义查询:
use Illuminate\Database\Eloquent\Builder;
if (Auth::attempt([
'email' => $email,
'password' => $password,
fn (Builder $query) => $query->has('activeSubscription'),
])) {
// 认证成功...
}
在这些示例中,email 不是必需选项,只是示例。你应使用数据库中对应“用户名”的列名。
attemptWhen 方法
attemptWhen 方法可接收一个闭包作为第二个参数,用于在实际认证用户前对潜在用户进行更深入检查。闭包接收潜在用户,并返回 true 或 false 表示用户是否可认证:
if (Auth::attemptWhen([
'email' => $email,
'password' => $password,
], function (User $user) {
return $user->isNotBanned();
})) {
// 认证成功...
}
访问特定 Guard 实例
通过 Auth 门面的 guard 方法,可以指定使用哪个 guard 实例进行认证。这允许你为应用不同部分使用完全独立的可认证模型或用户表进行管理。
传给 guard 方法的名称应对应 auth.php 配置文件中 guards 数组的某个键。
if (Auth::guard('admin')->attempt($credentials)) {
// ...
}
记住用户
许多 Web 应用在登录表单中会提供一个 “记住我” 复选框。如果你希望在应用中实现此功能,可以将一个布尔值作为 attempt 方法的第二个参数传入。
当该值为 true 时,Laravel 会让用户保持长期认证状态,直到用户手动注销。你的用户表必须包含 字符串类型的 remember_token 列,用于存储 “记住我” 令牌。新建 Laravel 应用中默认生成的用户表迁移文件已包含该列:
use Illuminate\Support\Facades\Auth;
if (Auth::attempt(['email' => $email, 'password' => $password], $remember)) {
// 用户将被记住...
}
如果应用启用了 “记住我” 功能,你可以使用 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 方法前指定认证 guard:
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 基本认证(HTTP Basic Authentication)
HTTP 基本认证为应用用户提供了一种快速认证方式,无需设置专门的 登录页面。要使用此功能,只需将 auth.basic 中间件 附加到路由上。auth.basic 中间件已随 Laravel 框架内置,因此无需手动定义:
Route::get('/profile', function () {
// 只有已认证用户可以访问此路由...
})->middleware('auth.basic');
将中间件附加到路由后,访问该路由时浏览器会自动提示输入凭证。默认情况下,auth.basic 中间件会将用户表中的 email 列视为用户的 “用户名”。
关于 FastCGI 的注意事项
如果你使用 PHP FastCGI 和 Apache 来部署 Laravel 应用,HTTP 基本认证可能无法正常工作。为解决此问题,可以在应用的 .htaccess 文件中添加以下内容:
RewriteCond %{HTTP:Authorization} ^(.+)$
RewriteRule .* - [E=HTTP_AUTHORIZATION:%{HTTP:Authorization}]
无状态 HTTP 基本认证
你还可以在 不在会话中设置用户标识 cookie 的情况下使用 HTTP 基本认证。这在使用 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
{
/**
* 处理传入请求
*
* @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 () {
// 只有已认证用户可以访问此路由...
})->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 内置了中间件,使这一过程非常简单。要实现此功能,你需要定义两个路由:一个用于显示视图,提示用户确认密码;另一个用于验证密码是否正确,并将用户重定向到其预期目标页面。
配置
在用户确认密码后,系统在 三小时内不会再次要求确认密码。不过,你可以通过修改应用 config/auth.php 配置文件中的 password_timeout 配置值,来自定义用户在再次被提示输入密码前的时间间隔。
路由
密码确认表单
首先,我们定义一个路由,用于显示请求用户确认密码的视图:
Route::get('/confirm-password', function () {
return view('auth.confirm-password');
})->middleware('auth')->name('password.confirm');
正如预期的那样,该路由返回的视图应包含一个 密码输入表单。此外,你可以在视图中添加提示文字,说明用户正在进入应用的受保护区域,需要确认密码。
确认密码
接下来,我们定义一个路由,用于处理来自“确认密码”视图的表单请求。该路由负责 验证密码 并将用户重定向到其预期目标页面:
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' => ['提供的密码与记录不匹配。']
]);
}
$request->session()->passwordConfirmed();
return redirect()->intended();
})->middleware(['auth', 'throttle:6,1']);
在进一步说明之前,我们来详细分析一下该路由:
- 首先,通过
Hash::check方法确认请求中提交的密码是否与已认证用户的密码匹配。 - 如果密码有效,需要告知 Laravel 的会话系统用户已确认密码。
passwordConfirmed方法会在用户会话中设置一个时间戳,Laravel 可以根据该时间戳判断用户上次确认密码的时间。 - 最后,将用户重定向到其预期访问的页面。
保护路由
你应确保 任何需要近期密码确认才能执行的操作路由 都使用 password.confirm 中间件。该中间件随 Laravel 默认安装包内置,会自动将用户的预期目标页面存储到会话中,以便用户在确认密码后能够重定向到该页面。存储完用户的预期目标后,中间件会将用户重定向到 password.confirm 命名路由:
Route::get('/settings', function () {
// ...
})->middleware(['password.confirm']);
Route::post('/settings', function () {
// ...
})->middleware(['password.confirm']);
添加自定义 Guard
你可以使用 Auth 门面的 extend 方法 定义自定义认证 guard。通常应将对 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) {
// 返回一个 Illuminate\Contracts\Auth\Guard 的实例...
return new JwtGuard(Auth::createUserProvider($config['provider']));
});
}
}
如上例所示,传给 extend 方法的回调应返回 Illuminate\Contracts\Auth\Guard 的实现。该接口包含一些方法,你需要实现这些方法来定义自定义 guard。
自定义 guard 定义完成后,可以在 auth.php 配置文件的 guards 配置中引用该 guard:
'guards' => [
'api' => [
'driver' => 'jwt',
'provider' => 'users',
],
],
闭包请求 Guard
实现基于 HTTP 请求的自定义认证系统,最简单的方式是使用 Auth::viaRequest 方法。该方法允许你通过一个闭包快速定义认证流程。
首先,在应用的 AppServiceProvider 的 boot 方法中调用 Auth::viaRequest。viaRequest 方法的第一个参数是认证驱动名称,可以是描述自定义 guard 的任意字符串;第二个参数是一个闭包,该闭包接收传入的 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',
],
],
最后,在给路由分配认证中间件时,可以引用该 guard:
Route::middleware('auth:api')->group(function () {
// ...
});
添加自定义用户提供者
如果你没有使用传统关系型数据库来存储用户数据,则需要通过自定义认证 用户提供者(User Provider) 来扩展 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 配置文件中切换到新的用户提供者。首先,定义一个使用新驱动的提供者:
'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($identifier) 通常接收表示用户的主键,例如 MySQL 数据库的自增 ID。方法应根据该 ID 获取并返回对应的 Authenticatable 实例。
- retrieveByToken($identifier, $token)
根据用户的唯一
$identifier和 “记住我”$token获取用户,通常存储在数据库的remember_token列中。方法应返回与$token匹配的 Authenticatable 实例。 - updateRememberToken(Authenticatable $user, $token)
更新
$user实例的remember_token为新的$token。在成功执行 “记住我” 认证或用户登出时,会为用户分配新的 token。 - retrieveByCredentials(array $credentials)
接收在调用
Auth::attempt时传入的凭据数组。方法应根据这些凭据查询底层持久化存储,返回匹配的 Authenticatable 实例。通常会用类似where('username', $credentials['username'])的查询条件查找用户。此方法不应进行密码验证或认证。 - validateCredentials(Authenticatable $user, array $credentials)
将给定的
$user与$credentials进行比较以认证用户。例如,通常会使用Hash::check方法将$user->getAuthPassword()与$credentials['password']进行比较。返回true或false表示密码是否有效。 - rehashPasswordIfRequired(Authenticatable $user, array $credentials, bool $force = false)
如有必要且支持,重新哈希给定
$user的密码。例如,通常会使用Hash::needsRehash方法检查$credentials['password']是否需要重新哈希。如果需要,则使用Hash::make重新哈希密码,并更新底层存储中的用户记录。
可认证用户契约
现在我们已经了解了 UserProvider 的各个方法,接下来看看 Authenticatable 接口。请记住,用户提供者(UserProvider)应在 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() 返回用户的哈希密码。
- getRememberToken() / setRememberToken($value) / getRememberTokenName() 用于获取、设置以及返回“记住我”功能的 token 字段名称。
通过这个接口,Laravel 的认证系统可以支持任何“用户”类,无论使用的是哪种 ORM 或存储抽象层。默认情况下,Laravel 在 app/Models 目录下包含了一个实现该接口的 App\Models\User 类。
自动密码重新哈希
Laravel 默认的密码哈希算法是 bcrypt。bcrypt 哈希的“工作因子”(work factor)可以通过应用的 config/hashing.php 配置文件或环境变量 BCRYPT_ROUNDS 进行调整。
通常,随着 CPU/GPU 处理能力的提升,bcrypt 的工作因子应逐步增加。如果你为应用提高了 bcrypt 的工作因子,Laravel 会在用户通过启动套件(starter kits)认证或通过 attempt 方法手动认证时,优雅地自动重新哈希用户密码。
一般情况下,自动密码重新哈希不会影响应用运行;但你也可以通过发布哈希配置文件来禁用此功能:
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 |