Como criar um Provider de Usuário Personalizado

Parte do processo de autenticação padrão do Symfony depende de “providers de usuário”. Quando um usuário submete um nome de usuário e senha, a camada de autenticação solicita ao provider de usuário configurado para retornar um objeto de usuário para um determinado nome de usuário. Em seguida, o Symfony verifica se a senha deste usuário está correta e gera um token de segurança para que o usuário permaneça autenticado durante a sessão atual. O Symfony vem com os providers de usuário “in_memory” e “entity” prontos para uso. Neste artigo, você vai aprender como criar o seu próprio provider de usuário, que pode ser útil se os usuários são acessados através de um banco de dados personalizado, um arquivo ou - como mostrado neste exemplo - um serviço web.

Crie uma Classe de Usuário

Em primeiro lugar, independentemente de onde os seus dados de usuário estão vindo, você vai precisar criar uma classe User que representa esses dados. No entando, a classe User pode parecer com o que você quiser e conter quaisquer dados. O único requisito é que a classe implemente UserInterface. Os métodos dessa interface devem ser definidos na classe de usuário personalizada: getRoles(), getPassword(), getSalt(), getUsername(), eraseCredentials(). Também pode ser útil implementar a interface EquatableInterface, a qual define um método para verificar se o usuário é igual ao usuário atual. Essa interface requer um método isEqualTo() .

Vamos ver isso na prática:

// src/Acme/WebserviceUserBundle/Security/User/WebserviceUser.php
namespace Acme\WebserviceUserBundle\Security\User;

use Symfony\Component\Security\Core\User\UserInterface;
use Symfony\Component\Security\Core\User\EquatableInterface;

class WebserviceUser implements UserInterface, EquatableInterface
{
    private $username;
    private $password;
    private $salt;
    private $roles;

    public function __construct($username, $password, $salt, array $roles)
    {
        $this->username = $username;
        $this->password = $password;
        $this->salt = $salt;
        $this->roles = $roles;
    }

    public function getRoles()
    {
        return $this->roles;
    }

    public function getPassword()
    {
        return $this->password;
    }

    public function getSalt()
    {
        return $this->salt;
    }

    public function getUsername()
    {
        return $this->username;
    }

    public function eraseCredentials()
    {
    }

    public function isEqualTo(UserInterface $user)
    {
        if (!$user instanceof WebserviceUser) {
            return false;
        }

        if ($this->password !== $user->getPassword()) {
            return false;
        }

        if ($this->getSalt() !== $user->getSalt()) {
            return false;
        }

        if ($this->username !== $user->getUsername()) {
            return false;
        }

        return true;
    }
}

Se você tiver mais informações sobre seus usuários - como um “primeiro nome” - então você pode adicionar um campo firstName para guardar esse dado.

Criar um Provider de Usuário

Agora que você tem uma classe User, você vai criar um provider de usuário, que irá pegar informações de usuário de algum serviço web, criar um objeto WebserviceUser e popular ele com os dados.

O provider de usuário é apenas uma classe PHP que deve implementar a UserProviderInterface, que requer a definição de três métodos: loadUserByUsername($username), refreshUser(UserInterface $user) e supportsClass($class). Para mais detalhes, consulte UserProviderInterface.

Aqui está um exemplo de como isso pode parecer:

// src/Acme/WebserviceUserBundle/Security/User/WebserviceUserProvider.php
namespace Acme\WebserviceUserBundle\Security\User;

use Symfony\Component\Security\Core\User\UserProviderInterface;
use Symfony\Component\Security\Core\User\UserInterface;
use Symfony\Component\Security\Core\Exception\UsernameNotFoundException;
use Symfony\Component\Security\Core\Exception\UnsupportedUserException;

class WebserviceUserProvider implements UserProviderInterface
{
    public function loadUserByUsername($username)
    {
        // make a call to your webservice here
        $userData = ...
        // pretend it returns an array on success, false if there is no user

        if ($userData) {
            $password = '...';

            // ...

            return new WebserviceUser($username, $password, $salt, $roles);
        }

        throw new UsernameNotFoundException(sprintf('Username "%s" does not exist.', $username));
    }

