Skip to content
全部文档

认证 ​

  • [社交认证](/11.x/socialite)
  • 事件

简介 ​

许多 Web 应用都会为用户提供认证与「登录」的方式。在 Web 应用中实现该功能可能复杂且存在风险。因此,Laravel 致力于提供所需工具,让你能快速、安全、轻松地实现认证。

从本质上说,Laravel 的认证设施由「守卫」(guards)与「提供者」(providers)组成。守卫定义每个请求如何认证用户。例如,Laravel 自带 session 守卫,使用会话存储与 Cookie 维护状态。

提供者定义如何从持久化存储中检索用户。Laravel 支持使用 Eloquent 与数据库查询构造器检索用户。当然,你也可以根据应用需要自行定义额外的提供者。

应用的认证配置文件位于 config/auth.php。该文件包含若干文档完善的选项,用于调整 Laravel 认证服务的行为。

INFO

不要将守卫与提供者与「角色」和「权限」混淆。若要通过权限授权用户操作,请参阅授权文档。

起步套件 ​

想快速上手?在全新的 Laravel 应用中安装一个 Laravel 应用起步套件。迁移数据库后,用浏览器访问 /register 或应用分配的任意其他 URL。起步套件会搭建完整的认证系统!

即使最终应用不打算使用起步套件,安装 Laravel Breeze 起步套件也是学习如何在真实 Laravel 项目中实现全部认证功能的绝佳机会。 由于 Laravel Breeze 会为你创建认证控制器、路由与视图,你可以查阅这些文件中的代码,了解 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 的整体认证生态,并说明各包的用途。

首先了解认证如何工作。使用浏览器时,用户通过登录表单提交用户名与密码。若凭证正确,应用会将已认证用户的信息存入用户的会话。发给浏览器的 Cookie 包含会话 ID,以便后续请求将用户与正确的会话关联。收到会话 Cookie 后,应用根据会话 ID 取回会话数据,确认认证信息已存入会话,并将该用户视为「已认证」。

当远程服务需要认证以访问 API 时,通常不会使用 Cookie(因为没有浏览器)。远程服务会在每次请求中向 API 发送 API 令牌。应用可对照有效 API 令牌表校验传入令牌,并将该请求「认证」为与该 API 令牌关联的用户所发起。

Laravel 内置的浏览器认证服务 ​

Laravel 包含内置的认证与会话服务,通常通过 Auth 与 Session facade 访问。这些功能为来自浏览器的请求提供基于 Cookie 的认证,并提供验证用户凭证与认证用户的方法。此外,这些服务会自动将正确的认证数据存入用户会话,并发放会话 Cookie。本文档将说明如何使用这些服务。

应用起步套件

如本文档所述,你可以手动使用这些认证服务来构建应用自己的认证层。不过,为帮助你更快上手,我们发布了免费包,为整套认证层提供稳健、现代的脚手架。这些包包括 Laravel Breeze、Laravel Jetstream 与 Laravel Fortify。

Laravel Breeze 是 Laravel 全部认证功能的简单、精简实现,包括登录、注册、密码重置、邮箱验证与密码确认。Laravel Breeze 的视图层由使用 Tailwind CSS 样式的简单 Blade 模板组成。要开始使用,请查阅 Laravel 应用起步套件文档。

Laravel Fortify 是 Laravel 的无头认证后端,实现了本文档中的许多功能,包括基于 Cookie 的认证,以及双因素认证与邮箱验证等其他功能。Fortify 为 Laravel Jetstream 提供认证后端,也可独立结合 Laravel Sanctum 使用,为需要与 Laravel 认证的 SPA 提供认证。

Laravel Jetstream 是稳健的应用起步套件,消费并暴露 Laravel Fortify 的认证服务,并提供由 Tailwind CSS、Livewire 和 / 或 Inertia 驱动的美观现代 UI。Laravel Jetstream 可选支持双因素认证、团队、浏览器会话管理、个人资料管理,以及与 Laravel Sanctum 的内置集成以提供 API 令牌认证。Laravel 的 API 认证方案将在下文讨论。

Laravel 的 API 认证服务 ​

Laravel 提供两个可选包,协助管理 API 令牌以及对使用 API 令牌的请求进行认证:Passport 与 Sanctum。请注意,这些库与 Laravel 内置的基于 Cookie 的认证库并不互斥。它们主要关注 API 令牌认证,而内置认证服务关注基于 Cookie 的浏览器认证。许多应用会同时使用 Laravel 内置的 Cookie 认证服务以及其中一个 API 认证包。

Passport

Passport 是 OAuth2 认证提供者,提供多种 OAuth2「授权类型」,可签发各类令牌。总体而言,这是一个功能强大且复杂的 API 认证包。不过,多数应用并不需要 OAuth2 规范中那些复杂特性,而这些特性往往让用户与开发者都感到困惑。此外,开发者历史上也常常不清楚如何用 Passport 这类 OAuth2 认证提供者来认证 SPA 或移动应用。

Sanctum

