Documentação do Symfony2
Renderizada do repositório symfony-docs-pt-BR no Github

Como implementar seu próprio Voter para lista negra (blacklist) de endereços IP

O componente de segurança do Symfony2 fornece várias camadas para autorizar usuários. Uma das camadas é chamada “voter”. Um voter é uma classe dedicada que verifica se o usuário possui direitos para conectar-se à aplicação ou para acessar um recurso/URL específico. Por exemplo, o Symfony2 fornece uma camada que verifica se o usuário está totalmente autorizado ou se ele tem alguns papéis esperados.

Às vezes é útil criar um voter personalizado para lidar com um caso específico não manipulado pelo framework. Nesta seção, você vai aprender como criar um voter que permitirá criar uma blacklist de usuários por seus IPs.

A Interface Voter

Um voter personalizado deve implementar Symfony\Component\Security\Core\Authorization\Voter\VoterInterface, que exige os três métodos a seguir:

interface VoterInterface
{
    public function supportsAttribute($attribute);
    public function supportsClass($class);
    public function vote(TokenInterface $token, $object, array $attributes);
}

O método :method:`Symfony\\Component\\Security\\Core\\Authorization\\Voter\\VoterInterface::supportsAttribute` é usado para verificar se o voter suporta o atributo de usuário fornecido (ou seja: um papel - role - como ROLE_USER, um ACL EDIT, etc.).

O método :method:`Symfony\\Component\\Security\\Core\\Authorization\\Voter\\VoterInterface::supportsClass` é usado para verificar se o voter suporta a classe do objeto cujo acesso está sendo verificado.

O método :method:`Symfony\\Component\\Security\\Core\\Authorization\\Voter\\VoterInterface::vote` deve implementar a lógica de negócio que verifica se o usuário tem acesso. Esse método deve retornar um dos seguintes valores:

  • VoterInterface::ACCESS_GRANTED: A autorização será concedida por este voter;
  • VoterInterface::ACCESS_ABSTAIN: O voter não pode decidir se a autorização deve ser concedida;
  • VoterInterface::ACCESS_DENIED: A autorização será negada por este voter.

Neste exemplo, você vai verificar se o endereço IP do usuário corresponde a uma lista de endereços na blacklist e “algo” será a aplicação. Se o IP do usuário está na lista negra, você vai retornar VoterInterface::ACCESS_DENIED, caso contrário você vai retornar VoterInterface::ACCESS_ABSTAIN pois o propósito desse voter é somente negar acesso, e não conceder.

Criando um Voter Personalizado

Para adicionar um usuário na lista negra com base em seu endereço IP, você pode usar o serviço request e comparar o endereço IP a um conjunto de endereços IP na lista negra:

// src/Acme/DemoBundle/Security/Authorization/Voter/ClientIpVoter.php
namespace Acme\DemoBundle\Security\Authorization\Voter;

use Symfony\Component\HttpFoundation\RequestStack;
use Symfony\Component\Security\Core\Authorization\Voter\VoterInterface;
use Symfony\Component\Security\Core\Authentication\Token\TokenInterface;

class ClientIpVoter implements VoterInterface
{
    protected $requestStack;
    private $blacklistedIp;

    public function __construct(RequestStack $requestStack, array $blacklistedIp = array())
    {
        $this->requestStack  = $requestStack;
        $this->blacklistedIp = $blacklistedIp;
    }

    public function supportsAttribute($attribute)
    {
        // you won't check against a user attribute, so return true
        return true;
    }

    public function supportsClass($class)
    {
        // your voter supports all type of token classes, so return true
        return true;
    }

    public function vote(TokenInterface $token, $object, array $attributes)
    {
        $request = $this->requestStack->getCurrentRequest();
        if (in_array($request->getClientIp(), $this->blacklistedIp)) {
            return VoterInterface::ACCESS_DENIED;
        }

        return VoterInterface::ACCESS_ABSTAIN;
    }
}

É isso! O voter está pronto. O próximo passo é injetar o voter na camada de segurança. Isso pode ser feito facilmente através do container de serviço.

Tip

A sua implementação dos métodos :method:`Symfony\\Component\\Security\\Core\\Authorization\\Voter\\VoterInterface::supportsAttribute` e :method:`Symfony\\Component\\Security\\Core\\Authorization\\Voter\\VoterInterface::supportsClass` não está sendo chamada internamente pelo framework. Depois de ter registado o seu voter o método vote() sempre será chamado, independentemente desses dois métodos retornarem true ou não. Portanto, você precisa chamar os métodos na sua implementação do método vote() e retornar ACCESS_ABSTAIN se o seu voter não suporta a classe ou atributo.

Declarando o Voter como um Serviço

Para injetar o voter na camada de segurança, você deve declará-lo como um serviço, e adicionar a tag security.voter:

Tip

Certifique-se de importar esse arquivo de configuração em seu arquivo de configuração principal da aplicação (por exemplo, app/config/config.yml). Para mais informações veja Importando configuração com imports. Para ler mais sobre a definição de serviços em geral, consulte o capítulo Container de Serviço .

Mudando a Estratégia de Decisão de Acesso

Para que o novo voter tenha efeito, é necessário alterar a estratégia de decisão de acesso padrão, que, concede acesso se qualquer voter conceder acesso.

Neste caso, escolha a estratégia unanimous. Ao contrário da estratégia affirmative (o padrão), com a estratégia unanimous, se apenas um voter negar o acesso (por exemplo, o ClientIpVoter), o acesso não é concedido ao o usuário final.

Para fazer isso, sobrescreva a seção access_decision_manager padrão de seu arquivo de configuração da aplicação com o seguinte código.

É isso! Agora, no momento de decidir se um usuário deve ou não ter acesso, o novo voter vai negar acesso a qualquer usuário na lista negra de IPs.

See also

Para um uso mais avançado ver components-security-access-decision-manager.