基于 Laravel Fortify 提供纯后端用户认证解决方案


简介

Laravel Fortify 是一个与前台技术无关的纯后端 Laravel 认证功能实现扩展包。Fortify 提供了实现 Laravel 认证功能所需的所有路由和控制器,包括登录、注册、密码重置、邮箱验证等。安装完 Fortify 扩展包后,你可以运行 route:list Artisan 命令来查看 Fortify 注册的路由。

Fortify 扩展包并没有提供自己的用户界面,所以需要搭配一套与之适配的用户界面对它注册的路由进行请求方可正常使用。至于如何对这些路由发起请求,我们将在本文档后续部分进行讨论。

记住,Fortify 是一个旨在为你实现 Laravel 认证功能提供一个良好起点的扩展包,并不是必须要使用它才能实现用户认证,不要被它绑架了。你完全可以参考用户认证密码重置邮件验证文档来手动实现 Laravel 应用的认证功能。

什么是 Fortify?

如前面所述,Laravel Fortify 是一个与前端技术无关的 Laravel 后端认证实现扩展包,Fortify 提供了实现 Laravel 认证功能所需的所有路由和控制器,包括登录、注册、密码重置、邮箱验证等。

如果你是 Laravel 新手, 在尝试使用 Laravel Fortify 之前, 你可以先看看 Laravel Breeze 这个入门套件扩展包。Laravel Breeze 会为你的应用程序提供认证脚手架代码,包括基于 Tailwind CSS 构建的用户界面。与 Fortify 不同,Breeze 直接将其路由和控制器发布到你的应用程序中,熟悉 Laravel Breeze 可以让你在使用 Laravel Fortify 之前,先学习并了解 Laravel 认证的基本实现流程。

Laravel Fortify 本质上是将 Laravel Breeze 的路由和控制器作为一个不包含用户界面的扩展包来提供,这使得你仍然可以快速地在后端实现应用程序的用户认证功能, 而不被任何特定的前端技术栈所束缚。

什么时候应该使用 Fortify?

你可能想知道什么时候使用 Laravel Fortify 是合适的。首先,如果你使用了 Laravel 应用的入门套件,则不需要再安装 Laravel Fortify,因为所有 Laravel 应用入门套件扩展包都已经提供了完整的认证功能。

如果你没有使用这些入门套件,而你的应用程序又需要实现认证功能,你有两个选择:手动实现应用程序的认证功能,或者使用 Laravel Fortify 扩展包来提供认证功能的后端实现。

如果你选择安装 Fortify 扩展包实现认证,你的用户界面将会向 Fortify 注册的认证路由发起请求(本文档后续部分会详细介绍),以便实现用户注册和认证。

如果你选择手动与 Laravel 底层的认证服务交互,而不是使用 Fortify 扩展包,可以参考用户认证密码重置邮件验证文档去实现。

Laravel Fortify & Laravel Sanctum

有些开发者对 Laravel Sanctum 和 Laravel Fortify 的区别感到困惑,因为这两个扩展包解决了两个不同但相关的问题,首先需要声明的是,Laravel Fortify 和 Laravel Sanctum 不是相互排斥或竞争的扩展包。

Laravel Sanctum 聚焦于 API 令牌管理,可以基于 Session + Cookie 或令牌认证现有用户,不提供任何处理用户注册、登录或者密码重置的路由。

如果你试图为一个提供 API 接口的应用程序手动构建认证服务,或者构建一个单页面应用程序的后端实现,则完全有可能同时使用 Laravel Fortify(处理用户注册、密码重置等) 和Laravel Sanctum (管理 API 令牌和用户认证)。

安装

使用 Fortify 之前,需要先使用 Composer 包管理器安装它:

composer require laravel/fortify

接下来,使用 vendor:publish 命令发布 Fortify 的资源文件:

php artisan vendor:publish --provider="Laravel\Fortify\FortifyServiceProvider"

