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

Дата обновления перевода 2023-01-11

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

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

Caution

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

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

  • YAML
  • XML
  • PHP
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

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

  • YAML
  • XML
  • PHP
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

Эта функция доступна только пользователям со специально ролью под названием 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
28
29
// src/Service/SomeService.php
namespace App\Service;

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

class SomeService
{
    private $security;

    public function __construct(Security $security)
    {
        $this->security = $security;
    }

    public function someMethod()
    {
        // ...

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

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

        // ...
    }
}

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

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

  • YAML
  • XML
  • PHP
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 }

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

6.2

Опция конфигурации target_route была представлена в Symfony 6.2.

Note

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

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

  • YAML
  • XML
  • PHP
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_ (чтобы гарантировать, что только ваш избиратель будет вызван):

  • YAML
  • XML
  • PHP
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
50
51
// 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
{
    private $security;

    public function __construct(Security $security)
    {
        $this->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, когда произойдёт смена пользователей.

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