【问题标题】:Configuring Symfony2.4 security.yml to use custom authenticator配置 Symfony2.4 security.yml 以使用自定义身份验证器
【发布时间】:2014-07-27 22:23:43
【问题描述】:

我正在尝试将我的 Symfony2.4 应用程序配置为使用自定义身份验证器来检查数据库表以防止暴力登录尝试,我遇到了一个问题,当用户提供正确的凭据时,他们会重新引导回登录屏幕,而不是他们给定的 URL。这是我的 security.yml 文件:

security:
    encoders:
        Symfony\Component\Security\Core\User\User: plaintext
        Acme\FakeBundle\Entity\User: sha512
        Acme\FakeBundle\Entity\User: sha512

    role_hierarchy:
        ROLE_VENDOR: ROLE_USER
        ROLE_STANDARD: ROLE_USER
        ROLE_SUPER_ADMIN: [ROLE_USER, ROLE_STANDARD, ROLE_ALLOWED_TO_SWITCH]

    providers:
        users:
            id: my_custom_user_provider

    firewalls:
        assets_firewall:
            pattern:  ^/(_(profiler|wdt)|css|images|js|media|img)/
            security: false
        registration_area:
            pattern: ^(/register|/register/details|/register/success)$
            security: false
        unsecured_area:
            pattern: ^(/login(?!_check$))|^(?!support).privacy|^(?!support).terms_and_conditions
            security: false
        secured_area:
            pattern:    ^/
            simple_form:
                authenticator: my_custom_authenticator
                check_path:    /login_check
                login_path:    /login
                username_parameter: form[_username]
                password_parameter: form[_password]
                csrf_parameter: form[_token]
            logout:
                path: /logout
                target: /login
    access_control:
        - { path: ^/, roles: IS_AUTHENTICATED_FULLY, requires_channel: %force_channel% }
        - { path: ^/, roles: IS_AUTHENTICATED_ANONYMOUSLY, requires_channel:%force_channel%}

这是我的自定义用户提供程序:

<?php

namespace Acme\FakeBundle\Services;

use Doctrine\ORM\NoResultException;
use Acme\FakeBundle\Entity\User;
use Symfony\Component\DependencyInjection\ContainerInterface;
use Symfony\Component\Security\Core\Exception\AuthenticationException;
use Symfony\Component\Security\Core\Exception\UnsupportedUserException;
use Symfony\Component\Security\Core\Exception\UsernameNotFoundException;
use Symfony\Component\Security\Core\User\UserInterface;
use Symfony\Component\Security\Core\User\UserProviderInterface;
use Doctrine\ORM\EntityManager;
use Doctrine\ORM\EntityRepository;

class AcmeFakeUserProvider implements UserProviderInterface
{


    /**
     * Holds the Doctrine entity manager for database interaction
     * @var EntityManager
     */
    protected $em;

    /**
     * Fake bundle User entity repository
     * @var EntityRepository
     */
    protected $user_repo;

    /**
     * Fake bundle FloodTableEntry repository
     * @var EntityRepository
     */
    protected $flood_table_repo;

    protected $container;

    /**
     * @var \Symfony\Component\HttpFoundation\Request
     */
    protected $request;

    public function __construct(EntityManager $em, ContainerInterface $container)
    {
        $this->em = $em;
        $this->user_repo = $this->em->getRepository("AcmeFakeBundle:User");
        $this->flood_table_repo = $this->em->getRepository('AcmeFakeBundle:FloodTableEntry');
        $this->container = $container;
        $this->request = $this->container->get('request');
    }

    /**
     * @return User
     */
    public function loadUserByUsername($username)
    {
        $q = $this->user_repo
            ->createQueryBuilder('u')
            ->where('LOWER(u.username) = :username OR u.email = :email')
            ->setParameter('username', strtolower($username))
            ->setParameter('email', $username)
            ->getQuery();

        try {

            /*
             * Verify that the user has not tried to log in more than 5 times in the last 5 minutes for
             * the same username or from the same IP Address. If so, block them from logging in and notify
             * them that they must wait a few minutes before trying again.
             */
            $qb2 = $this->flood_table_repo->createQueryBuilder('f');
            $entries = $qb2
                ->where($qb2->expr()->eq('f.ipAddress', ':ipAddress'))
                ->andWhere($qb2->expr()->gte('f.attemptTime', ':fiveMinsAgo'))
                ->setParameters(
                    array(
                        'fiveMinsAgo' => date('o-m-d H:i:s',time() - 5 * 60),
                        'ipAddress' => $this->request->getClientIp(),
                    )
                )->getQuery()
                ->getResult();

            if (count($entries) >= 10) {
                throw new AuthenticationException("Too many unsuccessful login attempts. Try again in a few minutes.");
            }


            // The Query::getSingleResult() method throws an exception
            // if there is no record matching the criteria.
            $user = $q->getSingleResult();

        } catch (NoResultException $e) {
            $message = sprintf(
                'Unable to find an active admin AcmeFakeBundle:User object identified by "%s".',
                $username
            );
            throw new UsernameNotFoundException($message, 0, $e);
        }

        return $user;
    }

    /**
     * @return User
     */
    public function refreshUser(UserInterface $user)
    {
        $class = get_class($user);
        if (!$this->supportsClass($class)) {
            throw new UnsupportedUserException(
                sprintf(
                    'Instances of "%s" are not supported.',
                    $class
                )
            );
        }

        return $this->user_repo->find($user->getId());
    }

    public function supportsClass($class)
    {
        return 'Acme\FakeBundle\Entity\User' === $class
        || is_subclass_of($class, 'Acme\FakeBundle\Entity\User');
    }
}

最后,这是自定义身份验证器:

<?php

namespace Acme\FakeBundle\Services;