这个命令会发布 Fortify 的所有动作到 app/Actions 目录下,如果该目录不存在会自动创建。此外,Fortify 的配置文件和数据库迁移文件也会被发布。

再然后,需要运行数据库迁移命令让数据库变更生效:

php artisan migrate

Fortify 服务提供者

上述 vendor:publish 命令还会发布一个服务提供者 App\Providers\FortifyServiceProvider,你需要确保这个类被注册到 config/app.php 配置文件的 provider 配置数组中。

Fortify 服务提供者会注册 Fortify 的发布动作,并指示 Fortify 在各自的任务被 Fortify 执行时使用它们。

Fortify 特性

fortify 配置文件中包含了一个 features 配置数组,这个数组定义了 Fortify 默认要对外提供的后台路由/特性。如果你没有将 Fortify 和 Laravel Jetstream 结合使用,我们建议你只启用以下功能,这些功能也是大多数 Laravel 应用程序提供的基本认证功能:

'features' => [
    Features::registration(),
    Features::resetPasswords(),
    Features::emailVerification(),
],

禁用视图路由

默认情况下,Fortify 定义了用于返回视图的路由,比如登录页面或注册页面。不过,如果你正在构建一个 JavaScript 驱动的单页面应用程序,你可能不需要这些路由,为此,你可以在应用程序的 config/fortify.php 配置文件中设置 views 配置值为 false 来完全禁用这些路由:

'views' => false,

禁用视图 & 密码重置

如果你选择禁用 Fortify 的视图但是又要实现密码重置功能,你仍然应该定义一个名为 password.reset 的路由,该路由负责显示「重置密码」视图。这是必须的,因为 Laravel 的 Illuminate\Auth\Notifications\ResetPassword 通知会通过名为password.reset 的路由生成密码重置 URL。

认证

接下来,我们正式开始使用 Fortify 实现认证功能。首先需要告知 Fortify 如何返回「登录」视图。记住,Fortify 是一个纯后端认证实现库,如果你想要的是一个完整的包含前后端实现的认证功能,应该使用入门套件扩展包。

所有认证视图的渲染逻辑都可以通过 Laravel\Fortify\Fortify 类中提供的方法来定制,通常,你应该在 App\Providers\FortifyServiceProvider 类的 boot 方法中调用对应的方法。例如,login 路由对应的视图渲染逻辑可以这样实现:

use Laravel\Fortify\Fortify;

/**
 * Bootstrap any application services.
 *
 * @return void
 */
public function boot()
{
    Fortify::loginView(fn () => view('auth.login'));

    // ...
}

登录视图模板中应该包括一个向 /login 接口发送 POST 请求的表单。/login 接口需要一个字符串格式的电子邮件地址/用户名以及密码信息。电子邮件/用户名的字段名称应该与 config/fortify.php 配置文件中的 username 配置值相匹配。此外,可以提供一个布尔类型的 remember 字段来表示用户希望使用 Laravel 提供的「记住我」功能。

如果登录成功,Fortify 默认会将用户重定向 fortify 配置文件中通过 home 配置选项配置的 URI,如果登录请求是 XHR 请求(即 Ajax 请求),将返回一个 200 HTTP 响应。

如果登录失败,用户将被重定向回登录表单页面,并且将表单验证错误信息通过共享的 $errors Blade 模板变量提供给用户。或者,在 XHR 请求的情况下,验证错误将与 422 HTTP 响应一起返回。

自定义用户认证

Fortify 默认将根据用户提供的登录凭证和为应用程序配置的身份验证防护,自动匹配和验证用户。不过,有时候你可能希望完全自定义登录凭证的认证和用户的匹配方式。值得庆幸的是,Fortify 允许你使用 Fortify::authenticateUsing 方法轻松地实现这个功能。

该方法接受一个闭包,它会接收传入的 HTTP 请求。这个闭包负责验证附加到请求中的登录凭证,并返回相关的用户实例。如果凭证无效或找不到用户,闭包应该返回 nullfalse。通常情况下,这个方法应该在 FortifyServiceProviderboot 方法中调用:

