用户认证

未匹配的标注
本文档最新版为 11.x,旧版本可能放弃维护,推荐阅读最新版!

认证系统

简介

大多数Web应用都需要为用户提供认证和登录功能。在应用中实现这一功能可能既复杂又充满风险。为此,Laravel致力于为您提供快速、安全且简单实现认证所需的工具。

Laravel认证系统的核心由"守卫(guards)"和"提供器(providers)"组成。守卫定义了每个请求如何进行用户认证。例如,Laravel自带了一个 session 守卫,它通过会话存储和cookies来维持状态。

提供器定义了如何从持久化存储中获取用户。Laravel原生支持使用 Eloquent 和数据库查询构造器来获取用户。当然,您也可以根据需要自由定义额外的提供器。

您应用的认证配置文件位于 config/auth.php,该文件包含多个经过详细文档说明的配置选项,可用于调整 Laravel 认证服务的行为。

[!注意]
注意不要将「守卫(Guards)」和「提供者(Providers)」与「角色(Roles)」和「权限(Permissions)」混淆。如需了解基于权限的用户操作授权,请参阅 授权文档

入门套件

需要快速开始?在新安装的 Laravel 应用中安装 应用入门套件 。完成数据库迁移后,访问 /register 或应用分配的其他 URL 即可。入门套件将为您搭建完整的认证系统脚手架!

即使您最终不打算在正式应用中使用入门套件,安装 入门套件 也是学习如何在真实项目中实现 Laravel 全部认证功能的绝佳机会。 由于 Laravel 入门套件已包含认证控制器、路由和视图,您可以通过研究这些文件中的代码来学习 Laravel 认证功能的实现方式。

数据库考量

默认情况下,Laravel 在app/Models 目录中包含一个 App\Models\User Eloquent 模型 ,该模型可与默认的 Eloquent 认证驱动配合使用。

I如果您的应用不使用 Eloquent,可以使用基于 Laravel 查询构造器的 database认证提供者。对于使用 MongoDB 的应用,请查阅 MongoDB 官方提供的 Laravel 用户认证文档

在为 App\Models\User 模型构建数据库结构时,请确保 password 列的长度至少为 60 个字符。当然,新的 Laravel 应用程序中包含的 users 表迁移已经创建了一个超过此长度的列。

此外,你应该验证你的 users(或等效)表是否包含一个可为空的、长度为 100 个字符的字符串 remember_token 列。此列将用于为在登录你的应用程序时选择「记住我」选项的用户存储令牌。同样,新的 Laravel 应用程序中包含的默认 users 表迁移已经包含了此列。

生态系统概述

Laravel 提供了几个与身份验证相关的扩展包。在继续之前,我们将回顾 Laravel 中的通用身份验证生态系统,并讨论每个扩展包的预期用途。

首先,考虑身份验证是如何工作的。当使用 Web 浏览器时,用户将通过登录表单提供其用户名和密码。如果这些凭据是正确的,应用程序将在用户的 session 中存储有关已验证用户的信息。颁发给浏览器的 cookie 包含会话 ID,以便后续对应用程序的请求可以将用户与正确的会话关联起来。收到会话 cookie 后,应用程序将根据会话 ID 检索会话数据,注意身份验证信息已存储在会话中,并将用户视为「已通过身份验证」。

当远程服务需要进行身份验证以访问 API 时,通常不使用 cookie 进行身份验证,因为没有 Web 浏览器。取而代之的是,远程服务在每个请求中向 API 发送一个 API 令牌。应用程序可以根据有效的 API 令牌表验证传入的令牌,并「验证」该请求是由与该 API 令牌关联的用户执行的。

Laravel 内置的浏览器身份验证服务

Laravel 包含内置的身份验证和会话服务,通常通过 AuthSession facade 进行访问。这些功能为从 Web 浏览器发起的请求提供基于 cookie 的身份验证。它们提供了允许你验证用户凭据并对用户进行身份验证的方法。此外,这些服务会自动将正确的身份验证数据存储在用户的会话中,并颁发用户的会话 cookie。关于如何使用这些服务的讨论包含在本篇文档中。

应用程序入门套件

