认证


介绍

许多网络应用程序为其用户提供了身份认证和“登录”的方式。在 Web 应用程序中实现此功能可能是一项复杂且潜在风险的任务。因此,Laravel 致力于为您提供实现身份认证的工具,快速、安全和简便地进行身份认证。

在核心层,Laravel 的身份认证功能由“guards”和“providers”组成。守卫定义了用户在每个请求中的身份认证方式。例如,Laravel 默认提供了一个会话守护程序,该守护程序使用会话存储和 cookie 来维护状态。

提供者定义了如何从持久存储中检索用户。Laravel 默认支持使用 Eloquent 和数据库查询构建器来检索用户。但是,您可以根据应用程序的需要自由定义其他提供者。

您的应用程序的身份认证配置文件位于 config/auth.php。该文件包含了一些详细记录的选项,用于调整Laravel 身份认证服务的行为。

不应将 guards 和 providers 与“角色”和“权限”混淆。有关通过权限授权用户操作的更多信息,请参阅授权文档

入门套件

想要快速开始吗?在新的 Laravel 应用程序中安装一个 Laravel 应用程序入门套件。在迁移数据库后,将浏览器导航到 /register 或分配给您的应用程序的任何其他 URL。入门套件将负责为您的整个身份认证系统进行脚手架搭建!

即使您选择不在最终的 Laravel 应用程序中使用入门套件,安装 Laravel Breeze 入门套件也是学习如何在实际Laravel 项目中实现所有 Laravel 身份认证功能的绝佳机会。由于 Laravel Breeze 创建了身份认证控制器、路由和视图,您可以检查这些文件中的代码,了解 Laravel 身份认证功能的实现方式。

数据库注意事项

默认情况下,Laravel 在 app/Models 目录中包含一个 App\Models\User Eloquent模型。此模型可以与默认的Eloquent 身份认证驱动程序一起使用。如果您的应用程序不使用 Eloquent,可以使用使用 Laravel 查询构建器的数据库身份认证提供程序。

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

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

生态系统概述

Laravel 提供了几个与身份认证相关的包。在继续之前,我们将回顾 Laravel 的常规身份认证生态系统,并讨论每个包的预期目的。

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

当远程服务需要进行身份认证以访问 API 时,通常不会使用 Cookie 进行身份认证,因为没有 Web 浏览器。相反,远程服务在每个请求中向 API 发送一个 API 令牌。应用程序可以根据有效 API 令牌表中验证传入的令牌,并将请求“身份认证”为与该 API 令牌关联的用户。

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

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

应用程序入门套件

如本文档中所讨论的那样,您可以手动与这些身份认证服务进行交互,以构建应用程序自己的身份认证层。但是,为了帮助您更快地入门,我们发布了提供完整齐全的现代化脚手架的免费包,以构建整个身份认证层。这些包括 Laravel BreezeLaravel JetstreamLaravel Fortify

Laravel Breeze 是 Laravel 的所有身份认证功能的简洁、简约实现,包括登录、注册、密码重置、电子邮件验证和密码确认。Laravel Breeze 的视图层由采用了 Tailwind CSS 的简单的 Blade 模板组成。要开始使用,请查看 Laravel 应用程序入门套件的文档。

Laravel Fortify 是用于 Laravel 的无头身份认证后端,它实现了本文档中介绍的许多功能,包括基于 Cookie 的身份认证,以及其他功能,如双因素身份认证和电子邮件验证。Fortify 为 Laravel Jetstream 提供了身份认证后端,或者可以与 Laravel Sanctum 结合使用,为需要与 Laravel 进行身份认证的SPA提供身份认证。

Laravel Jetstream 是一个强大的应用程序入门套件,它使用 Tailwind CSSLivewire 和 / 或 Inertia 提供了一个美观、现代的 UI,消耗和公开了 Laravel Fortify 的身份认证服务。Laravel Jetstream 包括对双因素身份认证、团队支持、浏览器会话管理、配置文件管理以及与 Laravel Sanctum 的内置集成,以提供 API 令牌身份认证的可选支持。下面将讨论 Laravel 的 API 身份认证功能。

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浏览器的一方请求和通过令牌的 API 请求。这个目标在 Laravel Sanctum 的发布中实现,Laravel Sanctum 应被视为能够提供第一方 Web UI 以及 API 的应用程序或由与后端 Laravel 应用程序分开存在的单页应用程序(SPA)或提供移动客户端的应用程序的首选和推荐身份认证包。

