laragear/two-factor

本地部署的即插即用2FA认证。

v2.0.2 2024-07-23 00:53 UTC

README

Latest Version on Packagist Latest stable test run Codecov coverage Maintainability Sonarcloud Status Laravel Octane Compatibility

为您的所有用户提供本地部署的即插即用双因素认证。

use Illuminate\Http\Request;
use Laragear\TwoFactor\Facades\Auth2FA;

public function login(Request $request)
{
    $attempt = Auth2FA::attempt($request->only('email', 'password'));
    
    if ($attempt) {
        return 'You are logged in!';
    }
    
    return 'Hey, you should make an account!';
}

此包通过使用6位数字代码启用TOTP认证。无需外部API。

提示

想要使用Passkeys进行用户认证?请查看Laragear WebAuthn

成为赞助商

您的支持使我能够保持此包免费、更新和可维护。或者,您也可以 传播信息!

要求

  • Laravel 10或更高版本

安装

启动Composer并在您的项目中要求此包。

composer require laragear/two-factor

就这样。

这是如何工作的

此包添加了一个 Contract 以检测在验证凭据有效后,用户是否应使用双因素认证作为第二层认证。

它包括一个自定义 视图 和一个 辅助器,用于在登录尝试期间处理双因素认证。

无需中间件或新守卫即可工作,但如果您愿意,也可以完全手动操作。

设置

  1. 首先,使用two-factor:install Artisan命令将迁移、翻译、视图和配置安装到您的应用程序中。
php artisan two-factor:install

提示

您可以通过添加新列并在迁移之前修改迁移来编辑迁移,并更改表名

之后,您可以通过Artisan命令像往常一样迁移您的表。

php artisan migrate
  1. TwoFactorAuthenticatable contractTwoFactorAuthentication trait添加到User模型中,或您想要使其可用的任何其他模型。
<?php

namespace App;

use Illuminate\Foundation\Auth\User as Authenticatable;
use Laragear\TwoFactor\TwoFactorAuthentication;
use Laragear\TwoFactor\Contracts\TwoFactorAuthenticatable;

class User extends Authenticatable implements TwoFactorAuthenticatable
{
    use TwoFactorAuthentication;
    
    // ...
}

提示

此合约用于识别使用双因素认证的模型,而特例则方便地实现了处理它所需的方法。

就这样。您现在可以开始在您的应用程序中使用2FA了。

启用双因素认证

要为用户启用双因素认证,他必须在其认证器应用程序与应用程序之间同步共享密钥。

提示

免费认证器应用程序,无特定顺序,包括 iOS AuthenticatorFreeOTPAuthy2FAS2Stable AuthenticatorStep-twoBinaryRoot AuthenticatorGoogle AuthenticatorMicrosoft Authenticator 等。

首先,使用 createTwoFactorAuth() 方法生成所需数据。此方法返回一个可序列化的 共享密钥,您可以将它作为字符串或SVG编码的QR码显示给用户。

use Illuminate\Http\Request;

public function prepareTwoFactor(Request $request)
{
    $secret = $request->user()->createTwoFactorAuth();
    
    return view('user.2fa', [
        'qr_code' => $secret->toQr(),     // As QR Code
        'uri'     => $secret->toUri(),    // As "otpauth://" URI.
        'string'  => $secret->toString(), // As a string
    ]);
}

提示

当您对一个已经启用双因素认证的人使用 createTwoFactorAuth() 方法时,之前的数据将永久无效。这确保用户在任何时候都不会同时启用两个共享密钥。

然后,用户必须使用他们的认证应用生成的代码确认共享密钥。如果代码有效,confirmTwoFactorAuth() 方法将自动启用它。

use Illuminate\Http\Request;

public function confirmTwoFactor(Request $request)
{
    $request->validate([
        'code' => 'required|numeric'
    ]);
    
    $activated = $request->user()->confirmTwoFactorAuth($request->code);
    
    // ...
}

如果用户没有输入正确的代码,方法将返回 false。您可以建议用户检查其设备的时区,或使用 createTwoFactorAuth() 生成另一个共享密钥。

恢复代码

每次启用双因素认证时,都会自动生成恢复代码。默认情况下,创建一个包含十个一次性8位字符代码的集合。

您可以使用 getRecoveryCodes() 显示它们。

use Illuminate\Http\Request;

public function confirmTwoFactor(Request $request)
{
    if ($request->user()->confirmTwoFactorAuth($request->code)) {
        return $request->user()->getRecoveryCodes();
    }
    
    return 'Try again!';
}