use App\Models\User;
use Illuminate\Http\Request;
use Illuminate\Support\Facades\Hash;
use Laravel\Fortify\Fortify;

/**
 * Bootstrap any application services.
 *
 * @return void
 */
public function boot()
{
    Fortify::authenticateUsing(function (Request $request) {
        $user = User::where('email', $request->email)->first();

        if ($user &&
            Hash::check($request->password, $user->password)) {
            return $user;
        }
    });

    // ...
}

认证守卫

您可以在应用程序的 fortify 配置文件中自定义 Fortify 使用的认证守卫(Guard)。不过,你需要确保配置的守卫是 Illuminate/Contracts\Auth\StatefulGuard 接口的实现。如果你试图使用 Laravel Fortify 来认证 SPA,应该结合 Laravel Sanctum 来使用 Laravel 默认的 web 守卫。

双因子认证

当 Fortify 的双因子认证功能启用时,用户在认证过程中需要输入一个六位数字的数字令牌。这个令牌是通过基于时间的一次性密码(TOTP)生成的,你可以从任何兼容 TOTP 的移动认证应用程序中获取它,比如 Google Authenticator。

在开始之前, 首先要确保应用程序的 App/Models/User 模型使用了 Laravel\Fortify/TwoFactorAuthenticatable Trait:

<?php

namespace App\Models;

use Illuminate\Foundation\Auth\User as Authenticatable;
use Illuminate\Notifications\Notifiable;
use Laravel\Fortify\TwoFactorAuthenticatable;

class User extends Authenticatable
{
    use Notifiable, TwoFactorAuthenticatable;
}

接下来,你应该在应用程序中创建一个视图,让用户可以管理他们的双因子认证设置。在这个视图页面中,应该允许用户启用和禁用双因子认证,以及重新生成他们的双因子认证恢复码。

默认情况下,fortify 配置文件的 feature 数组指示 Fortify 的双因子认证设置在修改前需要密码确认。因此,应用程序还要实现 Fortify 的密码确认功能。

启用双因子认证

要启用双因子认证,应用程序需要向 Fortify 定义的 /user/two-factor-authentication 端点发送 POST 请求。如果请求成功,用户将被重定向到之前的 URL,同时该用户 Session 中的 status 变量将被设置为 two-factor-authentication-enabled 表示双因子认证已启用。你可以在视图模板中检测这个 status 会话变量,以显示相应的成功消息。如果是 Ajax 请求,则返回 200 HTTP 响应:

@if (session('status') == 'two-factor-authentication-enabled')
    <div class="mb-4 font-medium text-sm text-green-600">
        Two factor authentication has been enabled.
    </div>
@endif

接下来,需要展示双因子认证二维码,以便让用户扫描后成功登录到应用程序。如果你使用的是 Blade 视图渲染前端页面,可以使用用户实例上的 twoFactorQrCodeSvg 方法获取对应的 SVG 格式二维码图片:

$request->user()->twoFactorQrCodeSvg();

如果你构建的是基于 JavaScript 驱动的前端页面,可以通过向 /user/two-factor-qr-code 端点发送 XHR GET 请求,以获取用户的双因子认证二维码。这个端点将返回一个包含 svg 属性的 JSON 对象。

显示恢复码

除此之外,还需要显示用户的双因子恢复码。这些恢复码允许用户在移动设备的认证状态过期时重新进行身份认证。如果你使用的是 Blade 视图渲染前端页面,可以通过已认证的用户实例访问恢复码:

(array) $request->user()->two_factor_recovery_codes

如果你构建的是 JavaScript 驱动的前端页面,可以向 /user/two-factor-recovery-codes 端点发送 XHR GET 请求。这个端点将返回一个包含用户恢复码的 JSON 数组。

要重新生成用户的恢复码,应用程序应该向 /user/two-factor-recovery-codes 端点发送 POST 请求。

