brackets/验证

为 Laravel 提供的双因素认证(2FA)和基于代码的动作验证(短信或电子邮件)

v0.1.2 2021-08-11 12:37 UTC

README

此包应用于基于代码的动作验证(通常是路由)。

当前包支持两种发送验证码的渠道

  • 短信
  • 电子邮件

但很容易扩展包以支持自定义渠道。

包还包含一个简单的前端(用户可以输入代码的屏幕 + 默认电子邮件/短信模板),可以轻松覆盖以满足您的用户体验。

包还可以帮助您处理验证的特殊情况 - 双因素认证

安装

  1. composer require brackets/verifications

  2. php artisan verifications:install

使用方法

实现 Verifiable

首先,您需要一个经过身份验证的用户(即 User 模型),该模型实现 Verifiable 接口并使用 VerifiableTrait。您需要在模型上定义 email 和/或 phone 属性,或者实现访问器方法 getPhoneAttribute()/getEmailAttribute()

注意:如果您需要将 phone_number/email 属性插入到您的 user 表中,可以使用 artisan 命令 verifications:add-email {table-name} 和/或 verifications:add-phone {table-name}

配置

然后您需要定义一个需要验证的操作。

这可以通过配置文件 /config/verifications.php 实现。

    'enabled' => env('VERIFICATION_ENABLED', true),                     // you can enable/disable globally (i.e. disabled for tests/dev env)
    'actions' => [
        'my-action' => [
            'enabled' => true,                                          // you can enable/disable single action
            'channel' => 'sms',                                         // currently: sms, email
            'form_template' => 'brackets/verifications::verification',  // blade name with namespace used for verification code form
            'expires_in' => 15,                                         // specifies how many minutes does it take to require another code verification for the same action
            'expires_from' => 'verification',                           // one of: 'last-activity' or 'verification', specifies what triggers the expiration (see expires_in)
            'code' => [
                'type' => 'numeric',                                    // specifies the type of verification code, can be one of: 'numeric' or 'string'
                'length' => 6,                                          // specifies the verification code length, defaults to 6
                'expires_in' => 10,                                     // specifies how many minutes is the code valid
            ]
        ]
    ]

GET 请求验证

通常,您使用此包来保护应用程序的一些特定区域的入口。这可以通过使用 VerificationMiddleware 中间件保护所有路由来实现。

Route::middleware('verifications.verify:{my-action}')

示例
假设我们想验证秘密 money-balance 屏幕。

在您的配置中定义操作

    'enabled' => env('VERIFICATION_ENABLED', true),
    'actions' => [
        'money-balance' => [
            'enabled' => true,
            'channel' => 'sms',
            'form_template' => 'brackets/verifications::verification',
            'expires_in' => 15,
            'expires_from' => 'verification',
            'code' => [
                'type' => 'numeric',
                'length' => 6,
                'expires_in' => 10,
            ]
        ]
    ]

并保护路由

Route::get('/{account}/money-balance', 'MoneyController@moneyBalance')
    ->name('money-balance')
    ->middleware('verifications.verify:money-balance');

当用户尝试访问 /{account}/money-balance URL 时,他将被重定向到验证屏幕,需要提供发送给他的代码。

POST 请求验证

验证 POST 动作有点棘手,因为用户不能被重定向回 POST 请求(这是技术上不可能的)。

当然,您可以在用户验证后允许对某些 POST 动作的访问。一旦他完成了验证,一切都会对他顺畅。

您应该始终创建一个显示用户可以执行操作的屏幕的 GET 路由,并保护此 GET 路由(这意味着保护用户可以执行 POST 操作的区域入口)。在这种情况下,用户永远不会遇到需要点击同一操作两次的奇怪行为。

您有两个选择

  1. 或者确保用户在执行 POST 操作之前始终在某个 GET 路由上验证(因此限制可以执行 POST 操作的区域的入口),
  2. 或者使用一些方便的 JavaScript 创建一个虚拟屏幕,在加载时自动运行代表用户的 POST 请求,这样他就不必点击两次

使用哪种选项取决于您的具体用例。但通常,如果操作需要用户向表单输入数据,则 showForm GET 路由应该始终受到保护等。

示例
让我们继续使用MoneyApp示例,但现在我们想要保护money-withdraw动作。

保护POST路由的方法非常相似。

Route::post('/{account}/money-withdraw', 'MoneyController@moneyWithdraw')
    ->name('money-withdraw')
    ->middleware('verifications.verify:money-withdraw');

这肯定可以防止未验证用户提取资金。但这并没有解决重定向问题。让我们来解决它。

