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

Дата обновления перевода 2024-07-27

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

Поставщики пользователей (повторно) загружают пользователей из хранилища (к примеру, базы данных), основываясь на "идентификаторе пользователя" (к примеру, адресе электронной почты или имени пользователя). См. , чтобы узнать более детализированную информацию о том, когда используется поставщик пользователей.

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

Поставщик пользователей сущности
Загружает пользователей из базы данных, используя Doctrine;
Поставщик пользователей LDAP
Загружает пользователей с сервера LDAP;
Поставщик пользователей памяти
Загружает пользователей из файла конфигурации;
Цепной поставщик пользователей
Слияет два или более поставщиков пользователей в нового поставщика пользователей.

Поставщик пользователей сущности

Это наиболее распространенный поставщик пользователей. Пользователи хранятся в базе данных, и поставщик пользователей использует Doctrine, чтобы извлечь их:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
# config/packages/security.yaml
security:
    providers:
        users:
            entity:
                # клас сущности, который представляет пользователей
                class: 'App\Entity\User'
                # свойство, по которому делается запрос - например, email, имя пользователя и т.д.
                property: 'email'

                # опционально: если вы используете несколько менеджеров сущностей Doctrine,
                # эта опция определяет, какой использовать
                #manager_name: 'customer'

    # ...

Использование пользовательского запроса для загрузки пользователя

Поставщик сущностей может делать запрос только из одного конкретного поля, указаннного ключом конфигурации property. Если вы хотите иметь немного больше контроля - например, вы хотите найти пользователя по email или username, вы можете сделать это, реализуя UserLoaderInterface в вашем хранилище Doctrine (например, UserRepository):

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
// src/Repository/UserRepository.php
namespace App\Repository;

use App\Entity\User;
use Doctrine\Bundle\DoctrineBundle\Repository\ServiceEntityRepository;
use Symfony\Bridge\Doctrine\Security\User\UserLoaderInterface;

class UserRepository extends ServiceEntityRepository implements UserLoaderInterface
{
    // ...

    public function loadUserByIdentifier(string $usernameOrEmail): ?User
    {
        $entityManager = $this->getEntityManager();

        return $entityManager->createQuery(
                'SELECT u
                FROM App\Entity\User u
                WHERE u.username = :query
                OR u.email = :query'
            )
            ->setParameter('query', $usernameOrEmail)
            ->getOneOrNullResult();
    }
}

Чтобы закончить, удалите ключ property из поставщика пользователей в security.yaml:

1
2
3
4
5
6
7
8
# config/packages/security.yaml
security:
    providers:
        users:
            entity:
                class: App\Entity\User

    # ...

Теперь, каждый раз, когда Symfony будет использовать поставщика пользователей, в вашем UserRepository будет вызван метод loadUserByIdentifier().

Поставщик пользователей памяти

Не рекомендуется использовать этого поставщика в реальных приложениях из-за его ограничений и того, насколько сложно управлять пользователями. Он может быть полезен в прототипах приложений и для ограниченных приложений, которые не хранят пользователей в базах данных.

Этот поставщик пользоваталей хранит всю информацию пользоваталей в файле конфигурации, включая их пароли. Убедитесь в том, что пароли правильно хешированы. См. Хеширование и верификация паролей, чтобы узнать больше.

После настройки хеширования, вы можете сконфигурировать всю пользовательскую информацию в security.yaml:

1
2
3
4
5
6
7
8
9
10
# config/packages/security.yaml
security:
    providers:
        backend_users:
            memory:
                users:
                    john_admin: { password: '$2y$13$jxGxc ... IuqDju', roles: ['ROLE_ADMIN'] }
                    jane_admin: { password: '$2y$13$PFi1I ... rGwXCZ', roles: ['ROLE_ADMIN', 'ROLE_SUPER_ADMIN'] }

    # ...

Caution

При использовании поставщика memory, а не алгоритма auto, вам нужно выбирать алгоритм хеширования без соли (т.е. bcrypt).

Цепной поставщик пользователей