使用双因子认证进行认证操作

在认证过程中,Fortify 会自动将用户重定向到应用程序的双因子认证界面。不过,如果应用正在进行 XHR 登录请求,在认证成功后返回的 JSON 响应中会包含一个 JSON 对象,该对象有一个 two_factor 布尔属性。你需要检查这个值以便了解是否应该重定向到应用程序的双因子认证界面。

为了实现双因子认证功能,需要告知 Fortify 如何返回双因子认证视图。所有 Fortify 的认证视图渲染逻辑都可以通过 Laravel\Fortify\Fortify 类提供的方法进行定制。通常,你应该在 App\Providers\FortifyServiceProvider 类的 boot 方法中调用这个方法:

use Laravel\Fortify\Fortify;

/**
 * Bootstrap any application services.
 *
 * @return void
 */
public function boot()
{
    Fortify::twoFactorChallengeView(function () {
        return view('auth.two-factor-challenge');
    });

    // ...
}

Fortify 将负责定义 /two-factor-challenge 路由,以返回该视图。two-factor-challenge 视图模板应该包含一个表单,它可以向 /two-factor-challenge 端点发送 POST 请求。/two-factor-challenge 动作期望能获取一个包含有效 TOTP 令牌的 code 字段或者一个包含某个用户恢复码的 recovery_code 字段。

如果登录成功,Fortify 将把用户重定向到通过应用程序的 fortify 配置文件中的 home 配置选项配置的 URI。如果登录请求是一个 XHR 请求,将返回一个 204 HTTP 响应。

如果登录不成功,用户将被重定向回登录页面,同时表单验证错误信息将通过共享的 $errors Blade 模板变量提供给用户。或者,在 XHR 请求时,验证错误将以 422 HTTP 响应返回。

禁用双因子认证

要禁用双因子认证,应用程序应该向 /user/two-factor-authentication 端点发送 DELETE 请求。记住,Fortify 的双因子认证端点在被调用之前需要进行密码确认验证身份。

注册

为了实现应用程序的注册功能,我们需要告知 Fortify 如何返回「注册」视图。记住,Fortify 是一个纯后端认证实现库,如果你想要的是一个完整的包含前后端实现的认证功能,应该使用入门套件扩展包。

所有 Fortify 的视图渲染逻辑都可以通过 Laravel\Fortify\Fortify 类提供的方法来定制。通常,你可以在 App\Providers\FortifyServiceProvider 类的 boot 方法中调用这个方法:

use Laravel\Fortify\Fortify;

/**
 * Bootstrap any application services.
 *
 * @return void
 */
public function boot()
{
    Fortify::registerView(fn () => view('auth.register'));

    // ...
}

Fortify 将负责定义返回该视图的 /register 路由。注册页面视图模板应该包含一个表单,它可以向 Fortify 定义的 /register 端点发送 POST 请求。

/register 端点需要接收字符串格式的 name、电子邮件地址/用户名、passwordpassword_confirmation 字段。电子邮件/用户名字段的名称应该与应用程序的 fortify 配置文件中定义的 username 配置值相匹配。

如果注册成功,Fortify 将会把用户重定向到通过 fortify 配置文件中的 home 配置选项配置的 URI。如果是一个 XHR 请求,将返回一个 200 HTTP 响应。

如果注册不成功,用户将被重定向回注册页面,同时表单验证错误信息将通过共享的 $errors Blade 模板变量提供给用户。或者,在 XHR 请求时,验证错误以 422 HTTP 响应返回。

自定义注册

用户认证和注册过程可以通过修改安装 Laravel Fortify 时生成的 App\Actions\Fortify\CreateNewUser 动作来定制。

密码重置

请求密码重置链接

要实现应用程序的密码重置功能,需要告知 Fortify 如何返回「忘记密码」视图。记住,Fortify 是一个纯后端认证实现库,如果你想要的是一个完整的包含前后端实现的认证功能,应该使用入门套件扩展包。

