hydrat-agency/laravel-2fa

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

1.1.2 2022-06-22 08:08 UTC

This package is auto-updated.

Last update: 2024-09-10 14:05:53 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 hydrat-agency/laravel-2fa
  1. 将服务提供者添加到config/app.php文件中的providers数组中,如下所示
'providers' => [   
    [...]
    /*
     * Package Service Providers...
     */
    Hydrat\Laravel2FA\Laravel2FAServiceProvider::class,
],
  1. 运行以下命令发布资产
php artisan vendor:publish --provider "Hydrat\Laravel2FA\Laravel2FAServiceProvider"
  1. 运行以下命令迁移数据库
php artisan migrate
  1. 在您的用户模型(例如App\Models\User.php)中添加以下行
  • 在类声明之前添加以下行
use Hydrat\Laravel2FA\TwoFactorAuthenticatable;
use Hydrat\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 Hydrat\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任务是为了检查是否必须进行双因素认证,或者是否可以跳过(例如:浏览器是否已知?跳过双因素认证)。

策略定义在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 Hydrat\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 Hydrat\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 \Hydrat\Laravel2FA\Contracts\TwoFactorAuthenticatableContract
 */
protected $user = null;

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

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

<?php

namespace App\Auth\Policies;

use Hydrat\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 Hydrat\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 Hydrat\Laravel2FA\Drivers\BaseDriver;
use Hydrat\Laravel2FA\Contracts\TwoFactorAuthenticatableContract as Authenticatable;

class CustomDriver extends BaseDriver
{
    /**
     * Check if must trigger 2FA token for this user.
     *
     * @param \Illuminate\Http\Request $request
     * @param \Hydrat\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@hydrat.agency与我联系,而不是创建公开的github问题。

首次贡献指南

致谢

许可协议

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