如果我们仔细想想,我们实际上想要保护的是资金提取功能的GET路由,而不仅仅是最终的提交按钮。

所以让我们添加一个GET路由

Route::get('/{account}/money-withdraw', 'MoneyController@moneyWithdrawForm')
    ->name('money-withdraw-form')
    ->middleware('verifications.verify:money-withdraw');

Route::post('/{account}/money-withdraw', 'MoneyController@moneyWithdraw')
    ->name('money-withdraw')
    ->middleware('verifications.verify:money-withdraw');

方法moneyWithdrawForm会在提交时显示一个表单,该表单会执行POST到/{account}/money-withdraw。但在GET路由中,用户已经进行了验证,所以他的用户体验将会很流畅。

提示:当然,你可以为整个路由组添加中间件

Route::middleware(['verifications.verify:money-balance'])->group(static function () {
    Route::get('/{account}/money-balance', 'MoneyController@moneyBalance')
        ->name('money-balance');
        
    Route::get('/{account}/money-withdraw', 'MoneyController@moneyWithdrawAutoConfirmator')
        ->name('money-withdraw-auto-confirmator');
        
    Route::post('/{account}/money-withdraw', 'MoneyController@moneyWithdraw')
        ->name('money-withdraw');
    // ...
});

高级定制用例

在某些场景下,你可能想在某些动作上使用验证并进行进一步定制(例如,只有当满足某些其他条件时才进行验证,或为POST动作验证提供自定义的redirectTo方法)。你可以使用验证外观(Verification facade)在你的控制器中手动运行验证,提供一个闭包,该闭包只有在验证成功后才会运行。

public function postDownloadInvoice(Invoice $invoice)
{
    // this code will run on the attempt before the verification and then again, after the successful verification
    if (!$invoice->isPublished()) {
        throw new InvoiceNotPublishedException();  
    }  

    return Verification::verify('download-invoice',             // name of the action
                                '/invoices',                    // URL user will be redirect after verification (he must click to download the invoice again manually :( 
                                function () use ($invoice) {
                                    // on the other hand this code will run only once, after the verification
                                    return $invoice->download();
                                });
}

注意,即使在当前场景下,你也需要在POST之前保护GET路由,否则用户将无法被重定向到验证提示屏幕,他将无法继续操作。

定制视图

要自定义默认的blade视图,你只需要使用以下命令发布它们

php artisan vendor:publish --provider="Brackets\Verifications\VerificationServiceProvider" --tag="views"

条件验证

在某些情况下,你可能希望为用户提供一个选项,让他们选择是否应该验证某些特定动作。或者你可能希望允许具有某些特定角色/权限的用户在某些特定动作上跳过验证。在这些情况下,你只需要定义严格命名的isVerificationRequired(string $action)方法。

示例

class User extends Authenticatable implements Verifiable
{
    use VerifiableTrait;
    // ...

    public function isVerificationRequired($action) {
    
        // allow super admin to all actions
        if ($this->hasRole('Admin') {
            return false;
        }
    
        if ($action == 'withdraw-money') {
            // allow withdraw-money action to be optional (i.e. user can set it in their profile)
            if (!$this->withdraw_monoey_requires_verification) {
                return false;
            }        
        }
        
        return true; 
    }

双因素认证

此包的使用有一个特殊用例是双因素认证。

想象一下简单的场景,其中所有用户都需要进行双因素认证。

首先,将新的2FA动作添加到配置中

    'actions' => [
        '2FA' => [
            'enabled' => true,
            'channel' => 'sms',
            'expires_in' => 15,
            'expires_from' => 'last_activity', 
            'code' => [
                // ...
            ],
        ],
    ]

然后保护所有你的路由

Route::middleware([‘verifications.verify:2FA’])->group(function() {

    // all your routes goes here
    
})

通道

该包提供了两个默认通道 - 邮件和短信。

邮件

该包使用Laravel的默认邮件外观来发送邮件,所以请确保正确配置。

短信

该包提供了一个短信提供商 - Twilio。

要使用Twilio,你只需要在你的.env文件中提供这些变量

TWILIO_SID="INSERT YOUR TWILIO SID HERE"
TWILIO_AUTH_TOKEN="INSERT YOUR TWILIO TOKEN HERE"
TWILIO_NUMBER="INSERT YOUR TWILIO NUMBER IN [E.164] FORMAT"

查看这篇博客文章以获取有关Twilio集成的更多信息。

安全

如果你发现任何安全相关的问题,请通过电子邮件pavol.perdik@brackets.sk联系,而不是使用问题跟踪器。

致谢

许可证

MIT许可证(MIT)。有关更多信息,请参阅许可证文件