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

Como criar um Formulário Authenticator de Senha Personalizado

Imagine que você deseja permitir o acesso ao seu site apenas entre 2pm e 4pm UTC. Antes do Symfony 2.4, você tinha que criar um token personalizado, factory, listener e provedor. Nesse artigo, você vai aprender como fazer isso em um formulário de login (ou seja, onde o usuário submete seu nome de usuário e senha).

O Authenticator de Senha

New in version 2.4: A interface SimplePreAuthenticatorInterface foi introduzida no Symfony 2.4.

Primeiro, crie uma nova classe que implementa SimpleFormAuthenticatorInterface. Eventualmente, isso permitirá criar uma lógica personalizada para autenticar o usuário:

// src/Acme/HelloBundle/Security/TimeAuthenticator.php
namespace Acme\HelloBundle\Security;

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 TimeAuthenticator implements SimpleFormAuthenticatorInterface
{
    private $encoderFactory;

    public function __construct(EncoderFactoryInterface $encoderFactory)
    {
        $this->encoderFactory = $encoderFactory;
    }

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

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

        if ($passwordValid) {
            $currentHour = date('G');
            if ($currentHour < 14 || $currentHour > 16) {
                throw new AuthenticationException(
                    'You can only log in between 2 and 4!',
                    100
                );
            }

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

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

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

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

Como funciona

Excelente! Agora você só precisa configurar algumas Configuração. Mas, primeiro, você pode aprender mais sobre o que cada método faz nessa classe.

1) createToken

Quando Symfony começa a lidar com um pedido, createToken() é chamado, onde você cria um objeto TokenInterface que contém todas as informações que você precisa em authenticateToken() para autenticar o usuário (por exemplo, o nome de usuário e senha).

Seja qual for o objeto token você criar aqui, ele será passado para você mais tarde em authenticateToken().

2) supportsToken

Após o Symfony chamar createToken(), ele irá então chamar supportsToken() em sua classe (e em quaisquer outros listeners de autenticação) para descobrir quem deve lidar com o token. Esta é apenas uma maneira de permitir que vários mecanismos de autenticação sejam utilizados para o mesmo firewall (dessa forma, você pode, por exemplo, primeiro tentar autenticar o usuário via um certificado ou uma chave de API e fall back para um formulário de login).

Na maioria das vezes, você só precisa ter certeza de que esse método retorna true para um token que foi criado por createToken(). Sua lógica provavelmente deve parecer exatamente como neste exemplo.

3) authenticateToken

Se supportsToken retornar true, o Symfony irá chamar agora authenticateToken(). Seu trabalho aqui é verificar se é permitido o login do token primeiro obtendo o objeto User através do provedor de usuário e, em seguida, verificando a senha e a hora atual.

Note

O “fluxo” de como obter o objeto User e determinar se o token é ou não válido (por exemplo, verificar a senha), pode variar de acordo com os seus requisitos.

Por fim, o seu trabalho é retornar um objeto token novo que é “autenticado” (ou seja, ele tem pelo menos um papel - role - definido) e que tem o objeto User em seu interior.

Dentro deste método, um encoder é necessário para verificar a validade da senha:

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

Esse é um serviço que já está disponível no Symfony e o algoritmo de senha está definido na configuração de segurança (por exemplo, security.yml) sob a chave encoders. Abaixo, você verá como injetar isso no TimeAuthenticator.

Configuração

Agora, configure o seu TimeAuthenticator como um serviço:

  • YAML
    1
    2
    3
    4
    5
    6
    7
    # app/config/config.yml
    services:
        # ...
    
        time_authenticator:
            class:     Acme\HelloBundle\Security\TimeAuthenticator
            arguments: ["@security.encoder_factory"]
    
  • XML
     1
     2
     3
     4
     5
     6
     7
     8
     9
    10
    11
    12
    13
    14
    15
    16
    <!-- app/config/config.xml -->
    <?xml version="1.0" ?>
    <container xmlns="http://symfony.com/schema/dic/services"
        xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
        xsi:schemaLocation="http://symfony.com/schema/dic/services
            http://symfony.com/schema/dic/services/services-1.0.xsd">
        <services>
            <!-- ... -->
    
            <service id="time_authenticator"
                class="Acme\HelloBundle\Security\TimeAuthenticator"
            >
                <argument type="service" id="security.encoder_factory" />
            </service>
        </services>
    </container>
    
  • PHP
     1
     2
     3
     4
     5
     6
     7
     8
     9
    10
    // app/config/config.php
    use Symfony\Component\DependencyInjection\Definition;
    use Symfony\Component\DependencyInjection\Reference;
    
    // ...
    
    $container->setDefinition('time_authenticator', new Definition(
        'Acme\HelloBundle\Security\TimeAuthenticator',
        array(new Reference('security.encoder_factory'))
    ));
    

Em seguida, ative-o na seção firewalls da configuração de segurança utilizando a chave simple_form:

  • YAML
     1
     2
     3
     4
     5
     6
     7
     8
     9
    10
    11
    12
    # app/config/security.yml
    security:
        # ...
    
        firewalls:
            secured_area:
                pattern: ^/admin
                # ...
                simple_form:
                    authenticator: time_authenticator
                    check_path:    login_check
                    login_path:    login
    
  • XML
     1
     2
     3
     4
     5
     6
     7
     8
     9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    <!-- app/config/security.xml -->
    <?xml version="1.0" encoding="UTF-8"?>
    <srv:container xmlns="http://symfony.com/schema/dic/security"
        xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
        xmlns:srv="http://symfony.com/schema/dic/services"
        xsi:schemaLocation="http://symfony.com/schema/dic/services
            http://symfony.com/schema/dic/services/services-1.0.xsd">
        <config>
            <!-- ... -->
    
            <firewall name="secured_area"
                pattern="^/admin"
                >
                <simple-form authenticator="time_authenticator"
                    check-path="login_check"
                    login-path="login"
                />
            </firewall>
        </config>
    </srv:container>
    
  • PHP
     1
     2
     3
     4
     5
     6
     7
     8
     9
    10
    11
    12
    13
    14
    15
    16
    17
    // app/config/security.php
    
    // ..
    
    $container->loadFromExtension('security', array(
        'firewalls' => array(
            'secured_area'    => array(
                'pattern'     => '^/admin',
                'simple_form' => array(
                    'provider'      => ...,
                    'authenticator' => 'time_authenticator',
                    'check_path'    => 'login_check',
                    'login_path'    => 'login',
                ),
            ),
        ),
    ));
    

A chave simple_form tem as mesmas opções que a opção normal form_login , mas com a chave adicional authenticator que aponta para o novo serviço. Para mais detalhes, veja reference-security-firewall-form-login.

Se criar um formulário de login em geral é novidade para você ou se não entende as opções check_path ou login_path, veja /cookbook/security/form_login.