在symfony 3登录中实现recaptcha的正确方法是什么?



我在两个不同的防火墙中使用form_login,一个用于用户,一个用于管理员。我希望这只影响用户。

在登录表单上使用 recaptcha 的正确实现是什么?

我正在考虑的一些事情:

  • 新的登录表单工厂,它扩展了symfony FormLoginFactory,我可以在其中验证recaptcha或
  • 覆盖 UsernamePasswordFormAuthenticationListener,以便form_login使用验证码
  • 或验证码的新侦听器
  • 将验证码放在自己的页面上,并且仅在用户多次输入无效凭据时才显示它

我为这个问题创建了一个捆绑包:https://packagist.org/packages/syspay/login-recaptcha-bundle

旧回复:

我做了什么来解决这个问题:

我创建了一个名为 CaptchaLoginFormFactory 的新安全侦听器工厂,它具有以下内容

<?php
namespace ProjectBundleCoreBundleDependencyInjectionSecurityFactory;
use SymfonyBundleSecurityBundleDependencyInjectionSecurityFactoryFormLoginFactory;
/**
 * CaptchaLoginFormFactory
 */
class CaptchaLoginFormFactory extends FormLoginFactory
{
    /**
     * {@inheritdoc}
     */
    public function getKey()
    {
        return 'form_login_captcha';
    }
    /**
     * {@inheritdoc}
     */
    protected function getListenerId()
    {
        return 'security.authentication.listener.form_login_captcha';
    }
}

以及一个名为 CaptchaFormAuthenticationListener 的新身份验证侦听器

<?php
namespace ProjectBundleCoreBundleSecurityFirewall;
use ProjectSecurityCaptchaManager;
use ProjectSecurityExceptionInvalidCaptchaException;
use SymfonyComponentHttpFoundationRequest;
use SymfonyComponentSecurityHttpFirewallUsernamePasswordFormAuthenticationListener;
use SymfonyComponentSecurityHttpParameterBagUtils;
/**
 * CaptchaFormAuthenticationListener
 */
class CaptchaFormAuthenticationListener extends UsernamePasswordFormAuthenticationListener
{
    /** @var CaptchaManager $captchaManager */
    private $captchaManager;
    /**
     * setCaptchaManager
     *
     * @param CaptchaManager $captchaManager
     */
    public function setCaptchaManager(CaptchaManager $captchaManager)
    {
        $this->captchaManager = $captchaManager;
    }
    /**
     * {@inheritdoc}
     */
    protected function attemptAuthentication(Request $request)
    {
        if ($this->captchaManager->isCaptchaNeeded($request)) {
            $requestBag = $this->options['post_only'] ? $request->request : $request;
            $recaptchaResponse = ParameterBagUtils::getParameterBagValue($requestBag, 'g-recaptcha-response');
            if (!$this->captchaManager->isValidCaptchaResponse($recaptchaResponse, $request->getClientIp())) {
                throw new InvalidCaptchaException();
            }
        }
        return parent::attemptAuthentication($request);
    }
}

可以看出,它们通过一些更改扩展了原始 FormFactory,在我使用普通身份验证侦听器之前,我使用自己的方法来验证验证码。

然后我把它添加到 CoreBundle::build 方法中

public function build(ContainerBuilder $container)
{
    parent::build($container);
    $extension = $container->getExtension('security');
    $extension->addSecurityListenerFactory(new CaptchaLoginFormFactory());
}

并创建了服务

security.authentication.listener.form_login_captcha:
    class: ProjectBundleCoreBundleSecurityFirewallCaptchaFormAuthenticationListener
    parent: security.authentication.listener.form
    abstract: true
    calls:
        - [ setCaptchaManager, ['@project.security.captcha_manager'] ]

然后在防火墙下的security.yml中,我只使用与form_login相同的选项的新工厂form_login_captcha。这样,我可以在另一个防火墙上使用form_login而不会影响它。

这是我

在Symfony 4.4上使用订阅者使用 https://github.com/karser/KarserRecaptcha3Bundle 的解决方案。这只是一个快速的解决方案,您可能需要修改以提高灵活性。