您可以根据自己的喜好向用户展示这些代码,但请确保在成功启用双因素认证后至少展示一次,并要求他们将其打印下来。

提示

当用户发送的代码是恢复代码而不是TOTP代码时,这些恢复代码将自动处理。如果它是恢复代码,则包将使用并标记为无效,因此不能再次使用。

用户可以使用 generateRecoveryCodes() 生成一批新代码,这将替换之前的批次。

use Illuminate\Http\Request;

public function showRecoveryCodes(Request $request)
{
    return $request->user()->generateRecoveryCodes();
}

重要

如果用户在使用认证应用之前耗尽了恢复代码而没有禁用双因素认证,或者恢复代码被禁用,他可能会永久被锁定,无法使用认证应用。确保您有应对措施,如恢复电子邮件。

自定义恢复代码

虽然不建议这样做,因为包含的逻辑足以满足大多数情况,但您可以为恢复代码创建自己的生成器。只需使用 TwoFactorAuthentication 模型的 generateRecoveryCodesUsing() 添加一个回调即可。

此方法接收一个回调,该回调应返回一个随机字母数字代码,并在生成每个代码时被调用。

use Laragear\TwoFactor\Models\TwoFactorAuthentication;
use MyRandomGenerator;

$generator = function ($length, $iteration, $amount) {
    return MyRandomGenerator::random($length)->make();
}

TwoFactorAuthentication::generateRecoveryCodesUsing($generator);

登录

在您的应用程序中登录用户最简单的方法是使用 Auth2FA 门面。它包含您处理需要2FA代码的用户所需的一切。

  • 仅当用户启用了2FA时才有效。
  • 如果需要2FA代码,则显示自定义表单。
  • 凭据将被加密并存储在会话中以便重用。

在您的登录控制器中,使用凭据调用 Auth2FA::attempt() 方法。如果用户需要2FA代码,它将自动停止认证并显示表单。

您可以将以下代码直接复制粘贴到登录控制器中。

use Laragear\TwoFactor\Facades\Auth2FA;
use Illuminate\Http\Request;

public function login(Request $request)
{
    // If the user is trying for the first time, ensure both email and the password are
    // required to log in. If it's not, then he would issue its 2FA code. This ensures
    // the credentials are not required again when is just issuing his 2FA code alone.
    if ($request->isNotFilled('2fa_code')) {
        $request->validate([
            'email' => 'required|email',
            'password' => 'required|string'
        ]);
    }
    
    $attempt = Auth2FA::attempt($request->only('email', 'password'), $request->filled('remember'));
    
    if ($attempt) {
        return redirect()->home();
    }
    
    return back()->withErrors(['email' => 'There is no existing user for these credentials']);
}

您可以使用以下流畅方法进一步自定义处理2FA代码认证流程的方法。

例如,我们可以更改要显示的消息和登录表单中使用的输入。

use Laragear\TwoFactor\Facades\Auth2FA;

Auth2FA::message('You need 2FA set up to access this area')
    ->redirect('/auth/2fa-required')
    ->input('2fa-code')
    ->attempt($request->only('email', 'password'), $request->filled('remember'));

提示

  • 对于 Laravel UI,覆盖 attemptLogin() 方法以用 Auth2FA::attempt() 替换默认的守卫尝试,并在您的登录控制器中使用 validateLogin 方法将 if ($request->isNotFilled('2fa_code')) 语句包装起来。
  • 对于 Laravel Breeze,您可能需要扩展 LoginRequest::authenticate() 调用。
  • 对于 Laravel FortifyJetstream,您可能需要使用 Fortify::authenticateUsing() 方法设置自定义回调。

或者,您可以使用带有双因素辅助方法的 Auth::attemptWhen(),它返回一个回调以检查用户在继续使用 TwoFactor::hasCode() 之前是否需要2FA代码。

use Illuminate\Support\Facades\Auth;
use Laragear\TwoFactor\TwoFactor;

$attempt = Auth::attemptWhen(
    [/* Credentials... */], TwoFactor::hasCode(), $request->filled('remember')
);

您可以使用hasCodeOrFails()方法,它执行相同的操作,但会抛出一个验证异常,框架会优雅地处理它。它还接受一个自定义的错误消息,如果没有提供,则使用默认的翻译行。

确定安全设备绕过

当用户处于由cookie确定的安全设备上时,登录不需要2FA代码。要检查当前请求是否如此,请使用用户上的wasTwoFactorBypassedBySafeDevice()方法。

