Как создать пользовательского поставщика пользователей

Как создать пользовательского поставщика пользователей

Часть стандартного процесса аутентификации Symfony зависит от "поставщико пользователей". Когда пользователь отправляет имя пользователя и пароль, слой аутентификации просит сконфигурированного поставщика пользователей вернуть объект пользователя для данного имени пользователя. Потом Symfony проверяет, правильный ли пароль у этого пользователя и генерирует токен безопасности, чтобы пользователь оставался в системе во время текущей сессии. Сразу после установки, Symfony имеет четыре поставщика пользователей: memory, entity, ldap и chain. В этой статье вы увидите, как вы можете создать собственного поставщика пользователей, который может быть полезен, если доступ к вашим пользователям получен через пользовательскую БД, файл, или, как показано в этом примере, через веб-сервис.

Создайте класс пользователя

Для начала, не зависимо от того, откуда идут данные пользователя, вам понадобится создать класс User, который представляет эти данные. User может выглядеть так, как вам этого хочется, и содержать любые данные. Единственное требование - чтобы он реализовывал UserInterface. Методы в этом интерфейсе поэтому должны быть определены в пользовательском классе пользователя getRoles(), getPassword(), getSalt(), getUsername(), eraseCredentials(). Также может быть полезно реализовать интерфейс EquatableInterface, который определяет метод для проверки, равен ли пользователь текущему пользователю. Этот интерфейс требует метод isEqualTo().

Вот как ваш класс WebserviceUser выглядит в действии:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
// src/AppBundle/Security/User/WebserviceUser.php
namespace AppBundle\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->salt !== $user->getSalt()) {
            return false;
        }

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

        return true;
    }
}

Если у вас есть больше информации о ваших пользователях, например, "имя", тогда вы можете добавить поле firstName для хранения этих данных.

Создайте поставщика пользователей

Теперь, когда у вас есть класс User, вы создадите поставщика пользователей, который будет брать информацию о пользователе с какого-то веб-сервиса, создавать объект WebserviceUser и наполнять его данными.

Поставщик пользователей - это простой PHP-класс, который должен реализовать UserProviderInterface, который требует определения трёх методов: loadUserByUsername($username), refreshUser(UserInterface $user), и supportsClass($class). Чтобы узнать больше деталей, смотрите UserProviderInterface.

Вот пример того, как это может выглядеть:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
// src/AppBundle/Security/User/WebserviceUserProvider.php
namespace AppBundle\Security\User;

use AppBundle\Security\User\WebserviceUser;
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)
    {
        // тут сделайте вызов к вашему веб-сервису
        $userData = ...
        // представьте, чтоон возвращает массив при удачной операции, и false, если пользователя нет

        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 WebserviceUser::class === $class;
    }
}

Создайте сервис для поставщика пользователей

Теперь вы сделаете поставщика пользоваталей доступным в качестве сервиса. Если вы используете конфигурацию services.yml по умолчанию, это произойдёт автоматически.

Настройка security.yml

Всё сходится вместе в вашей конфигурации безопасности. Добавьте поставщика пользователей к списку поставщиков в части "безопасность". Выберите имя для поставщика пользоваталей (например, "webservice") и укажите id сервиса, который вы только что определили.

  • YAML
    1
    2
    3
    4
    5
    6
    7
    # app/config/security.yml
    security:
        # ...
    
        providers:
            webservice:
                id: AppBundle\Security\User\WebserviceUserProvider
    
  • XML
     1
     2
     3
     4
     5
     6
     7
     8
     9
    10
    11
    12
    13
    14
    <!-- 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>
            <!-- ... -->
    
            <provider name="webservice" id="AppBundle\Security\User\WebserviceUserProvider" />
        </config>
    </srv:container>
    
  • PHP
     1
     2
     3
     4
     5
     6
     7
     8
     9
    10
    11
    12
    // app/config/security.php
    use AppBundle\Security\User\WebserviceUserProvider;
    
    $container->loadFromExtension('security', array(
        // ...
    
        'providers' => array(
            'webservice' => array(
                'id' => WebserviceUserProvider::class,
            ),
        ),
    ));
    

Symfony также должна знать, как шифровать пароли, которые поставляются пользователями сайта, например, заполняя форму входа в систему. Вы можете сделать это добавив строку к разделу "кодировщики" в вашей конфигурации безопасности:

  • YAML
    1
    2
    3
    4
    5
    6
    # app/config/security.yml
    security:
        # ...
    
        encoders:
            AppBundle\Security\User\WebserviceUser: bcrypt
    
  • XML
     1
     2
     3
     4
     5
     6
     7
     8
     9
    10
    11
    12
    13
    14
    15
    <!-- 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>
            <!-- ... -->
    
            <encoder class="AppBundle\Security\User\WebserviceUser"
                algorithm="bcrypt" />
        </config>
    </srv:container>
    
  • PHP
     1
     2
     3
     4
     5
     6
     7
     8
     9
    10
    11
    // app/config/security.php
    use AppBundle\Security\User\WebserviceUser;
    
    $container->loadFromExtension('security', array(
        // ...
    
        'encoders' => array(
            WebserviceUser::class => 'bcrypt',
        ),
        // ...
    ));
    

Значение здесь должно соответствовать с тем, как изначально были зашифрованы паролипри создании ваших пользователей (и тем, как были созданы пользователи). Когда пользователь отправляет свой пароль, он зашифровывается с использованием этого алгоритма и результат сравнивается с хешированным паролем, возвращённым методом getPassword().

Symfony использует специфический метод, чтобы доюавить соли и зашифровать пароль перед тем, как сравнивать его с вашим зашифрованным паролем. Если getSalt() ничего не возвращает, то отправленный пароль просто зашифрован, используя алгоритм, который вы указали в security.yml. Если соль указана, тогда следующее значение создаётся и потом хешируется с помощью алгоритма:

1
$password.'{'.$salt.'}'

Если к вашим внешним ползователям добавлялась соль с помощью другого метода, то вам нужно будет проделать немного больше работы, чтобы Symfony могла правильно зашифровать пароль. Этои способ не входит в данную статью, но он будет включать в себя под-классификацию MessageDigestPasswordEncoder и переопределение метода mergePasswordAndSalt().

В дополнение, вы можете сконфигурировать детали алгоритма, используемого для хеширования пароля. В этом примере, приложение ясно устанавливает расходы на хеширование bcrypt:

  • YAML
    1
    2
    3
    4
    5
    6
    7
    8
    # app/config/security.yml
    security:
        # ...
    
        encoders:
            AppBundle\Security\User\WebserviceUser:
                algorithm: bcrypt
                cost: 12
    
  • XML
     1
     2
     3
     4
     5
     6
     7
     8
     9
    10
    11
    12
    13
    14
    15
    16
    <!-- 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>
            <!-- ... -->
    
            <encoder class="AppBundle\Security\User\WebserviceUser"
                algorithm="bcrypt"
                cost="12" />
        </config>
    </srv:container>
    
  • PHP
     1
     2
     3
     4
     5
     6
     7
     8
     9
    10
    11
    12
    13
    // app/config/security.php
    use AppBundle\Security\User\WebserviceUser;
    
    $container->loadFromExtension('security', array(
        // ...
    
        'encoders' => array(
            WebserviceUser::class => array(
                'algorithm' => 'bcrypt',
                'cost' => 12,
            ),
        ),
    ));
    

Эта документация является переводом официальной документации Symfony и предоставляется по свободной лицензии CC BY-SA 3.0.