配置Symfony2.4 security.yml以使用自定义身份验证器

时间:2014-07-27 22:23:43

标签: php symfony authentication configuration

我正在尝试配置我的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)。但是,如上所述,如果给出了正确的凭据,那么用户只需停留在登录页面上,不会显示任何错误消息。

1 个答案:

答案 0 :(得分:0)

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