use Acme\FakeBundle\Entity\User;
use Symfony\Component\DependencyInjection\ContainerInterface;
use Doctrine\ORM\EntityManager;
use Symfony\Component\HttpFoundation\Request;
use Symfony\Component\Security\Core\Authentication\SimpleFormAuthenticatorInterface;
use Symfony\Component\Security\Core\Authentication\Token\TokenInterface;
use Symfony\Component\Security\Core\Authentication\Token\UsernamePasswordToken;
use Symfony\Component\Security\Core\Encoder\EncoderFactoryInterface;
use Symfony\Component\Security\Core\Exception\AuthenticationException;
use Symfony\Component\Security\Core\Exception\UsernameNotFoundException;
use Symfony\Component\Security\Core\User\UserProviderInterface;

class AcmeFakeAuthenticator implements SimpleFormAuthenticatorInterface
{
    private $container;

    private $encoderFactory;

    /**
     * @var \Acme\FakeBundle\Services\FloodTableManager
     */
    protected $floodManager;

    /**
     * Holds the Doctrine entity manager for database interaction
     * @var EntityManager
     */
    protected $em;

    /**
     * @var \Symfony\Component\HttpFoundation\Request
     */
    protected $request;

    public function __construct(ContainerInterface $container, EncoderFactoryInterface $encoderFactory)
    {
        $this->container = $container;
        $this->encoderFactory = $encoderFactory;
        $this->floodManager = $this->container->get('acme.fakebundle.floodtable');
        $this->em = $this->container->get('doctrine.orm.fakebundle_entity_manager');
        $this->request = $this->container->get('request');
    }

    public function createToken(Request $request, $username, $password, $providerKey)
    {
        return new UsernamePasswordToken($username, $password, $providerKey);
    }

    public function authenticateToken(TokenInterface $token, UserProviderInterface $userProvider, $providerKey)
    {
        try {
            $user = $userProvider->loadUserByUsername($token->getUsername());
        } catch (UsernameNotFoundException $e) {
            $this->floodManager->addLoginFailureToFloodTable($token->getUsername(), $this->request->getClientIp());
            $this->floodManager->trimFloodTable();
            throw new AuthenticationException('Invalid username or password');
        }

        $passwordValid = $this->encoderFactory
            ->getEncoder($user)
            ->isPasswordValid(
                $user->getPassword(),
                $token->getCredentials(),
                $user->getSalt()
            );

        if ($passwordValid) {

            // If User is not active, throw appropriate exception
            $status = $user->getStatus();

            if (!$status == User::USER_ACTIVE) {

                // If User's account is waiting on available seats, print this message:
                if ($status == User::USER_PENDING_SEAT) {
                    throw new AuthenticationException("Account pending activation");
                } else {
                    // Otherwise, User's account is inactive, print this error message.
                    throw new AuthenticationException("Account inactive");
                }
            }

            return new UsernamePasswordToken(
                $user,
                $user->getPassword(),
                $providerKey,
                $user->getRoles()
            );
        }

        $this->floodManager->addLoginFailureToFloodTable($user->getUsername(), $this->request->getClientIp());
        $this->floodManager->trimFloodTable();

        throw new AuthenticationException('Invalid username or password');
    }

    public function supportsToken(TokenInterface $token, $providerKey)
    {
        return $token instanceof UsernamePasswordToken && $token->getProviderKey() === $providerKey;
    }
}

当用户提供不正确的登录凭据时,它会被正确处理(即正确的 AuthenticationException 与正确的消息一起被抛出)。但是,如上所述,如果提供了正确的凭据,则用户只会停留在登录页面上,而不会显示任何错误消息。

【问题讨论】:

  • 这不是您问题的答案,但它会帮助您解决问题。php-and-symfony.matthiasnoback.nl/2013/03/…
  • @user2268997 这不是您链接的问题的重复——该问题询问 Symfony 如何在用户通过身份验证后重定向用户,并处理身份验证后的侦听器。这个问题询问为什么使用我的自定义身份验证器无法正确进行身份验证。此外,引用的问题使用 form_login 密钥而不是 simple_form 密钥,就像我在定义身份验证中使用的用户提供程序、处理程序等时所做的那样。虽然这些问题是切线相关的,但我真的不认为这个问题是重复的。
  • @user2268997 我的团队将于周一启动我们的项目,因此如果您能尽快删除不正确的重复标志,我们将不胜感激。另外,感谢您在第一条评论中提供的资源。不幸的是,由于各种原因,这是我们想要采取的方法,所以如果有人能想到使用上述方法解决我的问题,那就太好了。
  • @user2268997 事实证明,我设法使用您上面的资源实现了我需要的功能,所以再次感谢您!话虽如此,我认为我们应该让这篇文章保持开放,因为如上所述,我在这里采用的方法与成功通过身份验证后使用侦听器和重定向用户完全不同。
  • 是的,你是对的。我删除了它。很高兴听到你修复它。不管你问它的方式,让我相信问题只是重定向而不是身份验证本身。因为您没有说明身份验证本身有问题。

标签: php symfony authentication configuration


【解决方案1】:

我想我找到了问题的答案是您在unsecured_area.^/login_(?!check$) 中的正则表达式 确实匹配“login_check”。美元符号应该在(?!_check)$中的括号之后。目前发生的是login_check路径位于unsecured_area防火墙下,并且没有为Secured_area的上下文设置令牌。实际上我没有'认为它自 security: falseunsecured_area 以来一直保存在任何地方。阅读 http://symfony.com/doc/current/book/security.html#book-security-common-pitfalls 中的防火墙上下文

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 2018-11-22
    • 2020-04-08
    • 2014-02-23
    • 2020-04-01
    • 2022-12-10
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多