Documentação do Symfony - versão 3.4
Renderizada do repositório symfony-docs-pt-BR no Github
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).
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);
}
}
Excelente! Agora você só precisa configurar algumas Configuração. Mas, primeiro, você pode aprender mais sobre o que cada método faz nessa classe.
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().
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.
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.
Agora, configure o seu TimeAuthenticator como um serviço:
1 2 3 4 5 6 7 | # app/config/config.yml
services:
# ...
time_authenticator:
class: Acme\HelloBundle\Security\TimeAuthenticator
arguments: ["@security.encoder_factory"]
|
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>
|
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:
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
|
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>
|
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.