正如本文档中所讨论的,你可以手动与这些身份验证服务进行交互,以构建应用程序自己的身份验证层。但是,为了帮助你更快地入门,我们发布了免费的入门套件,它们为整个身份验证层提供了健壮、现代的脚手架。

Laravel 的 API 身份验证服务

Laravel 提供了两个可选的扩展包来帮助你管理 API 令牌和验证使用 API 令牌发出的请求:PassportSanctum。请注意,这些库和 Laravel 内置的基于 cookie 的身份验证库不是互斥的。这些库主要关注 API 令牌身份验证,而内置的身份验证服务则关注基于 cookie 的浏览器身份验证。许多应用程序会同时使用 Laravel 内置的基于 cookie 的身份验证服务和 Laravel 的一个 API 身份验证扩展包。

Passport

Passport 是一个 OAuth2 身份验证提供者,提供各种 OAuth2「授权类型」,允许你颁发各种类型的令牌。总的来说,这是一个用于 API 身份验证的健壮而复杂的扩展包。然而,大多数应用程序并不需要 OAuth2 规范提供的复杂功能,这可能会让用户和开发人员都感到困惑。此外,历史上开发人员一直对如何使用像 Passport 这样的 OAuth2 身份验证提供者来验证 SPA 应用程序或移动应用程序感到困惑。

Sanctum

为了应对 OAuth2 的复杂性和开发人员的困惑,我们着手构建一个更简单、更精简的身份验证包,既能处理来自 Web 浏览器的第一方 Web 请求,也能处理通过令牌的 API 请求。随着 Laravel Sanctum 的发布,这个目标得以实现,对于除了 API 之外还将提供第一方 Web UI 的应用程序,或者由独立于后端 Laravel 应用程序的单页应用程序 (SPA) 驱动的应用程序,或者提供移动客户端的应用程序,应将 Sanctum 视为首选和推荐的身份验证包。

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 facade 的 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 facade 上的 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) {
    $middleware->redirectGuestsTo('/login');

    // 使用闭包...
    $middleware->redirectGuestsTo(fn (Request $request) => route('login'));
})

重定向已认证用户

guest 中间件检测到已认证用户时,它会将用户重定向到 dashboardhome 命名路由。
你可以在应用的 bootstrap/app.php 文件中,使用 redirectUsersTo 方法来修改此行为:

use Illuminate\Http\Request;

