airondev/laravel-2fa

此包允许您在Laravel应用程序中启用双因素认证。它本地存储令牌并通过电子邮件、短信或任何自定义通道通知用户。包括原生条件性检查,以根据已知设备、IP地址或IP位置触发或跳过2FA。

dev-main 2024-02-06 13:39 UTC

This package is auto-updated.

Last update: 2024-09-06 14:58:26 UTC


README

Software License Latest Version on Packagist Total Downloads

简介

此包允许您非常容易地在Laravel应用程序中启用双因素认证,无需添加中间件或修改路由。它在数据库中存储令牌,并使用独立的表,因此您无需更改users表。通过邮件、短信或任何自定义通道通知用户令牌。

包括原生条件性检查以触发或跳过2FA:当用户使用已知浏览器、IP地址、IP地理位置或任何自定义规则时,您可以跳过检查。

此包受到了srmklive/laravel-twofactor-authentication包的启发,该包支持Authy 2FA认证。

安装

  1. 使用Composer安装包
composer require Airondev-agency/laravel-2fa
  1. 将服务提供者添加到config/app.php文件中的providers数组中
'providers' => [   
    [...]
    /*
     * Package Service Providers...
     */
    Airondev\Laravel2FA\Laravel2FAServiceProvider::class,
],
  1. 运行以下命令发布资产
php artisan vendor:publish --provider "Airondev\Laravel2FA\Laravel2FAServiceProvider"
  1. 运行以下命令迁移数据库
php artisan migrate
  1. 在您的用户模型(例如 App\Models\User.php)中添加以下行
  • 在类声明之前添加以下行
use Airondev\Laravel2FA\TwoFactorAuthenticatable;
use Airondev\Laravel2FA\Contracts\TwoFactorAuthenticatableContract;
  • 修改类定义以实现TwoFactorAuthenticatableContract合同
class User extends Authenticatable implements AuthenticatableContract,
                                              AuthorizableContract,
                                              CanResetPasswordContract,
                                              TwoFactorAuthenticatableContract
  • 添加TwoFactorAuthenticatable特质
use Authenticatable,
    Authorizable, 
    CanResetPassword, 
    TwoFactorAuthenticatable;
  1. 确保您的用户模型使用了Notifiable特质

  2. 您需要通过向app\Http\Controllers\Auth\LoginController.php类中添加authenticated方法来更改登录流程。

<?php

namespace App\Http\Controllers\Auth;

use Airondev\Laravel2FA\TwoFactorAuth;

class LoginController extends Controller
{
    /** [...] **/

    /**
     * The user has been authenticated.
     *
     * @param  \Illuminate\Http\Request  $request
     * @param  mixed  $user
     * @return mixed
     */
    protected function authenticated(Request $request, $user)
    {
        # Trigger 2FA if necessary.
        if (TwoFactorAuth::getDriver()->mustTrigger($request, $user)) {
            return TwoFactorAuth::getDriver()->trigger($request, $user);
        }

        # If not, do the usual job.
        return redirect()->intended($this->redirectPath());
    }      

🚀 您也可以使用您最喜欢的简短版本

