Symfony 在身份验证上添加约束

Symfony 在身份验证上添加约束,symfony,authentication,Symfony,Authentication,实际上,我在AuthenticationEvents::AUTHENTICATION\u FAILURE上有一个侦听器,它在Redis缓存中存储failedLogin,如: [ 'ip' => [ 'xxx.xxx.xxx.xxx' => [ 'nbAttempts' => 5, 'lastAttempd' => \DateTime ], ], 'username' => [ 'my_login' =>

实际上,我在
AuthenticationEvents::AUTHENTICATION\u FAILURE
上有一个侦听器,它在Redis缓存中存储
failedLogin
,如:

[
  'ip' => [
    'xxx.xxx.xxx.xxx' => [
      'nbAttempts' => 5,
      'lastAttempd' => \DateTime
    ],
  ],
  'username' => [
    'my_login' => [
      'nbAttempts' => 3,
      'lastAttempd' => \DateTime
    ],
    'my_other_login' => [
      'nbAttempts' => 2,
      'lastAttempd' => \DateTime
    ],
  ]
]
但是现在,我需要使用这个失败列表来阻止用户在n分钟内尝试连接用户名超过x次时登录,对于IP也是如此(具有其他比率)。(稍后,可能会在块之前添加一个ReCaptcha)

为此,我需要在登录名上添加自定义验证规则。我在文档中找到了它:

但是,在这两个文档中,我需要重写很多内容,但我希望保留所有实际行为:在上一页重定向用户(使用referer或在默认页),记住我吗(在gurad中,我被迫在成功时返回响应,否则请记住我不工作,但我真的不知道哪个响应返回…因为如果我返回null,重定向工作正常)、消息等

我已经搜索了Symfony默认使用的复制/粘贴保护,但没有找到,只添加了一条规则

有人知道另一种方式,就是重写
checkCredential

非常感谢

编辑(参见结尾处的答案):
我发现了一个高级的guard抽象类:
Symfony\Component\Security\guard\Authenticator\AbstractFormLoginAuthenticator
。然后,身份验证就像在Symfony中一样工作,现在,我只需要在checkCredentials中添加我自己的测试(在getUser()中),我希望在检索用户之前返回错误。

您可以侦听事件以查找失败的登录尝试。创建服务:

services:
    app.failed_login_listener:
    class: AppBundle\EventListener\AuthenticationFailureListener
    tags:
        - { name: kernel.event_listener, event: security.authentication.failure, method: onAuthenticationFailure }
然后创建侦听器:

<?php

namespace App\EventListener;

use Symfony\Component\HttpFoundation\Request;
use Symfony\Component\Security\Core\Exception\AuthenticationException;
use Symfony\Component\Security\Http\Authentication\AuthenticationFailureHandlerInterface;

class AuthenticationFailureListener implements AuthenticationFailureHandlerInterface
{
    public function onAuthenticationFailure(
        Request $request,
        AuthenticationException $exception
    ) {
        // do whatever
    }
}
<?php

namespace App\EventListener;

use Symfony\Component\Security\Http\Event\InteractiveLoginEvent;

class InteractiveLoginListener
{
    public function onInteractiveLogin(InteractiveLoginEvent $event)
    {
        // do whatever
    }
}
然后让你的听众:

<?php

namespace App\EventListener;

use Symfony\Component\HttpFoundation\Request;
use Symfony\Component\Security\Core\Exception\AuthenticationException;
use Symfony\Component\Security\Http\Authentication\AuthenticationFailureHandlerInterface;

class AuthenticationFailureListener implements AuthenticationFailureHandlerInterface
{
    public function onAuthenticationFailure(
        Request $request,
        AuthenticationException $exception
    ) {
        // do whatever
    }
}
<?php

namespace App\EventListener;

use Symfony\Component\Security\Http\Event\InteractiveLoginEvent;

class InteractiveLoginListener
{
    public function onInteractiveLogin(InteractiveLoginEvent $event)
    {
        // do whatever
    }
}

最后,我通过扩展这个抽象类找到了一个简单的方法:
Symfony\Component\Security\Guard\Authenticator\AbstractFormLoginAuthenticator
。这个验证器取代了Symfony使用的默认FormLoginAuthenticator,但非常简单,我们只重写了几个方法

可能只是找到了一种获取config.yml值的方法,以定义路由(避免将其写入此文件,因为我们在config中声明它)

我的服务声明:

app.security.form_login_authenticator:
    class: AppBundle\Security\FormLoginAuthenticator
    arguments: ["@router", "@security.password_encoder", "@app.login_brute_force"]
我的FormLoginAuthenticator:

<?php

namespace AppBundle\Security;

use AppBundle\Utils\LoginBruteForce;
use Symfony\Component\HttpFoundation\Request;
use Symfony\Component\Routing\Router;
use Symfony\Component\Security\Core\Encoder\UserPasswordEncoderInterface;
use Symfony\Component\Security\Core\Exception\AuthenticationException;
use Symfony\Component\Security\Core\Exception\UsernameNotFoundException;
use Symfony\Component\Security\Core\User\UserInterface;
use Symfony\Component\Security\Core\User\UserProviderInterface;
use Symfony\Component\Security\Guard\Authenticator\AbstractFormLoginAuthenticator;

class FormLoginAuthenticator extends AbstractFormLoginAuthenticator
{
    private $router;
    private $encoder;
    private $loginBruteForce;

    public function __construct(Router $router, UserPasswordEncoderInterface $encoder, LoginBruteForce $loginBruteForce)
    {
        $this->router = $router;
        $this->encoder = $encoder;
        $this->loginBruteForce = $loginBruteForce;
    }

    protected function getLoginUrl()
    {
        return $this->router->generate('login');
    }

    protected function getDefaultSuccessRedirectUrl()
    {
        return $this->router->generate('homepage');
    }

    public function getCredentials(Request $request)
    {
        if ($request->request->has('_username')) {
            return [
                'username' => $request->request->get('_username'),
                'password' => $request->request->get('_password'),
            ];
        }

        return;
    }

    public function getUser($credentials, UserProviderInterface $userProvider)
    {
        $username = $credentials['username'];

        // Check if the asked username is under bruteforce attack, or if client process to a bruteforce attack
        $this->loginBruteForce->isBruteForce($username);

        // Catch the UserNotFound execption, to avoid gie informations about users in database
        try {
            $user = $userProvider->loadUserByUsername($username);
        } catch (UsernameNotFoundException $e) {
            throw new AuthenticationException('Bad credentials.');
        }

        return $user;
    }

    public function checkCredentials($credentials, UserInterface $user)
    {
        // check credentials - e.g. make sure the password is valid
        $passwordValid = $this->encoder->isPasswordValid($user, $credentials['password']);

        if (!$passwordValid) {
            throw new AuthenticationException('Bad credentials.');
        }

        return true;
    }
}

您可以为该事件编写侦听器。请参见此处:。为
安全性。身份验证。失败
事件编写侦听器谢谢,但我已经有了一个存储失败的侦听器,现在我想在登录中添加一个验证签入,其中存储了失败。谢谢,但我已经有一个存储失败的侦听器,现在我想添加一个v验证签入登录,存储失败。更新了我的答案。谢谢,我也编辑了我的帖子,最后我找到了一种方法,通过实现
AbstractFormLoginAuthenticator
,轻松完成CustomAuthenticator。很高兴你找到了另一种方法,这似乎是一种很好的方法。我将签出它,并可能在将来使用类似的方法