针对 OAuth2 的复杂性与开发者困惑,我们着手打造更简单、更精简的认证包,既能处理来自浏览器的第一方 Web 请求,也能通过令牌处理 API 请求。这一目标随着 Laravel Sanctum 的发布得以实现;对于除 API 外还提供第一方 Web UI、或由与后端 Laravel 应用分离的单页应用(SPA)驱动、或提供移动客户端的应用,Sanctum 应视为首选且推荐的认证包。

Laravel Sanctum 是混合式 Web / API 认证包,可管理应用的完整认证流程。其原理是:基于 Sanctum 的应用收到请求时,Sanctum 会先判断请求是否包含指向已认证会话的会话 Cookie。Sanctum 通过调用前文所述的 Laravel 内置认证服务实现这一点。若请求未通过会话 Cookie 认证,Sanctum 会检查请求中是否有 API 令牌;若存在,则用该令牌认证请求。欲了解更多,请参阅 Sanctum 的「工作原理」文档。

Laravel Sanctum 是我们选择随 Laravel Jetstream 应用起步套件一并提供的 API 包,因为我们相信它最适合大多数 Web 应用的认证需求。

总结与技术栈选择 ​

总之,若应用通过浏览器访问,且你在构建单体 Laravel 应用,则应使用 Laravel 内置认证服务。

其次,若应用提供由第三方消费的 API,则可在 Passport 与 Sanctum 之间选择,为应用提供 API 令牌认证。一般应优先选择 Sanctum,因为它是覆盖 API、SPA 与移动端认证的简单完整方案,并支持「scopes」或「abilities」。

若你在构建由 Laravel 后端驱动的单页应用(SPA),应使用 Laravel Sanctum。使用 Sanctum 时,你需要手动实现自己的后端认证路由,或使用 Laravel Fortify 作为无头认证后端服务,它为注册、密码重置、邮箱验证等功能提供路由与控制器。

仅当你的应用确实需要 OAuth2 规范提供的全部特性时,才应选择 Passport。

若希望快速上手,我们很乐意推荐 Laravel Breeze,以便快速启动已采用我们首选认证栈(Laravel 内置认证服务与 Laravel Sanctum)的新 Laravel 应用。

认证快速入门 ​

WARNING

本节文档讨论通过 Laravel 应用起步套件 认证用户;起步套件包含 UI 脚手架以助你快速上手。若希望直接集成 Laravel 认证系统,请参阅手动认证用户文档。

安装起步套件 ​

首先,你应安装 Laravel 应用起步套件。我们当前的起步套件 Laravel Breeze 与 Laravel Jetstream,为在全新 Laravel 应用中接入认证提供了设计精美的起点。

Laravel Breeze 是 Laravel 全部认证功能的精简、简单实现,包括登录、注册、密码重置、邮箱验证与密码确认。Laravel Breeze 的视图层由使用 Tailwind CSS 样式的简单 Blade 模板组成。此外,Breeze 还提供基于 Livewire 或 Inertia 的脚手架选项,Inertia 脚手架可选择使用 Vue 或 React。

Laravel Jetstream 是更稳健的应用起步套件,支持使用 Livewire 或 Inertia 与 Vue 搭建应用脚手架。此外,Jetstream 还提供可选的双因素认证、团队、个人资料管理、浏览器会话管理、通过 Laravel Sanctum 的 API 支持、账户删除等功能。

获取已认证用户 ​

安装认证起步套件并允许用户在应用中注册与认证后,你通常需要与当前已认证用户交互。在处理传入请求时,可通过 Auth facade 的 user 方法访问已认证用户:

use Illuminate\Support\Facades\Auth;

// Retrieve the currently authenticated user...
$user = Auth::user();

// Retrieve the currently authenticated user's 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
{
    /**
     * Update the flight information for an existing flight.
     */
    public function update(Request $request): RedirectResponse
    {
        $user = $request->user();

        // ...

        return redirect('/flights');
    }
}

判断当前用户是否已认证 ​

要判断发起传入 HTTP 请求的用户是否已认证,可使用 Auth facade 上的 check 方法。若用户已认证,该方法返回 true:

use Illuminate\Support\Facades\Auth;

if (Auth::check()) {
    // The user is logged in...
}

INFO

尽管可用 check 方法判断用户是否已认证,但通常你会使用中间件,在允许用户访问特定路由 / 控制器之前验证其是否已认证。欲了解更多,请参阅保护路由文档。

保护路由 ​

路由中间件可用于仅允许已认证用户访问给定路由。Laravel 自带 auth 中间件,它是 Illuminate\Auth\Middleware\Authenticate 类的中间件别名。由于 Laravel 已在内部为该中间件设置别名,你只需将其附加到路由定义即可:

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

重定向未认证用户 ​

当 auth 中间件检测到未认证用户时,会将其重定向到 login 命名路由。你可在应用的 bootstrap/app.php 文件中使用 redirectGuestsTo 方法修改此行为:

use Illuminate\Http\Request;

->withMiddleware(function (Middleware $middleware) {
    $middleware->redirectGuestsTo('/login');

    // Using a closure...
    $middleware->redirectGuestsTo(fn (Request $request) => route('login'));
})