Этот поставщик пользователей объединяет два или более других типов постащиков пользователей (entity и ldap), чтобы создать нового поставщика пользователей. Порядок, в котором сконфигурированы поставщики, важен, так как Symfony будет искать пользователей начиная с первого поставщика и будет продолжать искать их в других поставщиков, пока не найдет:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
# config/packages/security.yaml
security:
    # ...
    providers:
        backend_users:
            ldap:
                # ...

        legacy_users:
            entity:
                # ...

        users:
            entity:
                # ...

        all_users:
            chain:
                providers: ['legacy_users', 'users', 'backend_users']

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

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

Для начала, убедитесь, что вы следовали Руководству Безопасности при создании вашего класса User.

Если вы использовали команду make:user для создания вашего класса User (и вы ответили на вопросы, указывающие, что вам необходим пользовательский поставщик), то эта команда сгенерирует хороший костяк для того, чтобы начать:

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
67
68
69
70
71
72
// src/Security/UserProvider.php
namespace App\Security;

use Symfony\Component\Security\Core\Exception\UnsupportedUserException;
use Symfony\Component\Security\Core\Exception\UserNotFoundException;
use Symfony\Component\Security\Core\User\PasswordAuthenticatedUserInterface;
use Symfony\Component\Security\Core\User\PasswordUpgraderInterface;
use Symfony\Component\Security\Core\User\UserInterface;
use Symfony\Component\Security\Core\User\UserProviderInterface;

class UserProvider implements UserProviderInterface, PasswordUpgraderInterface
{
    /**
     * Метод loadUserByIdentifier() был представлен в Symfony 5.3.
     * В предыдущих версиях он назывался loadUserByUsername()
     *
     * Symfony вызывает этот метод, если вы используете функции вроде switch_user
     * или remember_me. Если вы не используете эти функции, вам не нужно реализовывать
     * этот метод.
     *
     * @throws UserNotFoundException, если пользователь не найден
     */
    public function loadUserByIdentifier(string $identifier): UserInterface
    {
        // Загрузить объект User из вашего источника данных или вызвать UserNotFoundException.
        // Аргумент $identifier - то значение, которое возвращается методом
        // getUserIdentifier() в вашем классе User.
        throw new \Exception('TODO: fill in loadUserByIdentifier() inside '.__FILE__);
    }

    /**
     * Обновляет пользователя после повторной загрузки из сессии.
     *
     * Когда пользователь вошел в систему, в начале каждого запроса, объект
     * User загружается из сессии, а затем вызывается этот метод. Ваша задача
     * - убедиться, что данные пользователя все еще свежие, путем, к примеру,
     * повторного запроса свежих данных пользователя.
     *
     * Если ваш файерволл "stateless: true" (для чистого API), этот метод
     * не вызывается.
     *
     * @return UserInterface
     */
    public function refreshUser(UserInterface $user)
    {
        if (!$user instanceof User) {
            throw new UnsupportedUserException(sprintf('Invalid user class "%s".', get_class($user)));
        }

        // Вернуть объект User после того, как убедились, что его данные "свежие".
        // Или вызвать UserNotFoundException, если пользователь уже не существует.
        throw new \Exception('TODO: fill in refreshUser() inside '.__FILE__);
    }

    /**
     * Tells Symfony to use this provider for this User class.
     */
    public function supportsClass(string $class): bool
    {
        return User::class === $class || is_subclass_of($class, User::class);
    }

    /**
     * Обновляет зашифрованный пароль пользователя, обычно для использования лучшего алгоритма хеширования.
     */
    public function upgradePassword(PasswordAuthenticatedUserInterface $user, string $newHashedPassword): void
    {
        // СДЕЛАТЬ: когда используются хешированные пароли, этот метод должен:
        // 1. сохранять новый пароль в хранилище пользователя
        // 2. обновлять объект $user с $user->setPassword($newHashedPassword);
    }
}

Большинство работы уже сделано! Прочтите комментарии в коде и обновите разделы СДЕЛАТЬ, чтобы закончить с поставщиком пользователей. Когда вы закончите, сообщите Symfony о поставщике пользователей, добавив его в security.yaml:

1
2
3
4
5
6
# config/packages/security.yaml
security:
    providers:
        # имя вашего поставщика пользователя может быть любым
        your_custom_user_provider:
            id: App\Security\UserProvider

Наконец, обновите файл config/packages/security.yaml, чтобы установить ключ provider как your_custom_user_provider во всех файерволлах, которые будут использовать этого пользовательского поставщика.