yeebase/twofactorauthentication

为 Neos Flow 实现的双因素认证 (2FA)

3.0.0 2020-02-24 12:31 UTC

This package is auto-updated.

Last update: 2024-08-24 22:01:22 UTC


README

Yeebase.TwoFactorAuthentication Flow 包包含对 Flow 认证机制的扩展,使得您能够轻松实现双因素认证 (2FA)。

它提供了一个新的 认证提供者,可以与现有提供者一起使用,以通过一次性密码 (OTP) 启用 2FA。

安装

此包可以通过 composer 安装

composer require yeebase/twofactorauthentication

此包需要一个名为 yeebase_twofactorauthentication_secret 的新数据库表,可以通过以下方式添加:

./flow doctrine:migrate

配置

以下部分描述了将双因素认证包集成到现有 Flow 应用程序中的过程。安装后,双因素认证默认对所有系统账户都是禁用的。

认证提供者

此包提供了一个 TwoFactorAuthenticationProvider,需要除现有提供者之外进行配置。此外,必须将 authenticationStrategy 设置为 allTokens,以确保两个提供者都被考虑在内。

示例

Settings.yaml:

Neos:
  Flow:
    security:
      authentication:
        authenticationStrategy: 'allTokens'
        providers:
          'Some.Package:Default':
            # That assumes that the "PersistedUsernamePasswordProvider" is used as base authentication:
            provider: 'PersistedUsernamePasswordProvider'

          'Some.Package:2FA':
            provider: 'Yeebase\TwoFactorAuthentication\Security\Authentication\Provider\TwoFactorAuthenticationProvider'

应用程序名称和路由

如果有 TwoFactorAuthenticationProvider

Settings.yaml:

Yeebase:
  TwoFactorAuthentication:
    # This is the "issuer" that will be displayed in the authenticator app like: <issuer> (<holder>)
    applicationName: 'Some Application'
    routes:
      login:
        '@package':    'Some.Package'
        '@controller': 'Login'
        '@action':     'twoFactor'

Login/TwoFacor.html

...
<f:form action="authenticate">
    <div class="form-group">
        <label for="otp">2FA Code</label>
        <f:form.textfield name="__authentication[Yeebase][TwoFactorAuthentication][Security][Authentication][Token][OtpToken][otp]" id="otp" additionalAttributes="{autofocus: true, autocomplete: 'off'}" />
    </div>
    <f:form.submit value="Enter" />
</f:form>
...

而不是使用默认的 UsernamePasswordProvider,将您的设置更改为使用以下提供者: Yeebase\TwoFactorAuthentication\Security\Authentication\Provider\TwoFactorAuthenticationProvider

强制双因素认证

默认情况下,2FA 可以按账户启用,如果不为已认证的账户启用,则不是必需的。为了 要求 用户使用双因素认证登录,可以设置 Yeebase.TwoFactorAuthentication.requireTwoFactorAuthentication 标志。这样一来,每次账户认证时都必须 指定 一次性密码。为了避免在给定账户尚未启用 2FA 时导致异常,可以配置一个 设置,允许用户初始化 2FA。

Settings.yaml:

Yeebase:
  TwoFactorAuthentication:
    requireTwoFactorAuthentication: true
    routes:
      # ...
      setup:
        '@package':    'Some.Package'
        '@controller': 'TwoFactorAuthenticationSetup'
        '@action':     'index'

相应的设置控制器(示例)

TwoFactorAuthenticationSetupController.php

<?php
declare(strict_types=1);
namespace Some\Package\Controller;

use Neos\Error\Messages\Message;
use Neos\Flow\Annotations as Flow;
use Neos\Flow\Mvc\Controller\ActionController;
use Neos\Flow\Security\Account;
use Neos\Flow\Security\Context;
use Neos\Flow\Security\Exception\AccessDeniedException;
use Yeebase\TwoFactorAuthentication\Domain\ValueObjects\OneTimePassword;
use Yeebase\TwoFactorAuthentication\Domain\ValueObjects\SecretWithHmac;
use Yeebase\TwoFactorAuthentication\Exception\InvalidOtpException;
use Yeebase\TwoFactorAuthentication\Service\TwoFactorAuthenticationService;

class TwoFactorAuthenticationSetupController extends ActionController
{

