Как имперсонализировать пользователя

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

Как имперсонализировать пользователя

Иногда полезно иметь возможность переключаться с одного пользователя на другого, не выходя из системы (например, когда вы отлаживаете или пытаетесь понять баг, который видит пользователь, который вы не можете воспроизвести).

Caution

Имперсонализация пользователя не совместима с некоторыми механизмами аутентификации (например, REMOTE_USER), где отправка информации аутентификации ожидается при каждом запросе.

Имперсонализации пользователя можно легко добиться, активировав слушатель брандмауэра switch_user:

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

    firewalls:
        main:
            # ...
            switch_user: true

Чтобы переключиться на другого пользователя, просто добавьте строку запроса с параметром _switch_user и имя пользователя (or whatever field our user provider uses to load users), как значения текущего URL:

1
http://example.com/somewhere?_switch_user=thomas

Tip

Вы можете воспользоваться функцией Twig impersonation_path('thomas').

Tip

Вместо добавление параметра строки запроса _switch_user, вы можете передать имя пользователя в пользовательском заголовке HTTP, путем настройки parameter. Например, чтобы использовать заголовок X-Switch-User (доступный в PHP как HTTP_X_SWITCH_USER), добавьте эту конфигурацию:

1
2
3
4
5
6
7
# config/packages/security.yaml
security:
    # ...
    firewalls:
        main:
            # ...
            switch_user: { parameter: X-Switch-User }

Чтобы переключиться обратно на изначального пользователя, используйте специальное имя пользователя _exit:

1
http://example.com/somewhere?_switch_user=_exit

Tip

Вы можете воспользоваться функцией Twig impersonation_exit_path('/somewhere').

Эта функция доступна только пользователям со специально ролью под названием ROLE_ALLOWED_TO_SWITCH. Использование role_hierarchy - это отличный способ предоставить эту роль пользователям, которым она нужна.

Как узнать, что имперсонализация активна

Вы можете использовать специальный атрибут IS_IMPERSONATOR, чтобы проверить, активна ли имперсонализация в этой сессии. Используйте специальную роль, к примеру, чтобы отобразить ссылку для выхода из имперсонализации в шаблоне:

1
2
3
{% if is_granted('IS_IMPERSONATOR') %}
    <a href="{{ impersonation_exit_path(path('homepage') ) }}">Exit impersonation</a>
{% endif %}

Поиск изначального пользователя

В некоторых случаях, вам может понадобиться получить объект, который прердставляет пользователя-имперсонатера, а не имперсонализируемого пользователя. Когда пользователь имперсонализируется, токен, хранящийся в хранилище токенов, будет экземпляром SwitchUserToken. Используйте следующий отрезок, чтобы получить изначальный токен, который предоставляет вам доступ к пользователю-имперсонатору:

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

use Symfony\Bundle\SecurityBundle\Security;
use Symfony\Component\Security\Core\Authentication\Token\SwitchUserToken;
// ...

class SomeService
{
    public function __construct(
        private Security $security,
    ) {
    }

    public function someMethod(): void
    {
        // ...

        $token = $this->security->getToken();

        if ($token instanceof SwitchUserToken) {
            $impersonatorUser = $token->getOriginalToken()->getUser();
        }

        // ...
    }
}

Контроль параметра запроса

Эта функция должна быть доступна только ограниченной группе пользователей. По умолчанию, доступ предоставляется пользователям с ролью ROLE_ALLOWED_TO_SWITCH. Имя этой роли можно изменить через настройку role. Вы можете также настроить имя параметра запроса через настройку parameter:

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

    firewalls:
        main:
            # ...
            switch_user: { role: ROLE_ADMIN, parameter: _want_to_be_this_user }

Перенаправление по конкретному целевому маршруту

Note

Работает только в брандмауэре с состояниями.

