Як імперсонувати користувача

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

Як імперсонувати користувача

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

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 та імʼя користувача (або будь-яке поле, яке використовує наш постачальник користувачів для завантаження користувачів) як значення поточного URL:

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

Tip

Ви можете скористатися функцією гілки 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_PREVIOUS_ADMIN. Використання 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 [
            // constant for security.switch_user
            SecurityEvents::SWITCH_USER => 'onSwitchUser',
        ];
    }
}

Ось і все! Якщо ви використовуєте конфігурацію services.yml за замовчуванням , то Symfony автоматично виявить ваш сервіс та викличе onSwitchUser, коли відбудеться зміна користувачів.

Щоб дізнатися більше деталей про підписників подій, дивіться Події та слухачі подій.