    /**
     * @var Account
     */
    private $authenticatedAccount;

    /**
     * @Flow\Inject
     * @var Context
     */
    protected $securityContext;

    /**
     * @Flow\Inject
     * @var TwoFactorAuthenticationService
     */
    protected $twoFactorAuthenticationService;

    protected function initializeAction(): void
    {
        parent::initializeAction();
        $this->authenticatedAccount = $this->securityContext->getAccountByAuthenticationProviderName('Some.Package:Default');
        if ($this->authenticatedAccount === null) {
            throw new AccessDeniedException('...');
        }
    }

    public function indexAction(): void
    {
        $twoFactorAuthenticationEnabled = $this->twoFactorAuthenticationService->isTwoFactorAuthenticationEnabledFor($this->authenticatedAccount);
        $this->view->assign('2faEnabled', $twoFactorAuthenticationEnabled);
        if (!$twoFactorAuthenticationEnabled) {
            $holder = $this->authenticatedAccount->getAccountIdentifier();
            $qrCode = $this->twoFactorAuthenticationService->generateActivationQrCode($holder);
            $this->view->assignMultiple([
                'secretWithHmac' => SecretWithHmac::fromSecret($qrCode->getSecret()),
                'qrCode' => $qrCode->renderSvg(200),
            ]);
        }
    }

    public function enableAction(SecretWithHmac $secretWithHmac, OneTimePassword $otp): void
    {
        try {
            $this->twoFactorAuthenticationService->enableTwoFactorAuthentication($this->authenticatedAccount, $secretWithHmac->getSecret(), $otp);
        } catch (InvalidOtpException $exception) {
            $this->addFlashMessage('Invalid One-time Password', 'Invalid OTP', Message::SEVERITY_ERROR);
            $this->redirect('index');
        }
        $this->addFlashMessage('Two-Factor-Authentication was activated!', '2FA enabled', Message::SEVERITY_OK);
        $this->redirect('index');
    }

    public function disableAction(): void
    {
        $this->twoFactorAuthenticationService->disableTwoFactorAuthentication($this->authenticatedAccount);
        $this->addFlashMessage('Two-Factor-Authentication was deactivated!', '2FA disabled', Message::SEVERITY_NOTICE);
        $this->redirect('index');
    }
}

相应的模板(示例)

TwoFactorAuthenticationSetup/Index.html:

<h2>Two-Factor Authentication</h2>
<f:if condition="{2faEnabled}">
	<f:then>
		<ul>
			<li>2FA is active</li>
		</ul>
		<f:form action="disable">
			<f:form.submit value="disable 2FA" />
		</f:form>
	</f:then>
	<f:else>
		<ul>
			<li>2FA is not active</li>
		</ul>
		<f:form action="enable">
			<div>
				{qrCode -> f:format.raw()}
			</div>
            <label for="otp">2FA Code</label>
            <f:form.hidden name="secretWithHmac" value="{secretWithHmac}" />
            <f:form.textfield name="otp" id="otp" additionalAttributes="{autofocus: true, pattern: '\d\d\d\d\d\d'}" required="true" title="OTP (Format: ######)" />
			<f:form.submit value="enable 2FA" />
		</f:form>
	</f:else>
</f:if>

为了允许用户最初设置 2FA,必须允许调用相应的操作,即使账户尚未启用 2FA。这可以通过提供的 ExcludeTwoFactorAuthenticationSetup 请求模式 实现,该请求模式禁用了上面配置的 setup 路由的 2FA 认证提供者

Settings.yaml:

Neos:
  Flow:
    security:
      authentication:
        providers:
          # ...

          'Some.Package:2FA':
            requestPatterns:
              'Some.Package:2FASetup':
                pattern: 'Yeebase\TwoFactorAuthentication\Security\RequestPattern\ExcludeTwoFactorAuthenticationSetup'

注意: ExcludeTwoFactorAuthenticationSetup 将禁用配置控制器中所有操作的 2FA,因此控制器不应执行任何需要进一步检查的临界任务。

许可证

此包根据 MIT 许可证授权 - 详细信息请参阅 LICENSE 文件。

致谢

此包依赖于 google2fa 包 用于生成和验证密钥/OTP,以及 BaconQrCode 用于二维码渲染。