认证
介绍
许多Web应用程序为其用户提供了一种与应用程序进行身份验证和“登录”的方式。在Web应用程序中实现此功能可能是一个复杂且潜在风险的任务。因此,Laravel致力于为您提供快速、安全和轻松实现身份验证所需的工具。
在其核心,Laravel的认证功能由“守卫”和“提供者”组成。守卫定义了如何为每个请求认证用户。例如,Laravel附带一个session
守卫,它使用会话存储和cookie来维护状态。
提供者定义了如何从持久存储中检索用户。Laravel支持使用Eloquent和数据库查询构建器检索用户。但是,您可以根据应用程序的需要定义其他提供者。
您的应用程序的认证配置文件位于config/auth.php
。该文件包含几个详细说明的选项,用于调整Laravel认证服务的行为。
守卫和提供者不应与“角色”和“权限”混淆。要了解有关通过权限授权用户操作的更多信息,请参阅授权文档。
入门套件
想要快速入门?在一个新的Laravel应用程序中安装一个Laravel应用程序入门套件。迁移数据库后,将浏览器导航到/register
或分配给应用程序的任何其他URL。入门套件将负责为您搭建整个认证系统!
即使您选择不在最终的Laravel应用程序中使用入门套件,安装Laravel Breeze入门套件也可以是一个很好的机会,让您学习如何在实际的Laravel项目中实现所有的Laravel认证功能。 由于Laravel Breeze为您创建了认证控制器、路由和视图,您可以检查这些文件中的代码,以了解如何实现Laravel的认证功能。
数据库注意事项
默认情况下,Laravel在您的app/Models
目录中包含一个App\Models\User
Eloquent模型。此模型可以与默认的Eloquent认证驱动程序一起使用。如果您的应用程序不使用Eloquent,您可以使用database
认证提供者,该提供者使用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包括内置的认证和会话服务,通常通过Auth
和Session
门面访问。这些功能为从Web浏览器发起的请求提供基于cookie的认证。它们提供了允许您验证用户凭据和认证用户的方法。此外,这些服务将自动在用户的会话中存储适当的认证数据,并发出用户的会话cookie。如何使用这些服务的讨论包含在本文档中。
应用程序入门套件
如本文档中所述,您可以手动与这些认证服务交互,以构建应用程序自己的认证层。然而,为了帮助您更快地入门,我们发布了免费包,提供了整个认证层的强大、现代的脚手架。这些包是Laravel Breeze、Laravel Jetstream和Laravel Fortify。
Laravel Breeze 是一个简单、最小的实现,包含了Laravel的所有认证功能,包括登录、注册、密码重置、电子邮件验证和密码确认。Laravel Breeze的视图层由简单的Blade模板组成,使用Tailwind CSS进行样式化。要开始使用,请查看Laravel的应用程序入门套件文档。
Laravel Fortify 是一个Laravel的无头认证后端,实施了本文档中许多功能,包括基于cookie的认证以及其他功能,如双因素认证和电子邮件验证。Fortify为Laravel Jetstream提供认证后端,或者可以独立使用,结合Laravel Sanctum为需要与Laravel进行认证的SPA提供认证。
Laravel Jetstream 是一个强大的应用程序入门套件,使用Tailwind CSS、Livewire和/或Inertia提供美丽、现代的UI,消费和暴露Laravel Fortify的认证服务。Laravel Jetstream包括对双因素认证、团队支持、浏览器会话管理、配置文件管理的可选支持,以及与Laravel Sanctum的内置集成,以提供API令牌认证。Laravel的API认证产品将在下文中讨论。
Laravel的API认证服务
Laravel提供了两个可选包来帮助您管理API令牌和使用API令牌进行请求认证:Passport和Sanctum。请注意,这些库和Laravel的内置基于cookie的认证库不是互斥的。这些库主要关注API令牌认证,而内置认证服务则关注基于cookie的浏览器认证。许多应用程序将同时使用Laravel的内置基于cookie的认证服务和其中一个Laravel的API认证包。
Passport
Passport是一个OAuth2认证提供者,提供多种OAuth2“授权类型”,允许您颁发各种类型的令牌。总体而言,这是一个用于API认证的强大而复杂的包。然而,大多数应用程序不需要OAuth2规范提供的复杂功能,这可能会让用户和开发人员感到困惑。此外,开发人员在使用像Passport这样的OAuth2认证提供者认证SPA应用程序或移动应用程序时,历史上一直感到困惑。
Sanctum
为了应对OAuth2的复杂性和开发人员的困惑,我们着手构建一个更简单、更精简的认证包,可以处理来自Web浏览器的第一方Web请求和通过令牌的API请求。这个目标通过发布Laravel Sanctum得以实现,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,您将选择Passport或Sanctum为您的应用程序提供API令牌认证。一般来说,Sanctum应在可能的情况下优先选择,因为它是一个简单、完整的API认证、SPA认证和移动认证解决方案,包括对“范围”或“能力”的支持。
如果您正在构建一个将由Laravel后端提供支持的单页应用程序(SPA),您应该使用Laravel Sanctum。使用Sanctum时,您将需要手动实现自己的后端认证路由或使用Laravel Fortify作为无头认证后端服务,提供注册、密码重置、电子邮件验证等功能的路由和控制器。
当您的应用程序绝对需要OAuth2规范提供的所有功能时,可以选择Passport。
如果您想快速入门,我们很高兴推荐Laravel Breeze作为快速启动新Laravel应用程序的方式,该应用程序已经使用了我们首选的Laravel内置认证服务和Laravel Sanctum认证栈。
认证快速入门
本文档的这一部分讨论了通过Laravel应用程序入门套件认证用户,其中包括UI脚手架以帮助您快速入门。如果您想直接与Laravel的认证系统集成,请查看手动认证用户的文档。
安装入门套件
首先,您应该安装一个Laravel应用程序入门套件。我们当前的入门套件,Laravel Breeze和Laravel Jetstream,提供了美丽的设计起点,用于将认证集成到您的新Laravel应用程序中。
Laravel Breeze是一个简单、最小的实现,包含了Laravel的所有认证功能,包括登录、注册、密码重置、电子邮件验证和密码确认。Laravel Breeze的视图层由简单的Blade模板组成,使用Tailwind CSS进行样式化。Breeze还提供了一个基于Inertia的脚手架选项,使用Vue或React。
Laravel Jetstream是一个更强大的应用程序入门套件,支持使用Livewire或Inertia和Vue为您的应用程序搭建脚手架。此外,Jetstream还提供了对双因素认证、团队、配置文件管理、浏览器会话管理、通过Laravel Sanctum的API支持、账户删除等功能的可选支持。
检索已认证用户
在安装认证入门套件并允许用户注册和认证您的应用程序后,您通常需要与当前认证的用户进行交互。在处理传入请求时,您可以通过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\Request;
class FlightController extends Controller
{
/**
* 更新现有航班的航班信息。
*
* @param \Illuminate\Http\Request $request
* @return \Illuminate\Http\Response
*/
public function update(Request $request)
{
// $request->user()
}
}
确定当前用户是否已认证
要确定发出传入HTTP请求的用户是否已认证,您可以使用Auth
门面的check
方法。如果用户已认证,此方法将返回true
:
use Illuminate\Support\Facades\Auth;
if (Auth::check()) {
// 用户已登录...
}
即使可以使用check
方法确定用户是否已认证,您通常会使用中间件来验证用户在允许用户访问某些路由/控制器之前是否已认证。要了解更多信息,请查看保护路由的文档。
保护路由
路由中间件可用于仅允许已认证用户访问给定路由。Laravel附带一个auth
中间件,它引用Illuminate\Auth\Middleware\Authenticate
类。由于此中间件已在应用程序的HTTP内核中注册,您只需将中间件附加到路由定义即可:
Route::get('/flights', function () {
// 只有已认证用户可以访问此路由...
})->middleware('auth');
重定向未认证用户
当auth
中间件检测到未认证用户时,它将用户重定向到login
命名路由。您可以通过更新应用程序的app/Http/Middleware/Authenticate.php
文件中的redirectTo
函数来修改此行为:
/**
* 获取用户应重定向到的路径。
*
* @param \Illuminate\Http\Request $request
* @return string
*/
protected function redirectTo($request)
{
return route('login');
}
指定守卫
在将auth
中间件附加到路由时,您还可以指定应使用哪个“守卫”来认证用户。指定的守卫应对应于auth.php
配置文件中的guards
数组中的一个键:
Route::get('/flights', function () {
// 只有已认证用户可以访问此路由...
})->middleware('auth:admin');
登录节流
如果您使用的是Laravel Breeze或Laravel Jetstream 入门套件,速率限制将自动应用于登录尝试。默认情况下,如果用户在多次尝试后未能提供正确的凭据,则用户将无法登录一分钟。节流是针对用户的用户名/电子邮件地址和他们的IP地址唯一的。
如果您想限制应用程序中的其他路由,请查看速率限制文档。
手动认证用户
您不需要使用Laravel的应用程序入门套件中包含的认证脚手架。如果您选择不使用此脚手架,您将需要直接使用Laravel认证类来管理用户认证。别担心,这很简单!
我们将通过Auth
门面访问Laravel的认证服务,因此我们需要确保在类的顶部导入Auth
门面。接下来,让我们看看attempt
方法。attempt
方法通常用于处理应用程序“登录”表单的认证尝试。如果认证成功,您应该重新生成用户的会话以防止会话固定:
<?php
namespace App\Http\Controllers;
use Illuminate\Http\Request;
use Illuminate\Support\Facades\Auth;
class LoginController extends Controller
{
/**
* 处理认证尝试。
*
* @param \Illuminate\Http\Request $request
* @return \Illuminate\Http\Response
*/
public function authenticate(Request $request)
{
$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
方法的数组中。例如,我们可以验证用户是否被标记为“活跃”:
if (Auth::attempt(['email' => $email, 'password' => $password, 'active' => 1])) {
// 认证成功...
}
对于复杂的查询条件,您可以在凭据数组中提供一个闭包。此闭包将与查询实例一起调用,允许您根据应用程序的需要自定义查询:
if (Auth::attempt([
'email' => $email,
'password' => $password,
fn ($query) => $query->has('activeSubscription'),
])) {
// 认证成功...
}
在这些示例中,email
不是必需选项,它仅用作示例。您应该使用与数据库表中的“用户名”对应的列名。
attemptWhen
方法接收一个闭包作为其第二个参数,可用于在实际认证用户之前对潜在用户进行更广泛的检查。闭包接收潜在用户,并应返回true
或false
以指示用户是否可以被认证:
if (Auth::attemptWhen([
'email' => $email,
'password' => $password,
], function ($user) {
return $user->isNotBanned();
})) {
// 认证成功...
}
访问特定守卫实例
通过Auth
门面的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)) {
// 用户被记住...
}
如果您的应用程序提供“记住我”功能,您可以使用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
方法的第二个参数传递。此值指示是否需要“记住我”功能以进行认证会话。请记住,这意味着会话将无限期地认证,或直到用户手动注销应用程序:
Auth::loginUsingId(1, $remember = true);
认证用户一次
您可以使用once
方法在应用程序中认证用户一次请求。调用此方法时不会使用会话或cookie:
if (Auth::once($credentials)) {
//
}
HTTP基本认证
HTTP基本认证提供了一种快速认证应用程序用户的方法,而无需设置专用的“登录”页面。要开始使用,请将auth.basic
中间件附加到路由。auth.basic
中间件包含在Laravel框架中,因此您无需定义它:
Route::get('/profile', function () {
// 只有已认证用户可以访问此路由...
})->middleware('auth.basic');
一旦中间件附加到路由,您将在浏览器中访问路由时自动提示输入凭据。默认情况下,auth.basic
中间件将假定users
数据库表上的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 Illuminate\Support\Facades\Auth;
class AuthenticateOnceWithBasicAuth
{
/**
* 处理传入请求。
*
* @param \Illuminate\Http\Request $request
* @param \Closure $next
* @return mixed
*/
public function handle($request, $next)
{
return Auth::onceBasic() ?: $next($request);
}
}
接下来,注册路由中间件并将其附加到路由:
Route::get('/api/user', function () {
// 只有已认证用户可以访问此路由...
})->middleware('auth.basic.once');
登出
要手动注销应用程序的用户,您可以使用Auth
门面提供的logout
方法。这将从用户的会话中删除认证信息,以便后续请求不被认证。
除了调用logout
方法外,建议您使用户的会话失效并重新生成其CSRF令牌。注销用户后,您通常会将用户重定向到应用程序的根目录:
use Illuminate\Http\Request;
use Illuminate\Support\Facades\Auth;
/**
* 将用户从应用程序中注销。
*
* @param \Illuminate\Http\Request $request
* @return \Illuminate\Http\Response
*/
public function logout(Request $request)
{
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
方法时,用户的其他会话将完全失效,这意味着他们将从之前认证的所有守卫中“注销”。
密码确认
在构建应用程序时,您可能偶尔会有需要用户在执行操作之前或在用户被重定向到应用程序的敏感区域之前确认其密码的操作。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;
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
命名路由:
Route::get('/settings', function () {
// ...
})->middleware(['password.confirm']);
Route::post('/settings', function () {
// ...
})->middleware(['password.confirm']);
添加自定义守卫
您可以使用Auth
门面的extend
方法定义自己的认证守卫。您应在服务提供者中放置对extend
方法的调用。由于Laravel已经附带了一个AuthServiceProvider
,我们可以将代码放在该提供者中:
<?php
namespace App\Providers;
use App\Services\Auth\JwtGuard;
use Illuminate\Foundation\Support\Providers\AuthServiceProvider as ServiceProvider;
use Illuminate\Support\Facades\Auth;
class AuthServiceProvider extends ServiceProvider
{
/**
* 注册任何应用程序认证/授权服务。
*
* @return void
*/
public function boot()
{
$this->registerPolicies();
Auth::extend('jwt', function ($app, $name, array $config) {
// 返回一个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
方法。此方法允许您使用单个闭包快速定义认证过程。
要开始使用,请在AuthServiceProvider
的boot
方法中调用Auth::viaRequest
方法。viaRequest
方法接受一个认证驱动程序名称作为其第一个参数。此名称可以是描述自定义守卫的任何字符串。传递给方法的第二个参数应为一个闭包,该闭包接收传入的HTTP请求并返回用户实例,或者如果认证失败,则返回null
:
use App\Models\User;
use Illuminate\Http\Request;
use Illuminate\Support\Facades\Auth;
/**
* 注册任何应用程序认证/授权服务。
*
* @return void
*/
public function boot()
{
$this->registerPolicies();
Auth::viaRequest('custom-token', function (Request $request) {
return User::where('token', (string) $request->token)->first();
});
}
一旦定义了自定义认证驱动程序,您可以在auth.php
配置文件中将其配置为驱动程序:
'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\Foundation\Support\Providers\AuthServiceProvider as ServiceProvider;
use Illuminate\Support\Facades\Auth;
class AuthServiceProvider extends ServiceProvider
{
/**
* 注册任何应用程序认证/授权服务。
*
* @return void
*/
public function boot()
{
$this->registerPolicies();
Auth::provider('mongo', function ($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);
}
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']
的值进行比较。此方法应返回true
或false
,指示密码是否有效。
可认证契约
现在我们已经探讨了UserProvider
上的每个方法,让我们看看Authenticatable
契约。请记住,用户提供者应从retrieveById
、retrieveByToken
和retrieveByCredentials
方法返回此接口的实现:
<?php
namespace Illuminate\Contracts\Auth;
interface Authenticatable
{
public function getAuthIdentifierName();
public function getAuthIdentifier();
public function getAuthPassword();
public function getRememberToken();
public function setRememberToken($value);
public function getRememberTokenName();
}
此接口很简单。getAuthIdentifierName
方法应返回用户的“主键”字段的名称,getAuthIdentifier
方法应返回用户的“主键”。使用MySQL后端时,这可能是分配给用户记录的自动递增主键。getAuthPassword
方法应返回用户的哈希密码。
此接口允许认证系统与任何“用户”类一起工作,而不管您使用什么ORM或存储抽象层。默认情况下,Laravel在app/Models
目录中包含一个App\Models\User
类,该类实现了此接口。
事件
在认证过程中,Laravel会调度各种事件。您可以在EventServiceProvider
中附加这些事件的监听器:
/**
* 应用程序的事件监听器映射。
*
* @var array
*/
protected $listen = [
'Illuminate\Auth\Events\Registered' => [
'App\Listeners\LogRegisteredUser',
],
'Illuminate\Auth\Events\Attempting' => [
'App\Listeners\LogAuthenticationAttempt',
],
'Illuminate\Auth\Events\Authenticated' => [
'App\Listeners\LogAuthenticated',
],
'Illuminate\Auth\Events\Login' => [
'App\Listeners\LogSuccessfulLogin',
],
'Illuminate\Auth\Events\Failed' => [
'App\Listeners\LogFailedLogin',
],
'Illuminate\Auth\Events\Validated' => [
'App\Listeners\LogValidated',
],
'Illuminate\Auth\Events\Verified' => [
'App\Listeners\LogVerified',
],
'Illuminate\Auth\Events\Logout' => [
'App\Listeners\LogSuccessfulLogout',
],
'Illuminate\Auth\Events\CurrentDeviceLogout' => [
'App\Listeners\LogCurrentDeviceLogout',
],
'Illuminate\Auth\Events\OtherDeviceLogout' => [
'App\Listeners\LogOtherDeviceLogout',
],
'Illuminate\Auth\Events\Lockout' => [
'App\Listeners\LogLockout',
],
'Illuminate\Auth\Events\PasswordReset' => [
'App\Listeners\LogPasswordReset',
],
];