指定守卫 ​

将 auth 中间件附加到路由时,还可指定用哪个「守卫」来认证用户。指定的守卫应对应 auth.php 配置文件中 guards 数组的某个键:

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

登录节流 ​

若使用 Laravel Breeze 或 Laravel Jetstream 起步套件,登录尝试会自动应用速率限制。默认情况下,若用户在多次尝试后仍未提供正确凭证,将在一分钟内无法登录。节流按用户的用户名 / 邮箱地址及其 IP 地址唯一区分。

INFO

若希望对应用中的其他路由进行速率限制,请参阅速率限制文档。

手动认证用户 ​

你不必使用 Laravel 应用起步套件 自带的认证脚手架。若不使用该脚手架,则需直接使用 Laravel 认证类管理用户认证。别担心,这很简单!

我们将通过 Auth facade 访问 Laravel 的认证服务,因此需确保在类顶部导入 Auth facade。接下来看看 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
{
    /**
     * Handle an authentication attempt.
     */
    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 的认证服务会根据认证守卫的「provider」配置从数据库检索用户。在默认的 config/auth.php 配置文件中,指定了 Eloquent 用户提供者,并指示在检索用户时使用 App\Models\User 模型。你可根据应用需要在配置文件中更改这些值。

若认证成功,attempt 方法返回 true;否则返回 false。

Laravel 重定向器提供的 intended 方法会将用户重定向到被认证中间件拦截之前试图访问的 URL。若目标不可用,可为该方法提供备用 URI。

指定额外条件 ​

若需要,还可在用户邮箱与密码之外,为认证查询添加额外查询条件。为此,只需将查询条件加入传给 attempt 方法的数组。例如,可验证用户是否标记为「active」:

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...
}

WARNING

在这些示例中,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 facade 的 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:

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 文件:

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

无状态 HTTP 基本认证 ​

你也可以在不设置会话中用户标识 Cookie 的情况下使用 HTTP 基本认证。若选择用 HTTP 认证来认证对应用 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
{
    /**
     * 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 facade 提供的 logout 方法。这会从用户会话中移除认证信息,使后续请求不再处于已认证状态。

除调用 logout 方法外,建议使会话失效并重新生成其 CSRF 令牌。用户退出后,通常会将其重定向到应用根路径:

use Illuminate\Http\Request;
use Illuminate\Http\RedirectResponse;
use Illuminate\Support\Facades\Auth;
php
/**
 * Log the user out of the application.
 */
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 facade 提供的 logoutOtherDevices 方法。该方法要求用户确认当前密码,应用应通过输入表单接收该密码:

use Illuminate\Support\Facades\Auth;

Auth::logoutOtherDevices($currentPassword);

调用 logoutOtherDevices 方法时,用户的其他会话将完全失效,即会从先前已认证的所有守卫中「退出」。

密码确认 ​

构建应用时,偶尔会有一些操作要求用户在执行前或进入敏感区域前确认密码。Laravel 内置中间件让此流程变得轻松。实现该功能需要定义两条路由:一条用于显示要求用户确认密码的视图,另一条用于验证密码有效并将用户重定向到其目标地址。

INFO

下文讨论如何直接集成 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' => ['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 facade 上的 extend 方法定义自己的认证守卫。应将 extend 方法的调用放在服务提供者中。由于 Laravel 已自带 AppServiceProvider,可将代码放在该提供者中:

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
{
    // ...

    /**
     * Bootstrap any application services.
     */
    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;
php
/**
 * Bootstrap any application services.
 */
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 facade 上的 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
{
    // ...

    /**
     * Bootstrap any application services.
     */
    public function boot(): void
    {
        Auth::provider('mongo', function (Application $app, array $config) {
            // Return an instance of 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',
    ],
],

User Provider 契约 ​

Illuminate\Contracts\Auth\UserProvider 的实现负责从 MySQL、MongoDB 等持久化存储系统中取出 Illuminate\Contracts\Auth\Authenticatable 的实现。这两个接口使 Laravel 认证机制无论用户数据如何存储、用何种类型的类表示已认证用户,都能继续运作:

我们来看一下 Illuminate\Contracts\Auth\UserProvider 契约:

php
<?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 方法用新的 $token 更新 $user 实例的 remember_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
<?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。bcrypt 哈希的「工作因子」可通过应用的 config/hashing.php 配置文件或 BCRYPT_ROUNDS 环境变量调整。

通常,随着 CPU / GPU 处理能力提升,应逐步提高 bcrypt 工作因子。若提高了应用的 bcrypt 工作因子,当用户通过 Laravel 起步套件认证,或你通过 attempt 方法手动认证用户时,Laravel 会平滑、自动地重新哈希用户密码。

通常,自动重新哈希密码不会干扰应用;不过,你可通过发布 hashing 配置文件来禁用该行为:

shell
php artisan config:publish hashing

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

php
'rehash_on_login' => false,

事件 ​

Laravel 在认证过程中会派发多种事件。你可为下列任一事件定义监听器:

Event Name
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