.. index:: single: Segurança; Provider de Usuário 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 :class:`Symfony\\Component\\Security\\Core\\User\\UserInterface`. Os métodos dessa interface devem ser definidos na classe de usuário personalizada: :method:`Symfony\\Component\\Security\\Core\\User\\UserInterface::getRoles`, :method:`Symfony\\Component\\Security\\Core\\User\\UserInterface::getPassword`, :method:`Symfony\\Component\\Security\\Core\\User\\UserInterface::getSalt`, :method:`Symfony\\Component\\Security\\Core\\User\\UserInterface::getUsername`, :method:`Symfony\\Component\\Security\\Core\\User\\UserInterface::eraseCredentials`. Também pode ser útil implementar a interface :class:`Symfony\\Component\\Security\\Core\\User\\EquatableInterface`, a qual define um método para verificar se o usuário é igual ao usuário atual. Essa interface requer um método :method:`Symfony\\Component\\Security\\Core\\User\\EquatableInterface::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 :class:`Symfony\\Component\\Security\\Core\\User\\UserProviderInterface`, que requer a definição de três métodos: ``loadUserByUsername($username)``, ``refreshUser(UserInterface $user)`` e ``supportsClass($class)``. Para mais detalhes, consulte :class:`Symfony\\Component\\Security\\Core\\User\\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: .. configuration-block:: .. code-block:: 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%" .. code-block:: xml Acme\WebserviceUserBundle\Security\User\WebserviceUserProvider .. code-block:: 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%'); .. tip:: 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. .. note :: Certifique-se que o arquivo de serviços está sendo importado. Veja :ref:`service-container-imports-directive` 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. .. configuration-block:: .. code-block:: yaml // app/config/security.yml security: providers: webservice: id: webservice_user_provider .. code-block:: xml .. code-block:: 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: .. configuration-block:: .. code-block:: yaml # app/config/security.yml security: encoders: Acme\WebserviceUserBundle\Security\User\WebserviceUser: sha512 .. code-block:: xml sha512 .. code-block:: 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. .. sidebar:: Detalhes sobre como as senhas são codificadas O Symfony utiliza um método específico para combinar o salt e codificar a senha antes de compará-la com a senha codificada. Se o ``getSalt()`` não retornar nada, então a senha submetida é simplesmente codificada utilizando o algoritmo que você especificou no ``security.yml``. Se um salt *é* especificado, então o valor seguinte é criado e *então* feito o hash através do algoritmo: ``$password.'{'.$salt.'}';``, `` Caso os usuários externos tenham nas suas senhas um salt através de um método diferente, então você terá um pouco mais de trabalho para que o Symfony codifique corretamente a senha. Isso está além do escopo deste artigo, mas incluiria estender a classe ``MessageDigestPasswordEncoder`` e sobrescrever o método ``mergePasswordAndSalt``. Além disso, o hash, por padrão, é codificado várias vezes e codificado para base64. Para obter detalhes específicos, consulte `MessageDigestPasswordEncoder`_. Para evitar isso, configure ele no seu arquivo de configuração: .. configuration-block:: .. code-block:: yaml # app/config/security.yml security: encoders: Acme\WebserviceUserBundle\Security\User\WebserviceUser: algorithm: sha512 encode_as_base64: false iterations: 1 .. code-block:: xml .. code-block:: php // app/config/security.php $container->loadFromExtension('security', array( 'encoders' => array( 'Acme\WebserviceUserBundle\Security\User\WebserviceUser' => array( 'algorithm' => 'sha512', 'encode_as_base64' => false, 'iterations' => 1, ), ), )); .. _MessageDigestPasswordEncoder: https://github.com/symfony/symfony/blob/master/src/Symfony/Component/Security/Core/Encoder/MessageDigestPasswordEncoder.php