Documentação do Symfony - versão 3.1
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 VoterInterface, que exige os três métodos a seguir:

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:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
// 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 supportsAttribute() e 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:

  • YAML
    1
    2
    3
    4
    5
    6
    7
    8
    # src/Acme/AcmeBundle/Resources/config/services.yml
    services:
        security.access.blacklist_voter:
            class:      Acme\DemoBundle\Security\Authorization\Voter\ClientIpVoter
            arguments:  ["@request_stack", [123.123.123.123, 171.171.171.171]]
            public:     false
            tags:
                - { name: security.voter }
    
  • XML
     1
     2
     3
     4
     5
     6
     7
     8
     9
    10
    <!-- src/Acme/AcmeBundle/Resources/config/services.xml -->
    <service id="security.access.blacklist_voter"
             class="Acme\DemoBundle\Security\Authorization\Voter\ClientIpVoter" public="false">
        <argument type="service" id="request_stack" strict="false" />
        <argument type="collection">
            <argument>123.123.123.123</argument>
            <argument>171.171.171.171</argument>
        </argument>
        <tag name="security.voter" />
    </service>
    
  • PHP
     1
     2
     3
     4
     5
     6
     7
     8
     9
    10
    11
    12
    13
    14
    15
    // src/Acme/AcmeBundle/Resources/config/services.php
    use Symfony\Component\DependencyInjection\Definition;
    use Symfony\Component\DependencyInjection\Reference;
    
    $definition = new Definition(
        'Acme\DemoBundle\Security\Authorization\Voter\ClientIpVoter',
        array(
            new Reference('request_stack'),
            array('123.123.123.123', '171.171.171.171'),
        ),
    );
    $definition->addTag('security.voter');
    $definition->setPublic(false);
    
    $container->setDefinition('security.access.blacklist_voter', $definition);
    

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 /book/service_container .

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.

  • YAML
    1
    2
    3
    4
    5
    # app/config/security.yml
    security:
        access_decision_manager:
            # strategy can be: affirmative, unanimous or consensus
            strategy: unanimous
    
  • XML
    1
    2
    3
    4
    5
    <!-- app/config/security.xml -->
    <config>
        <!-- strategy can be: affirmative, unanimous or consensus -->
        <access-decision-manager strategy="unanimous">
    </config>
    
  • PHP
    1
    2
    3
    4
    5
    6
    7
    // app/config/security.xml
    $container->loadFromExtension('security', array(
        // strategy can be: affirmative, unanimous or consensus
        'access_decision_manager' => array(
            'strategy' => 'unanimous',
        ),
    ));
    

É 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.