所有 Fortify 的视图渲染逻辑都可以通过 Laravel\Fortify\Fortify 类提供的方法来定制。通常,你可以在 App\Providers\FortifyServiceProvider 类的 boot 方法中调用这个方法:

use Laravel\Fortify\Fortify;

/**
 * Bootstrap any application services.
 *
 * @return void
 */
public function boot()
{
    Fortify::requestPasswordResetLinkView(function () {
        return view('auth.forgot-password');
    });

    // ...
}

Fortify 将负责定义 /forgot-password 端点来返回这个视图。忘记密码视图模板应该包含一个表单,它可以向 /forgot-password 端点发送 POST 请求。

/forgot-password 端点期望接收字符串格式的 email 字段。这个字段/数据库列的名称应该与 fortify 配置文件中的 email 配置值相匹配。

处理密码重置链接请求响应

如果密码重置链接请求成功,Fortify 将会把用户重定向回 /forgot-password 端点,并向用户发送一封电子邮件,其中包含可以用来重置密码的安全链接。如果是 XHR 请求,将返回一个 200 HTTP 响应。

在请求成功并被重定向回 /forgot-password 端点后,用户 Session 中的 status 变量可用于显示密码重置链接请求的状态。这个变量的值将与应用程序的 passwords 语言文件中定义的翻译字符串相匹配:

@if (session('status'))
    <div class="mb-4 font-medium text-sm text-green-600">
        {{ session('status') }}
    </div>
@endif

如果请求不成功,用户将被重定向回请求密码重置链接页面,同时表单验证错误信息也将通过共享的 $errors Blade 模板变量提供给用户。或者,在 XHR 请求的情况下,验证错误将通过 422 HTTP 响应返回。

重置密码

为了实现应用程序的密码重置功能,我们需要告知 Fortify 如何返回「重置密码」视图。

所有 Fortify 的视图渲染逻辑都可以通过 Laravel\Fortify\Fortify 类提供的方法来定制。通常,你可以在 App\Providers\FortifyServiceProvider 类的 boot 方法中调用这个方法:

use Laravel\Fortify\Fortify;

/**
 * Bootstrap any application services.
 *
 * @return void
 */
public function boot()
{
    Fortify::resetPasswordView(function ($request) {
        return view('auth.reset-password', ['request' => $request]);
    });

    // ...
}

Fortify 将负责定义显示该视图的路由。reset-password 视图模板应该包含一个向 /reset-password 发送 POST 请求的表单。

/reset-password 端点期望接收字符串格式的 email 字段、password 字段、password_confirmation 字段和以及名为 token 的隐藏字段(其中包含 request()->route('token') 的值)。"email" 字段/数据库列的名称应该与 fortify 配置文件中定义的 email 配置值相匹配。

处理密码重置响应

如果密码重置请求成功,Fortify 将重定向回 /login 路由,这样用户就可以用新密码登录。此外,一个用户会话变量 status 将被设置,这样就可以在登录页面上显示密码重置的成功状态:

@if (session('status'))
    <div class="mb-4 font-medium text-sm text-green-600">
        {{ session('status') }}
    </div>
@endif

如果该请求是 XHR 请求,将返回一个 200 HTTP 响应。

如果请求不成功,用户将被重定向回重置密码页面,同时表单验证错误信息将通过共享的 $errors Blade 模板变量提供给用户。或者,在 XHR 请求的情况下,验证错误将以 422 HTTP 响应返回。

自定义密码重置

密码重置过程可以通过修改安装 Laravel Fortify 时生成的 App\Actions\ResetUserPassword 动作来定制。

邮箱验证

注册成功后,你可能希望用户在继续访问应用程序之前先验证他们的邮箱地址。开始实现这个功能之前,请确保在 fortify 配置文件的 features 数组中启用了 emailVerification 功能。接下来,还需要确保 App/Models/User 类实现了Illuminate/Contracts/Auth/MustVerifyEmail 接口。