use Illuminate\Http\Request;

public function changeServerSetting(Request $request)
{
    if ($request->user()->wasTwoFactorBypassedBySafeDevice()) {
        // Do something ...
    }
    
    // ...
}

停用

您可以使用disableTwoFactorAuth()方法停用特定用户的双因素认证。这将自动使认证数据失效,使用户只需凭据即可登录。

public function disableTwoFactorAuth(Request $request)
{
    $request->user()->disableTwoFactorAuth();
    
    return 'Two-Factor Authentication has been disabled!';
}

事件

除了默认的认证事件外,还会触发以下事件。

  • TwoFactorEnabled:用户已启用双因素认证。
  • TwoFactorRecoveryCodesDepleted:用户已使用其最后的恢复代码。
  • TwoFactorRecoveryCodesGenerated:用户已生成一组新的恢复代码。
  • TwoFactorDisabled:用户已停用双因素认证。

提示

您可以使用TwoFactorRecoveryCodesDepleted通知用户创建更多恢复代码或给他们发送更多。

中间件

TwoFactor为您的路由提供了两个中间件:2fa.enabled2fa.confirm

重要

为了避免意外结果,中间件仅对实现了TwoFactorAuthenticatable契约的用户模型起作用。如果一个用户模型没有实现它,中间件将绕过任何2FA逻辑。

要求2FA

如果您需要在用户进入特定路由之前确保已启用双因素认证,则可以使用2fa.enabled中间件。实现了TwoFactorAuthenticatable契约且2FA已停用的用户将被重定向到包含警告的路由,默认为2fa.notice

Route::get('system/settings', function () {
    // ...
})->middleware('2fa.enabled');

您可以使用此包中包含的视图轻松实现,可选地提供一个URL,指向启用2FA的用户。

use Illuminate\Support\Facades\Route;

Route::view('2fa-required', 'two-factor::notice', [
    'url' => url('settings/2fa')
])->name('2fa.notice');

确认2FA

类似于password.confirm中间件,您还可以使用2fa.confirm中间件要求用户通过输入2FA代码来确认访问路由。

Route::get('api/token', function () {
    // ...
})->middleware('2fa.confirm');

Route::post('api/token/delete', function () {
    // ...
})->middleware('2fa.confirm');

默认情况下,中间件会将用户重定向到名为2fa.confirm的路由,但您可以在第一个参数中更改它。要实现接收路由,TwoFactor提供了Confirm2FACodeController和一个可用于快速开始的视图。

use Illuminate\Support\Facades\Route;
use Laragear\TwoFactor\Http\Controllers\ConfirmTwoFactorCodeController;

Route::get('2fa-confirm', [ConfirmTwoFactorCodeController::class, 'form'])
    ->name('2fa.confirm');

Route::post('2fa-confirm', [ConfirmTwoFactorCodeController::class, 'confirm']);

由于未启用2FA的用户不会要求输入代码,因此您可以将中间件与2fa.require结合使用,以确保对于未启用2FA的用户,确认是强制性的。

use Illuminate\Support\Facades\Route;

Route::get('api/token', function () {
    // ...
})->middleware('2fa.require', '2fa.confirm');

强制确认

当用户使用TOTP代码确认时,中间件将记住确认一段时间。

始终可以强制进行确认,即使用户已经确认,也可以通过将第一个或第二个参数设置为"force"或"true"来实现。

use Illuminate\Support\Facades\Route;

Route::get('api/token', function () {
    // ...
})->middleware('2fa.require', '2fa.confirm:force');

Route::get('api/important-token', function () {
    // ...
})->middleware('2fa.require', '2fa.confirm:my-redirect-route-name,true');

验证

有时您可能想在应用程序的任何部分手动触发对认证用户的TOTP验证。您可以使用topt规则对认证用户验证TOTP代码。

public function checkTotp(Request $request)
{
    $request->validate([
        'code' => 'totp'
    ]);

    // ...
}

此规则仅在用户已认证、已启用双因素认证且代码正确或为恢复代码时才成功。

提示

您可以使用totp:code强制规则不使用恢复代码。

翻译

TwoFactor 包含翻译文件,您可以直接在应用程序中使用这些文件。它们也用于 验证规则

public function disableTwoFactorAuth()
{
    // ...

    session()->flash('message', trans('two-factor::messages.success'));

    return back();
}

要添加自己的语言,发布翻译文件。这些文件将位于 lang/vendor/two-factor