Laravel Sanctum 是一个混合 Web/API 身份认证包,可以管理您应用程序的整个身份认证过程。这是可能的,因为当基于 Sanctum 的应用程序接收到请求时,Sanctum 首先确定请求是否包含引用经过身份认证会话的会话Cookie。Sanctum 通过调用 Laravel 的内置身份认证服务(我们之前讨论过)来实现这一点。如果请求没有通过会话 Cookie 进行身份认证,Sanctum将检查API令牌是否存在于请求中。如果存在 API 令牌,Sanctum 将使用该令牌对请求进行身份认证。要了解有关此过程的更多信息,请参阅 Sanctum 的“工作原理”文档。

Laravel Sanctum 是我们选择使用的 API 包 Laravel Jetstream 应用程序入门套件,因为我们认为它最适合大多数 Web 应用程序的身份认证需求。

总结和选择您的堆栈

总的来说,如果您的应用程序将使用浏览器访问,并且您正在构建一个单体 Laravel 应用程序,您的应用程序将使用 Laravel 的内置身份认证服务。

接下来,如果您的应用程序提供的 API 将被第三方使用,您将选择 PassportSanctum 为应用程序提供 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 的试图层由采用了 Tailwind CSS 的简单的 Blade 模板组成。Breeze 还提供了一个基于 Inertia 和 Vue 的脚手架选项。

Laravel Jetstream 是一个更强大的应用程序入门套件,它支持使用 LivewireInertia 和 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\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 类),它将被引用。由于此中间件已经在应用程序的 HTTP 内核中注册,所以您只需要将中间件附加到一个路由定义上:

Route::get('/flights', function () {
    // 只有经过身份认证的用户可以访问此路由...
})->middleware('auth');

重定向未经过认证的用户

auth 中间件检测到未经过身份认证的用户时,它会将用户重定向到登录命名路由。您可以通过更新应用程序的 app/Http/Middleware/Authenticate.php 文件中的 redirectTo 函数更改此行为:

use Illuminate\Http\Request;

/**
 * 获取要重定向到的路径。
 */
protected function redirectTo(Request $request): string
{
	return route('login');
}

指定守卫

在将 auth 中间件附加到路由时,您还可以指定要在身份认证用户时使用的“guard”。指定的 guard 应该对应于配置文件 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\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 方法接受一个键/值对数组作为其第一个参数。数组中的值将用于在数据库表中查找用户。因此,在上面的示例中,用户将通过电子邮件列的值来检索。如果找到用户,将会将数据库中存储的哈希密码与通过数组传递给方法的密码值进行比较。您不应该对传入请求的密码值进行哈希处理,因为框架会自动将该值进行哈希处理,然后再将其与数据库中的哈希密码进行比较。如果两个哈希值匹配,将为用户启动经过身份认证的会话。

请记住,Laravel 的身份认证服务将根据身份认证守卫的“提供者”配置从数据库中检索用户。在默认的config/auth.php 配置文件中,指定了 Eloquent 用户提供者,并指示其在检索用户时使用 App\Models\User 模型。您可以根据应用程序的需要在配置文件中更改这些值。

如果身份认证成功,attempt 方法将返回 true。否则,将返回 false

Laravel 的重定向器提供的 intended 方法会将用户重定向至他们在被身份认证中间件拦截前试图访问的URL。如果预定的目的地不可用,可以向此方法提供一个备用的 URI。

确保性能时需考虑到的额外条件

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

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

对于复杂的查询条件,您可以在 credentials 数组中提供一个闭包。此闭包将使用查询实例进行调用,允许您根据应用程序的需要自定义查询:

use Illuminate\Database\Eloquent\Builder;

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

在这些示例中,邮箱不是必需选项,只是用作示例。您应该使用与您的数据库表中的“用户名”对应的任何列名。

可以使用 attemptWhen 方法来执行更广泛的用户潜在检查,然后再对用户进行身份认证。此方法接受一个闭包作为其第二个参数,闭包将接收潜在用户,并应返回 truefalse 来指示用户是否可以进行身份认证:

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

访问特定的守卫实例

通过 Auth 门面的 guard 方法,您可以指定在对用户进行身份认证时要使用的“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 方法之前指定身份认证守卫:

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 中间件将假定您的用户数据库表上的 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 门面提供的 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 中间件。通常,您应将此中间件放置在路由组定义上,以便将其应用于应用程序的大部分路由。默认情况下,AuthenticateSession 中间件可以使用 auth.session 路由中间件别名对路由进行附加,该别名在应用程序的 HTTP 内核中定义:

Route::middleware(['auth', 'auth.session'])->group(function () {
    Route::get('/', function () {
        // ...
    });
});

然后,您可以使用 Auth 门面提供的 logoutOtherDevices 方法来使其他设备上的会话无效。此方法需要用户确认其当前密码,并且您的应用程序应该通过输入表单来接受这个密码:

use Illuminate\Support\Facades\Auth;

Auth::logoutOtherDevices($currentPassword);

调用 logoutOtherDevices 方法时,将完全使用户的其他会话无效,这意味着他们将从先前经过身份认证的所有守卫中“注销”。

密码确认

在构建应用程序时,您可能偶尔会有一些操作需要在执行操作之前或用户被重定向到应用程序的敏感区域之前要求用户确认密码。Laravel 包含了内置的中间件,使这个过程变得非常简单。要实现此功能,您需要定义两个路由:一个用于显示一个视图,要求用户确认其密码,另一个用于确认密码是否有效并将用户重定向到其预期的目的地。

下面的文档将讨论如何直接与 Laravel 的密码确认功能集成;但是,如果您想更快地开始,Laravel 应用程序的起始套件包括对此功能的支持!

配置

在确认密码后,用户在3小时内将不会再次被要求确认密码。但是,您可以通过更改应用程序的 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;
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']);

在继续之前,让我们详细检查一下这个路由。首先,检查请求的密码字段是否实际匹配已认证用户的密码。如果密码有效,我们需要通知 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\Contracts\Foundation\Application;
use Illuminate\Foundation\Support\Providers\AuthServiceProvider as ServiceProvider;
use Illuminate\Support\Facades\Auth;

class AuthServiceProvider extends ServiceProvider
{
    /**
     * Register any application authentication / authorization 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 方法。此方法允许您使用一个闭包快速定义身份认证过程。

要开始,调用 AuthServiceProviderboot 方法中的 Auth::viaRequest 方法。viaRequest 方法接受一个身份认证驱动程序名称作为其第一个参数。该名称可以是任何字符串,用于描述您的自定义守卫。传递给方法的第二个参数应该是一个接收传入的 HTTP 请求并返回一个用户实例或(如果身份认证失败)null 的闭包:

use App\Models\User;
use Illuminate\Http\Request;
use Illuminate\Support\Facades\Auth;

/**
 * Register any application authentication / authorization 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 门面上的 provider 方法来定义自定义用户提供者。用户提供者解析器应返回一个Illuminate\Contracts\Auth\UserProvider 的实现:

<?php

namespace App\Providers;

use App\Extensions\MongoUserProvider;
use Illuminate\Contracts\Foundation\Application;
use Illuminate\Foundation\Support\Providers\AuthServiceProvider as ServiceProvider;
use Illuminate\Support\Facades\Auth;

class AuthServiceProvider extends ServiceProvider
{
    /**
     * Register any application authentication / authorization 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 配置文件中将其配置为驱动程序。首先,定义一个使用您的新驱动程序的提供者:

'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 和“remember me” $token 检索用户,通常存储在像remember_token 这样的数据库列中。与前一个方法一样,该方法应返回与匹配令牌值的实现。

updateRememberToken 方法使用新的 $token 更新 $user 实例的 remember_token。成功的“remember me”身份认证尝试或用户登出时,会为用户分配一个新的令牌。

retrieveByCredentials 方法接收 Auth::attempt 方法传递给应用程序时的信用凭证数组。然后,该方法应在底层持久存储中“查询”与这些凭证匹配的用户。通常,该方法将使用具有搜索值 $credentials['username']的“username”匹配条件运行查询,以搜索具有匹配值的用户记录。方法应返回 Authenticatable 的实现。此方法不应尝试进行任何密码验证或身份认证。

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

Authenticatable契约

现在我们已经探索了 UserProvider 上的每个方法,让我们来看看 Authenticatable 契约。请记住,用户提供者应该从 retrieveByIdretrieveByTokenretrieveByCredentials 方法返回此接口的实现:

<?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 中附加侦听器到这些事件:

/**
 * The event listener mappings for the application.
 *
 * @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',
    ],
];

点赞 取消点赞 收藏 取消收藏

<< 上一篇: 任务调度

>> 下一篇: 授权