完成这两个设置步骤后,新注册的用户将收到一封邮件,提示他们需要验证自己的邮箱地址。不过,我们需要告知 Fortify 如何渲染用户点击该邮件中的验证链接后跳转到的邮箱验证页面。

所有 Fortify 的视图渲染逻辑都可以通过 Laravel\Fortify\Fortify 类中提供的方法来定制。通常,你可以在 App\Providers\FortifyServiceProvider 类的 boot 方法中调用这个方法:

use Laravel\Fortify\Fortify;

/**
 * Bootstrap any application services.
 *
 * @return void
 */
public function boot()
{
    Fortify::verifyEmailView(fn () => view('auth.verify-email'));

    // ...
}

Fortify 将负责定义当用户被 Laravel 内置的 verified 中间件重定向到 /email/verify 端点时显示该视图的路由。

verify-email 视图模板应该包含一个提示消息,用于指示用户点击发送到他们注册邮箱的邮件验证链接。

重新发送邮箱验证链接

如果你愿意的话,可以在应用程序的 verification-email 视图模板中添加一个按钮,用于触发对 /email/verification-notification 端点发送 POST 请求。当这个端点收到请求时,一个新的验证邮箱链接将通过电子邮件发送给用户,这样一来,如果之前的验证链接被意外删除或丢失,用户可以获得一个新的验证链接。

如果重新发送验证链接邮件的请求成功,Fortify 会将用户重定向回 /email/verify 端点,并带有一个用户会话变量 status,以便允许你给用户显示一个提示消息,告知用户操作成功。如果是 XHR 请求,将返回一个 202 HTTP 响应:

@if (session('status') == 'verification-link-sent')
    <div class="mb-4 font-medium text-sm text-green-600">
        A new email verification link has been emailed to you!
    </div>
@endif

保护路由

要指定一个路由或一组路由需要用户通过邮箱验证后才能访问,可以将 Laravel 内置的 verified 中间件分配到该路由上,这个中间件是在应用程序的 App\Http\Kernel 类中注册的:

Route::get('/dashboard', function () {
   // ...
})->middleware(['verified']);

密码确认

构建应用程序时,你可能偶尔会有一些操作需要用户在执行前确认密码。通常情况下,这些路由都是通过 Laravel 内置的 password.confirm 中间件保护的。

要实现密码确认功能,我们需要先告知 Fortify 如何返回「密码确认」视图。记住,Fortify 是一个纯后端认证实现库,如果你想要的是一个完整的包含前后端实现的认证功能,应该使用入门套件扩展包。

所有 Fortify 的视图渲染逻辑都可以通过 Laravel\Fortify\Fortify 类中提供的方法进行定制。通常情况下,你需要在 App\Providers\FortifyServiceProvider 类的 boot 方法中调用这个方法:

use Laravel\Fortify\Fortify;

/**
 * Bootstrap any application services.
 *
 * @return void
 */
public function boot()
{
    Fortify::confirmPasswordView(function () {
        return view('auth.confirm-password');
    });

    // ...
}

Fortify 将负责定义 /user/confirm-password 端点来返回这个视图。confirm-password 视图模板应该包含一个向 /user/confirm-password 端点发送 POST 请求的表单。/user/confirm-password 端点期待接收一个包含用户当前密码的 password 字段。

如果密码与用户的当前密码相匹配,Fortify 将把用户重定向到他们之前试图访问的路由。如果是 XHR 请求,将返回一个 201 HTTP 响应。

如果请求不成功,用户将被重定向回确认密码页面,同时表单验证错误信息也将通过共享的$errors Blade 模板变量提供给用户。或者,在 XHR 请求的情况下,验证错误将以 422 HTTP 响应返回。


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

<< 上一篇: 基于 Laravel Envoy 提供远程部署解决方案

>> 下一篇: 基于 Laravel Horizon 提供队列系统解决方案