php artisan vendor:publish --provider="Laragear\TwoFactor\TwoFactorServiceProvider" --tag="translations"

配置

为了进一步配置该包,发布配置文件

php artisan vendor:publish --provider="Laragear\TwoFactor\TwoFactorServiceProvider" --tag="config"

您将收到以下内容的 config/two-factor.php 配置文件

return [
    'cache' => [
        'store' => null,
        'prefix' => '2fa.code'
    ],
    'recovery' => [
        'enabled' => true,
        'codes' => 10,
        'length' => 8,
	],
    'safe_devices' => [
        'enabled' => false,
        'max_devices' => 3,
        'expiration_days' => 14,
	],
    'confirm' => [
        'key' => '_2fa',
        'time' => 60 * 3,
    ],
    'login' => [
        'view' => 'two-factor::login',
        'key' => '_2fa_login',
        'flash' => true,
    ],
    'secret_length' => 20,
    'issuer' => env('OTP_TOTP_ISSUER'),
    'totp' => [
        'digits' => 6,
        'seconds' => 30,
        'window' => 1,
        'algorithm' => 'sha1',
    ],
    'qr_code' => [
        'size' => 400,
        'margin' => 4
    ],
];

缓存存储

return  [
    'cache' => [
        'store' => null,
        'prefix' => '2fa.code'
    ],
];

RFC 6238 指出,一次性密码不应该能够被多次使用,即使在时间窗口内。为此,我们需要使用缓存来确保相同的代码不能再次使用。

您可以更改要使用的存储,这是应用程序默认使用的存储,以及用作缓存键的前缀,以防冲突。

恢复

return [
    'recovery' => [
        'enabled' => true,
        'codes' => 10,
        'length' => 8,
    ],
];

默认情况下启用恢复代码处理,但您可以禁用它。如果您这样做,请确保用户可以通过其他方式验证,例如发送带有一个登录链接的电子邮件,该链接将登录用户并禁用双因素认证,或者发送短信。

生成代码的数量和长度是可配置的。大多数认证场景中,10个8位随机字符的代码就足够了。

安全设备

return [
    'safe_devices' => [
        'enabled' => false,
        'max_devices' => 3,
        'expiration_days' => 14,
    ],
];

启用此选项将允许应用程序通过使用cookie“记住”一个设备,允许它在设备中验证一个代码后绕过双因素认证。当用户再次在该设备上登录时,它将不会再次提示输入2FA代码。

cookie包含一个随机值,该值与为认证用户保存的安全设备列表进行核对。如果值匹配且未过期,则被视为安全设备。

可以保存的设备数量有限,但通常三个就足够了(手机、平板电脑和PC)。新设备将取代已注册的最老设备。设备被认为不再“安全”直到设定天数。

您可以更改已注册后保存的最大设备数量和有效天数。更多的设备和更长的有效期将使双因素认证更不安全。

提示

禁用双因素认证时,安全设备列表总是会被清除。

确认中间件

return [
    'confirm' => [
        'key' => '_2fa',
        'time' => 60 * 3,
    ],
];

这些用于控制在会话中用于处理 2fa.confirm 中间件 的哪个键,以及以分钟为单位的过期时间。

登录助手

return [
    'login' => [
        'view' => 'two-factor::login',
        'key' => '_2fa_login',
        'flash' => true,
    ],
];

这控制登录助手配置,例如要渲染的Blade视图、用于保存登录输入(如电子邮件和密码)的会话键,以及是否应使用flash或仅使用put保存这些凭证。

关于flash的使用,如果您在登录期间预期会有其他请求,例如Inertia.js或Livewire可能会发生的情况,您可以禁用它,但这可能会使登录输入永远保留在会话中,在某些情况下这可能是不可取的。

密钥长度

return [
    'secret_length' => 20,
];

这控制用于创建共享密钥的长度(以字节为单位)。虽然160位共享密钥就足够了,但您可以根据需要调整密钥长度。

建议使用128位或160位,因为一些认证器应用程序可能存在与RFC推荐长度不兼容的问题。

OTP配置

return [
    'issuer' => env('OTP_TOTP_ISSUER'),
    'totp' => [
        'digits' => 6,
        'seconds' => 30,
        'window' => 1,
        'algorithm' => 'sha1',
    ],
];

这控制TOTP代码生成和验证机制

  • 发行者:TOTP发行者的名称。默认为应用程序名称。
  • OTP数字:请求TOTP代码的数字数量。
  • OTP秒数:代码被认为是有效的秒数。
  • OTP窗口:保持代码有效性的额外秒数步骤。
  • OTP算法:处理代码生成的系统支持的算法。