Эта функция позволяет вам контролировать перенаправление по целевому маршруту через target_route.

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

    firewalls:
        main:
            # ...
            switch_user: { target_route: app_user_dashboard }

Ограничение переключений между пользователями

Если вам нужно больше контроля над перреключениями между пользователями, вы можете использовать избиратель безопасности. Для начала, сконфигуриуйте switch_user, чтобы проверить наличие нового пользовательского атрибута. Это может быть чем угодно, но не может начинаться с ROLE_ (чтобы гарантировать, что только ваш избиратель будет вызван):

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

    firewalls:
        main:
            # ...
            switch_user: { role: CAN_SWITCH_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
// src/Security/Voter/SwitchToCustomerVoter.php
namespace App\Security\Voter;

use Symfony\Bundle\SecurityBundle\Security;
use Symfony\Component\Security\Core\Authentication\Token\TokenInterface;
use Symfony\Component\Security\Core\Authorization\Voter\Voter;
use Symfony\Component\Security\Core\User\UserInterface;

class SwitchToCustomerVoter extends Voter
{
    public function __construct(
        private Security $security,
    ) {
    }

    protected function supports($attribute, $subject): bool
    {
        return in_array($attribute, ['CAN_SWITCH_USER'])
            && $subject instanceof UserInterface;
    }

    protected function voteOnAttribute($attribute, $subject, TokenInterface $token): bool
    {
        $user = $token->getUser();
        // если пользователь анонимный или если субъект не есть пользователем, не предоставлять доступ
        if (!$user instanceof UserInterface || !$subject instanceof UserInterface) {
            return false;
        }

        // вы все еще можете проверить ROLE_ALLOWED_TO_SWITCH
        if ($this->security->isGranted('ROLE_ALLOWED_TO_SWITCH')) {
            return true;
        }

        // проверить любые желаемые вами роли
        if ($this->security->isGranted('ROLE_TECH_SUPPORT')) {
            return true;
        }

        /*
         * или ипользовать какие-то пользовательские данные из вашего объекта User
        if ($user->isAllowedToSwitch()) {
            return true;
        }
        */

        return false;
    }
}

Это все! При переключении между пользователями, теперь ваш избиратель имеет полный контроль над тем, разрешено это или нет. Если ваш избиратель не вызывается, смотрите .

События

Событие security.switch_user развертывается непосредственно перед тем, как имперсонация полностью завершена. Ваш слушатель или подписчик
получит SwitchUserEvent, которое вы можете использовать для получения пользователя, которого вы сейчас имперсонализируете.

Это событие также развертывается непосредственно перед тем, как имперсонация будет полностью завершена. Вы можете использовать его для получения первоначального имперсонализированного пользователя.

Раздел не обновляет локаль, когда вы имперсонализируете пользователя. Если вы хотите быть уверены в обновлении локали при смене пользователя, добавьте подписчика события в это событие:

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

use Symfony\Component\EventDispatcher\EventSubscriberInterface;
use Symfony\Component\Security\Http\Event\SwitchUserEvent;
use Symfony\Component\Security\Http\SecurityEvents;

class SwitchUserSubscriber implements EventSubscriberInterface
{
    public function onSwitchUser(SwitchUserEvent $event): void
    {
        $request = $event->getRequest();

        if ($request->hasSession() && ($session = $request->getSession())) {
            $session->set(
                '_locale',
                // предполагая, что ваш User имеет некоторый метод getLocale()
                $event->getTargetUser()->getLocale()
            );
        }
    }

    public static function getSubscribedEvents(): array
    {
        return [
                // константа для security.switch_user
            SecurityEvents::SWITCH_USER => 'onSwitchUser',
        ];
    }
}

Вот и всё! Если вы используете конфигурацию services.yml по умолчанию , то Symfony автоматически обнаружит ваш сервис и вызовет onSwitchUser, когда произойдёт смена пользователей.

Чтобы узнать больше деталей о подписчиках событий, смотрите События и слушатели событий.