Як створити користувацького постачальника користувачів

Дата оновлення перекладу 2023-06-23

Як створити користувацького постачальника користувачів

Частина стандартного процесу аутентифікації Symfony залежить від "постачальників користувачів". Коли користувач надсилає ім'я користувача та пароль, шар аутентифікації просить сконфігурованого постачальника користувачів повернути об'єкт користувача для даного імені користувача. Потім Symfony перевіряє, чи правильний пароль у цього користувача і генерує токен безпеки, щоб користувач залишався у системі
під час поточної сесії. Одразу після встановлення, Symfony має чотири постачальники користувачів: memory, entity, ldap та chain. В цій статті ви побачите, як ви можете створити власного постачальника користувачів, який може бути корисним, якщо доступ до ваших користувачів отримано через користувацьку DB, файл, або, як показано в цьому прикладі, через веб-сервіс.

Створіть клас користувача

Для початку, незалежно від того, звідки йдуть дані користувача, вам знадобиться створити клас 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/Security/User/WebserviceUser.php
namespace App\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/Security/User/WebserviceUserProvider.php
namespace App\Security\User;

use App\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.yaml за замовчуванням , це відбудеться автоматично.

Налаштування security.yaml

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

1
2
3
4
5
6
7
# config/packages/security.yaml
security:
    # ...

    providers:
        webservice:
            id: App\Security\User\WebserviceUserProvider

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

1
2
3
4
5
6
# config/packages/security.yaml
security:
    # ...

    encoders:
        App\Security\User\WebserviceUser: bcrypt

Значення тут має співпадати з тим, як спочатку були зашифровані паролі при створенні ваших користувачів (і тим, як були створені користувачі). Коли користувач надсилає свій пароль, він зашифровується з використанням цього алгоритму і результат порівнюється з хешованим паролем, повернутим методом getPassword().

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

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

Якщо до ваших зовнішніх користувачів додавалася сіль за допомогою іншого методу, то вам потрібно буде виконати трохи більше роботи, щоб Symfony могла правильно зашифрувати пароль. Цей спосіб не входить до цієї статті, але він включатиме в себе під-класифікацію MessageDigestPasswordEncoder і перевизначення методу mergePasswordAndSalt().

На додаток, ви можете сконфігурувати деталі алгоритму, що використовується для хешування пароля. У цьому прикладі, додаток ясно встановлює витрати на хешування bcrypt:

1
2
3
4
5
6
7
8
# config/packages/security.yaml
security:
    # ...

    encoders:
        App\Security\User\WebserviceUser:
            algorithm: bcrypt
            cost: 12