这些配置值始终是URL编码的,并作为URI参数传递给身份验证应用。

otpauth://totp/Laravel%30taylor%40laravel.com?secret=THISISMYSECRETPLEASEDONOTSHAREIT&issuer=Laravel&label=Laravel%30taylor%40laravel.com&algorithm=SHA1&digits=6&period=30

这些值将打印到应用程序中每个2FA数据记录中。更改只会对新激活生效。

警告

如果您计划使用公开可用的身份验证器应用,请勿编辑这些参数,因为其中一些可能不支持非标准配置,例如更多数字、不同的秒数或其他算法。

QR码配置

return [
    'qr_code' => [
        'size' => 400,
        'margin' => 4
    ],
];

这控制创建QR码时使用的尺寸和边距,这些QR码以SVG格式创建。

自定义TOTP标签

您可以使用用户对象的getTwoFactorIssuer()getTwoFactorUserIdentifier()方法更改模型创建TOTP标签的方式,该标签将显示在用户的身份验证器上。

例如,我们可以根据用户访问的域名更改发行者和标识符。

public function getTwoFactorIssuer(): string
{
    return request()->getHost();
}

public function getTwoFactorUserIdentifier(): string
{
    return request()->getHost() === 'admin.myapp.com'
        ? $this->getAttribute('name')
        : $this->getAttribute('email');
}

上述代码将渲染users.myapp.com:john@gmail.comadmin.myapp.com:John Doe

迁移

此软件包提供了一种非常简单的迁移方法。如果您检查迁移...create_two_factor_authentications_table.php,您将看到如下内容

use Illuminate\Database\Schema\Blueprint;
use Laragear\TwoFactor\Migrations\TwoFactorAuthenticationMigration;

return new class extends TwoFactorAuthenticationMigration
{
    /**
     * Add additional columns to the table
     */
    public function addCustomColumns(Blueprint $table): void
    {
        // Here you can add custom columns to the Two Factor table.
        //
        // $table->string('alias')->nullable();
    }
};

表的架构由内部处理。《addCustomColumns()》方法为您提供机会向表中添加更多列。

如果您需要在创建表后或删除表之前执行逻辑,请分别使用《afterUp()》和《beforeDown()》方法。

use Illuminate\Database\Schema\Blueprint;

public function afterUp(Blueprint $table)
{
    $table->foreignId('authenticatable_id')->references('id')->on('users');
}

public function beforeDown(Blueprint $table)
{
    $table->dropForeign('authenticatable_id');
}

Morphs

默认情况下,该表使用查询构建器的默认id,《Builder::$defaultMorphKeyType》。如果您只想更改此表的morph key类型,可以将迁移的《$morphsType》属性设置为《uuid》或《ulid》。

use Illuminate\Database\Schema\Blueprint;
use Laragear\TwoFactor\Migrations\TwoFactorAuthenticationMigration;

return new class extends TwoFactorAuthenticationMigration
{
    protected string $morphsType = 'ulid'
};

自定义表名

默认情况下,《TwoFactorAuthentication》模型将使用《two_factor_authentications》作为表的名称。如果您出于任何原因想更改名称,请使用《TwoFactorAuthentication》模型的《$useTable》静态属性设置表。您应该在《AppServiceProvider》的《register()`》方法中这样做。

use Laragear\TwoFactor\Models\TwoFactorAuthentication;

public function register(): void
{
    TwoFactorAuthentication::$useTable = 'my_custom_table';
}

Laravel Octane 兼容性

  • 没有使用过时应用程序实例的单例。
  • 没有使用过时配置实例的单例。
  • 没有使用过时请求实例的单例。
  • 在请求过程中没有写入静态属性。

在使用此软件包时,与Laravel Octane没有问题。

安全

当使用《登录助手》时,凭据将被加密保存到会话中。这可能对某些应用程序来说是不理想的。虽然此机制存在是为了方便,但您也可以使用此软件包创建自己的2FA身份验证流程来避免凭据的《闪现》。

一个替代方案是在网站范围内使用《2fa.confirm》,并将配置密钥《two-factor.confirm.time》设置为《INF》。

如果您发现任何安全问题,请通过电子邮件darkghosthunter@gmail.com而不是使用问题跟踪器。

许可协议

此特定软件包版本根据发布时的《MIT许可协议》许可,MIT License

LaravelTaylor Otwell的商标。版权所有©2011-2024 Laravel LLC。