->withMiddleware(function (Middleware $middleware) {
    $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 facade 来访问 Laravel 的认证服务,因此需要确保在类的顶部引入 Auth facade。
接下来,我们来看看 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 的身份验证服务会根据你的身份验证 guard 的 “provider” 配置,从数据库中检索用户。
在默认的 config/auth.php 配置文件中,指定了 Eloquent 用户提供器(user provider),并且它被指示在检索用户时使用 App\Models\User 模型。
你可以根据应用程序的需要,在配置文件中更改这些值。

attempt 方法在验证成功时会返回 true,否则会返回 false

Laravel 的重定向器(redirector)提供的 intended 方法,会将用户重定向到他们在被身份验证中间件拦截之前尝试访问的 URL。
如果目标地址不可用,可以为该方法提供一个备用 URI。

指定额外条件

如果需要,除了用户的电子邮件和密码,你还可以向身份验证查询中添加额外的查询条件。
为此,我们只需将查询条件添加到传递给 attempt 方法的数组中即可。
例如,我们可以验证用户是否被标记为“active”(活跃):

if (Auth::attempt(['email' => $email, 'password' => $password, 'active' => 1])) {
    // 身份验证成功...
}

For complex query conditions, you may provide a closure in your array of credentials. This closure will be invoked with the query instance, allowing you to customize the query based on your application's needs:

use Illuminate\Database\Eloquent\Builder;

if (Auth::attempt([
    'email' => $email,
    'password' => $password,
    fn (Builder $query) => $query->has('activeSubscription'),
])) {
    // 身份验证成功...
}

[!警告]
这些示例中,email 并不是必需的选项,这里只是用作示例。
你应该使用与你数据库表中“用户名”对应的列名。

attemptWhen 方法的第二个参数接收一个闭包(closure),可在实际对用户进行身份验证之前,对潜在用户执行更深入的检查。
该闭包会接收潜在的用户对象,并应返回 truefalse,以指示该用户是否可以被验证:

if (Auth::attemptWhen([
    'email' => $email,
    'password' => $password,
], function (User $user) {
    return $user->isNotBanned();
})) {
     // 身份验证成功...
}

访问特定的 Guard 实例

通过 Auth facade 的 guard 方法,你可以指定在用户身份验证时要使用的 guard 实例。
这样,你就可以为应用程序的不同部分使用完全独立的可验证模型或用户表来管理身份验证。

传递给 guard 方法的 guard 名称应与 auth.php 配置文件中配置的某个 guard 对应:

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 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 方法之前指定一个身份验证 guard:

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

通过 ID 验证用户

如果你想根据数据库记录的主键来验证用户,可以使用 loginUsingId 方法。
该方法接收你想要验证的用户的主键 ID:

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 () {
     // 仅允许已验证用户访问此路由...
})->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
{
    /**
     * 处理传入的请求
     *
     * @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 facade 提供的 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 facade 提供的 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' => ['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 方法定义自己的认证 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',
    ],
],

基于闭包的请求守卫

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

要开始使用,请在应用程序的 AppServiceProviderboot 方法中调用 Auth::viaRequestviaRequest 方法的第一个参数是认证驱动名称,可以是任何描述你自定义 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 () {
    // ...
});

添加自定义用户提供器

如果你不是使用传统的关系型数据库来存储用户,你就需要为 Laravel 扩展自定义的认证用户提供器。我们可以使用 Auth Facade 的 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 来检索用户,通常这个 token 存储在类似 remember_token 的数据库列中。与前一个方法类似,该方法应返回匹配 token 的 Authenticatable 实例。

updateRememberToken 方法会用新的 $token 更新 $user 实例的 remember_token。在成功进行 “记住我” 认证或用户注销时,会给用户分配一个新的 token。

retrieveByCredentials 方法接收传递给 Auth::attempt 的凭证数组,用于尝试对应用进行身份验证。该方法应在底层持久化存储中查询匹配这些凭证的用户。通常,该方法会使用 “where” 条件查询一个用户记录,例如 $credentials['username'] 对应的用户名。该方法应返回一个 Authenticatable 实例。该方法不应尝试进行任何密码验证或认证。

validateCredentials 方法应将给定的 $user$credentials 进行比较,以验证用户身份。例如,该方法通常会使用 Hash::check 方法,将 $user->getAuthPassword() 的值与 $credentials['password'] 的值进行比较。此方法应返回 truefalse,表示密码是否有效。

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

可认证接口

在了解了 UserProvider 的各个方法之后,我们来看一下 Authenticatable 接口。记住,用户提供器(User Providers)应当在 retrieveByIdretrieveByTokenretrieveByCredentials 方法中返回该接口的实现类:

<?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 会优雅地并自动地重新哈希用户密码,当用户通过 Laravel 的入门套件认证或通过 attempt 方法手动认证用户 时。

通常,自动密码重新哈希不会影响你的应用程序;不过,你可以通过发布 hashing 配置文件来禁用此行为:

php artisan config:publish hashing

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

'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

本文章首发在 LearnKu.com 网站上。

本译文仅用于学习和交流目的,转载请务必注明文章译者、出处、和本文链接
我们的翻译工作遵照 CC 协议,如果我们的工作有侵犯到您的权益,请及时联系我们。

原文地址:https://learnku.com/docs/laravel/12.x/au...

译文地址:https://learnku.com/docs/laravel/12.x/au...

上一篇 下一篇
《L04 微信小程序从零到发布》
从小程序个人账户申请开始,带你一步步进行开发一个微信小程序,直到提交微信控制台上线发布。
《L01 基础入门》
我们将带你从零开发一个项目并部署到线上,本课程教授 Web 开发中专业、实用的技能,如 Git 工作流、Laravel Mix 前端工作流等。
贡献者:4
讨论数量: 2
发起讨论 只看当前版本


junqiang
laravel 8.* 用户认证 vite指令不生效
2 个点赞 | 0 个回复 | 分享 | 课程版本 8.x
qinplain
Laravel 自定义登陆
0 个点赞 | 1 个回复 | 分享 | 课程版本 5.5