    /**
     * The user has been authenticated.
     *
     * @param  \Illuminate\Http\Request  $request
     * @param  mixed  $user
     * @return mixed
     */
    protected function authenticated(Request $request, $user)
    {
        return TwoFactorAuth::getDriver()->maybeTrigger($request, $user) 
                ?: redirect()->intended($this->redirectPath());
    }

就是这样!现在您想自定义视图并查看配置部分。

构建视图

当您发布包资产时,将创建一个新的resources/views/auth/2fa/token.blade.php文件。您可以根据自己的需求设计此页面,但您必须保留token表单输入名称,并将表单发送到route('auth.2fa.store')路由。

您可能注意到一个$reason变量,它告诉您为什么触发了2FA认证。根据您的应用需求,您可以选择是否向用户显示它。

配置

所有配置都设置在当您发布包时创建的config/laravel-2fa.php文件中。

内置

首先,您需要选择要应用哪些策略。策略的工作是检查是否必须执行双因素认证,或者是否可以跳过(例如:浏览器是否已知?跳过双因素认证)。

策略在policy键中定义。规则可以组合,具有优先级顺序。每个策略都会被调用,并告诉驱动程序是否应该触发双因素认证。当策略需要双因素认证时,检查停止,其返回的message将用作视图中的$reason(参见构建视图部分)。

如果没有触发任何策略,或者 policy 数组为空,则跳过双因素认证,用户正常登录。

return [
    'policy' => [
        'browser',  // first check if we know the browser
        'geoip',    // if so, check if we know the user ip location

        // if so, no more rules : skip 2FA.
    ],
];

内置策略包括:

ℹ️ 需要创建自己的策略?请参阅下方的 自定义策略 部分。

一些策略有额外的设置,这些设置在配置文件中有自我说明。

return [
    /*
    |--------------------------------------------------------------------------
    | The 2FA package options.
    |--------------------------------------------------------------------------
    |
    | Here you may specify the package options, such as policies parameters.
    |
    */

    'options' => [
        # 2FA token lifetime in minutes.
        'token_lifetime' => 10,

        'policies' => [
            # Can be one of "country", "region", "city", "time_zone".
            'geoip'   => 'country',
            
            # Cookie expiration time in minutes (default 30 days).
            'browser' => 30 * 1440,
        ],
    ],
];

自定义通知

此包使用 Laravel 的 通知 系统。内置通知 TwoFactorToken 通过邮件将双因素令牌发送给用户。

您可以通过扩展此类来扩展此通知并配置其他通道,例如 短信

<?php

namespace App\Notifications;

use Airondev\Laravel2FA\Notifications\TwoFactorToken as BaseTwoFactorToken;

class TwoFactorToken extends BaseTwoFactorToken
{
    /**
     * Get the notification's delivery channels.
     *
     * @param  mixed  $notifiable
     * @return array
     */
    public function via($notifiable)
    {
        return [
            'nexmo',
        ];
    }

    /**
     * Get the Vonage / SMS representation of the notification.
     *
     * @param  mixed  $notifiable
     * @return NexmoMessage
     */
    public function toNexmo($notifiable)
    {
        return (new NexmoMessage)
                    ->content('Your two-factor token is ' . $this->token)
                    ->from('MYAPP');
    }
}

您需要更改 notification 配置键以指定您的新通知类

return [
    [...]
    /*
    |--------------------------------------------------------------------------
    | The 2FA notification containing the token.
    |--------------------------------------------------------------------------
    |
    | Here you may specify an alternative notification to use.
    |
    */

    'notification' => \App\Notifications\TwoFactorToken::class,
];

自定义策略

如果您对内置策略不满意,可以覆盖现有策略或创建自己的策略。
所有策略都必须扩展 AbstractPolicy

要覆盖现有策略,您可以直接扩展策略类

<?php

namespace App\Auth\Policies;

use Airondev\Laravel2FA\Policies\IpPolicy as BaseIpPolicy;

class IpPolicy extends BaseIpPolicy
{
    /**
     * Check that the request passes the policy.
     * If this return false, the 2FA Auth will be triggered.
     *
     * @return bool
     */
    public function passes(): bool
    {
        # Passes the check if the user didn't activate IpPolicy on his account.
        if ( ! $this->user->hasTwoFactorAuthActiveForIp()) {
            return true;
        }

        # Else, run the IpPolicy check.
        return parent::passes();
    }
    
    /**
     * The reason sent to the Notification and the frontend view,
     * to tell the user why the 2FA check was triggered.
     *
     * @return string
     */
    public function message(): string
    {
        return $this->message ?: __('your account activated 2FA for unknown IP adresses.');
    }
}

然后,更改设置中的 mapping 数组

return [
    [...]

    'mapping' => [
        [...]
        'ip' => \Auth\Policies\IpPolicy::class,
    ],
];

ℹ️ AbstractPolicy 有 3 个可用属性,您可以在 passes() 方法中用于构建策略检查

/**
 * The incomming request at login.
 * 
 * @var \Illuminate\Http\Request
 */
protected $request = null;

/**
 * The user that just loggued in.
 * 
 * @var \Airondev\Laravel2FA\Contracts\TwoFactorAuthenticatableContract
 */
protected $user = null;

/**
 * The login attempt, with UID and IP address data.
 * 
 * @var \Airondev\Laravel2FA\Models\LoginAttempt
 */
protected $attempt = null;

创建策略很简单。例如,假设用户可能在其设置中激活 2FA。您可以创建一个策略来验证用户是否激活了 2FA,如果是,则 passes() 方法失败,从而导致触发 2FA 认证

<?php

namespace App\Auth\Policies;

use Airondev\Laravel2FA\Policies\AbstractPolicy;

class ActivePolicy extends AbstractPolicy
{
    /**
     * Check that the request passes the policy.
     * If this return false, the 2FA Auth will be triggered.
     *
     * @return bool
     */
    public function passes(): bool
    {
        return $this->user->hasTwoFactorAuthActive() ? false : true;
    }