您需要修改代码以支持在多次失败尝试后显示验证码。

我猜您正在为不同的防火墙使用不同的路由。您可以检查登录路由名称并根据需要应用验证码验证。

要点代码

登录订阅者.php:

<?php
namespace AppEventSubscriber;
use AppFormLoginType;
use SymfonyComponentEventDispatcherEventSubscriberInterface;
use SymfonyComponentFormFormFactoryInterface;
use SymfonyComponentHttpFoundationRedirectResponse;
use SymfonyComponentHttpFoundationSessionFlashFlashBagInterface;
use SymfonyComponentHttpKernelEventRequestEvent;
use SymfonyComponentHttpKernelKernelEvents;
use SymfonyComponentSecurityCoreSecurity;

class LoginSubscriber implements EventSubscriberInterface
{
    /**
     * @var FormFactoryInterface
     */
    private $formFactory;
    /**
     * @var FlashBagInterface
     */
    private $flashBag;
    public function __construct(FlashBagInterface $flashBag, FormFactoryInterface $formFactory)
    {
        $this->formFactory = $formFactory;
        $this->flashBag = $flashBag;
    }
    /**
     * @return array
     */
    public static function getSubscribedEvents()
    {
        /**
         * You can add event subscriber on KernelEvents::REQUEST with priority 9.
         * because class SymfonyBundleSecurityBundleDebugTraceableFirewallListener(responsible for registering the events for symfony firewall) has priority 8.
         */
        return array(
            KernelEvents::REQUEST => ['onLogin', 9]
        );
    }
    /**
     * @param RequestEvent $event
     */
    public function onLogin(RequestEvent $event)
    {
        if ('public_login' !== $event->getRequest()->attributes->get('_route')) {
            return;
        }
        //form generation should be in the same way (createdNamed in this case) as in LoginController
        $loginForm = $this->formFactory->createNamed(null, LoginType::class);
        if (!$loginForm->has('captcha')) {
            return;
        }
        $loginForm->handleRequest($event->getRequest());
        if (!$loginForm->isSubmitted()) {
            return;
        }
        if (!$loginForm->get('captcha')->isValid()) {
            $errors = $loginForm->get('captcha')->getErrors();
            $message = count($errors) ? $errors[0]->getMessage() : 'Failed to pass robot test';
            $this->flashBag->add(
                'error',
                $message
            );
            $session = $event->getRequest()->getSession();
            $session->set(Security::LAST_USERNAME, $loginForm->get('_username')->getData());
            //to prevent request to call next event
            $event->setResponse(new RedirectResponse($event->getRequest()->getRequestUri()));
        }
    }
}

登录类型.php

<?php
namespace AppForm;
use KarserRecaptcha3BundleFormRecaptcha3Type;
use KarserRecaptcha3BundleValidatorConstraintsRecaptcha3;
use SymfonyComponentFormAbstractType;
use SymfonyComponentFormExtensionCoreTypeCheckboxType;
use SymfonyComponentFormExtensionCoreTypeEmailType;
use SymfonyComponentFormExtensionCoreTypePasswordType;
use SymfonyComponentFormFormBuilderInterface;
use SymfonyComponentOptionsResolverOptionsResolver;
class LoginType extends AbstractType
{
    public function buildForm(FormBuilderInterface $builder, array $options)
    {
        $builder
            ->add('_username', EmailType::class, ['attr' => ['placeholder' => 'email'], 'data' => $options['lastUsername']])
            ->add('_password', PasswordType::class, ['attr' => ['placeholder' => 'password']])
            ->add('_remember_me', CheckboxType::class, ['required' => false])
        ;
        $builder->add('captcha', Recaptcha3Type::class, [
            'constraints' => new Recaptcha3(),
            'action_name' => 'login'
        ]);
    }
    public function configureOptions(OptionsResolver $resolver)
    {
        $resolver->setDefaults([
            //default csrf parameters defined in Symfony codes. without this configuratio csrf check will fail
            'csrf_field_name' => '_csrf_token',
            'csrf_token_id'   => 'authenticate',
          
            'lastUsername' => null
        ]);
    }
}

相关内容

最新更新