.. index::
single: Security; User Provider
カスタムユーザプロバイダの作成方法
==================================
Part of Symfony's standard authentication process depends on "user providers".
When a user submits a username and password, the authentication layer asks
the configured user provider to return a user object for a given username.
Symfony then checks whether the password of this user is correct and generates
a security token so the user stays authenticated during the current session.
Out of the box, Symfony has an "in_memory" and an "entity" user provider.
In this entry we'll see how you can create your own user provider, which
could be useful if your users are accessed via a custom database, a file,
or - as we show in this example - a web service.
Create a User Class
-------------------
First, regardless of *where* your user data is coming from, you'll need to
create a ``User`` class that represents that data. The ``User`` can look
however you want and contain any data. The only requirement is that the
class implements :class:`Symfony\\Component\\Security\\Core\\User\\UserInterface`.
The methods in this interface should therefore be defined in the custom user
class: ``getRoles()``, ``getPassword()``, ``getSalt()``, ``getUsername()``,
``eraseCredentials()``, ``equals()``.
Let's see this in action::
// src/Acme/WebserviceUserBundle/Security/User.php
namespace Acme\WebserviceUserBundle\Security\User;
use Symfony\Component\Security\Core\User\UserInterface;
class WebserviceUser implements UserInterface
{
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 equals(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;
}
}
If you have more information about your users - like a "first name" - then
you can add a ``firstName`` field to hold that data.
For more details on each of the methods, see :class:`Symfony\\Component\\Security\\Core\\User\\UserInterface`.
Create a User Provider
----------------------
Now that we have a ``User`` class, we'll create a user provider, which will
grab user information from some web service, create a ``WebserviceUser`` object,
and populate it with data.
The user provider is just a plain PHP class that has to implement the
:class:`Symfony\\Component\\Security\\Core\\User\\UserProviderInterface`,
which requires three methods to be defined: ``loadUserByUsername($username)``,
``refreshUser(UserInterface $user)``, and ``supportsClass($class)``. For
more details, see :class:`Symfony\\Component\\Security\\Core\\User\\UserProviderInterface`.
Here's an example of how this might look::
// 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)
} else {
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';
}
}
Create a Service for the User Provider
--------------------------------------
Now we make the user provider available as service.
.. configuration-block::
.. code-block:: yaml
# src/Acme/MailerBundle/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::
The real implementation of the user provider will probably have some
dependencies or configuration options or other services. Add these as
arguments in the service definition.
.. note::
Make sure the services file is being imported. See :ref:`service-container-imports-directive`
for details.
Modify ``security.yml``
-----------------------
In ``/app/config/security.yml`` everything comes together. Add the user provider
to the list of providers in the "security" section. Choose a name for the user provider
(e.g. "webservice") and mention the id of the service you just defined.
.. code-block:: yaml
security:
providers:
webservice:
id: webservice_user_provider
Symfony also needs to know how to encode passwords that are supplied by website
users, e.g. by filling in a login form. You can do this by adding a line to the
"encoders" section in ``/app/config/security.yml``.
.. code-block:: yaml
security:
encoders:
Acme\WebserviceUserBundle\Security\User\WebserviceUser: sha512
The value here should correspond with however the passwords were originally
encoded when creating your users (however those users were created). When
a user submits her password, the password is appended to the salt value and
then encoded using this algorithm before being compared to the hashed password
returned by your ``getPassword()`` method.
.. sidebar:: Specifics on how passwords are encoded
Symfony uses a specific method to combine the salt and encode the password
before comparing it to your encoded password. If ``getSalt()`` returns
nothing, then the submitted password is simply encoded using the algorithm
you specify in ``security.yml``. If a salt *is* specified, then the following
value is created and *then* hashed via the algorithm:
``$password.'{'.$salt.'}';``
If your external users have their passwords salted via a different method,
then you'll need to do a bit more work so that Symfony properly encodes
the password. That is beyond the scope of this entry, but would include
sub-classing ``MessageDigestPasswordEncoder`` and overriding the ``mergePasswordAndSalt``
method.
.. 2012/01/10 ganchiku d792c492880cb6beb2d8066572bd0c08c363086e