    /**
     * The reason sent to the Notification and the frontend view,
     * to tell the user why the 2FA check was triggered.
     *
     * @return string
     */
    public function message(): string
    {
        return $this->message ?: __('your account activated the 2FA auth');
    }
}

您还可以有不同的检查,这些检查会产生不同的 $reason 消息

<?php

namespace App\Auth\Policies;

use Airondev\Laravel2FA\Policies\AbstractPolicy;

class ActivePolicy extends AbstractPolicy
{
    /**
     * Check that the request passes the policy.
     * If this return false, the 2FA Auth will be triggered.
     *
     * @return bool
     */
    public function passes(): bool
    {
        if ($this->user->hasTwoFactorAuthActive()) {
            $this->message = __('your account activated the 2FA auth');
            return false;
        }
        
        if ($this->user->didntSpecifyTwoAuthActive()) {
            $this->message = __('2FA auth is activated by default');
            return false;
        }

        if (anyReason()) {
            return false; // will use the default reason used in message() method.
        }

        return true;
    }

    /**
     * The reason sent to the Notification and the frontend view,
     * to tell the user why the 2FA check was triggered.
     *
     * @return string
     */
    public function message(): string
    {
        return $this->message ?: __('2FA auth is automatically activated for your account');
    }
}

创建策略后,您可以在配置文件中使用它

return [
    'policy' => [
        \Auth\Policies\ActivePolicy::class,
    ],
];

甚至更好,您可以创建一个简短名称来保持您的 policy 数组整洁!

return [
    'policy' => [
        'active',  // your new rule !
        'browser', // if 2FA is not activated for the account, will check anyways if the browser is known
    ],

    [...]

    'mapping' => [
        [...]
        'active' => \Auth\Policies\ActivePolicy::class,
    ],
];

一些策略需要在用户使用 2FA 成功登录时执行操作(例如:写入 cookie 或在数据库中写入某些内容)。您可以在策略的 onSucceed() 方法中定义您的回调

    /**
     * An action to perform on successful 2FA login.
     * May be used to remember stuff for the next policy check.
     *
     * @return void
     */
    public function onSucceed(): void
    {
        Cookie::queue(
            '2fa_remember',
            $this->attempt->uid,
            1440
        );
    }

自定义驱动器

如果您在整个过程中需要更多的灵活性,您可以扩展 BaseDriver 类并通过覆盖任何方法来更改其工作流程。

namespace App\Auth\Drivers;

use Airondev\Laravel2FA\Drivers\BaseDriver;
use Airondev\Laravel2FA\Contracts\TwoFactorAuthenticatableContract as Authenticatable;

class CustomDriver extends BaseDriver
{
    /**
     * Check if must trigger 2FA token for this user.
     *
     * @param \Illuminate\Http\Request $request
     * @param \Airondev\Laravel2FA\Contracts\TwoFactorAuthenticatableContract $user
     *
     * @return bool
     */
    public function mustTrigger(Request $request, Authenticatable $user): bool
    {
        // custom workflow.
    }
}

不要忘记在配置文件中更新 driver

return [
    'driver' => \App\Auth\Drivers\CustomDriver::class;
];

⚠️ 如果您希望从头开始构建驱动器,您必须实现 TwoFactorDriverContract

贡献

请随意为此包做出贡献!
如果您发现任何安全问题,请通过 thomas@Airondev.agency 联系我,而不是创建公开的 GitHub 问题。

首次贡献指南

致谢

许可证

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

laravel-2fa

laravel-2fa