    public function refreshUser(UserInterface $user)
    {
        if (!$user instanceof WebserviceUser) {
            throw new UnsupportedUserException(sprintf('Instances of "%s" are not supported.', get_class($user)));
        }

        return $this->loadUserByUsername($user->getUsername());
    }

    public function supportsClass($class)
    {
        return $class === 'Acme\WebserviceUserBundle\Security\User\WebserviceUser';
    }
}

Crie um Serviço para o Provider de Usuário

Agora você tornará o provider de usuário disponível como um serviço:

  • YAML
    # src/Acme/WebserviceUserBundle/Resources/config/services.yml
    parameters:
        webservice_user_provider.class: Acme\WebserviceUserBundle\Security\User\WebserviceUserProvider
    
    services:
        webservice_user_provider:
            class: "%webservice_user_provider.class%"
    
  • XML
    <!-- src/Acme/WebserviceUserBundle/Resources/config/services.xml -->
    <parameters>
        <parameter key="webservice_user_provider.class">Acme\WebserviceUserBundle\Security\User\WebserviceUserProvider</parameter>
    </parameters>
    
    <services>
        <service id="webservice_user_provider" class="%webservice_user_provider.class%"></service>
    </services>
    
  • PHP
    // src/Acme/WebserviceUserBundle/Resources/config/services.php
    use Symfony\Component\DependencyInjection\Definition;
    
    $container->setParameter('webservice_user_provider.class', 'Acme\WebserviceUserBundle\Security\User\WebserviceUserProvider');
    
    $container->setDefinition('webservice_user_provider', new Definition('%webservice_user_provider.class%');
    

Dica

A verdadeira implementação do provider de usuário provavelmente terá algumas dependências, opções de configuração ou outros serviços. Adicione eles como argumentos na definição de serviço.

Nota

Certifique-se que o arquivo de serviços está sendo importado. Veja Importando configuração com imports para mais detalhes.

Modifique o security.yml

Tudo será combinado em sua configuração de segurança. Adicione o provider de usuário na lista de providers na seção “security”. Escolha um nome para o provider de usuário (por exemplo, “webservice”) e mencione o id do serviço que você acabou de definir.

  • YAML
    // app/config/security.yml
    security:
        providers:
            webservice:
                id: webservice_user_provider
    
  • XML
    <!-- app/config/security.xml -->
    <config>
        <provider name="webservice" id="webservice_user_provider" />
    </config>
    
  • PHP
    // app/config/security.php
    $container->loadFromExtension('security', array(
        'providers' => array(
            'webservice' => array(
                'id' => 'webservice_user_provider',
            ),
        ),
    ));
    

O Symfony também precisa saber como codificar as senhas que são fornecidas no site pelos usuários, por exemplo, através do preenchimento de um formulário de login. Você pode fazer isso adicionando uma linha na seção “encoders” da sua configuração de segurança:

  • YAML
    # app/config/security.yml
    security:
        encoders:
            Acme\WebserviceUserBundle\Security\User\WebserviceUser: sha512
    
  • XML
    <!-- app/config/security.xml -->
    <config>
        <encoder class="Acme\WebserviceUserBundle\Security\User\WebserviceUser">sha512</encoder>
    </config>
    
  • PHP
    // app/config/security.php
    $container->loadFromExtension('security', array(
        'encoders' => array(
            'Acme\WebserviceUserBundle\Security\User\WebserviceUser' => 'sha512',
        ),
    ));
    

O valor aqui deve corresponder porém com as senhas que foram originalmente codificadas ao criar os seus usuários (no entanto os usuários foram criados). Quando um usuário submete a sua senha, o salt é acrescentado ao valor da senha e então são codificados usando este algoritmo antes de ser comparada com o hash da senha retornado pelo seu método getPassword(). Além disso, dependendo das suas opções, a senha pode ser codificada várias vezes e codificada para base64.