Как имперсонализировать пользователя
Дата обновления перевода 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
, когда
произойдёт смена пользователей.
Чтобы узнать больше деталей о подписчиках событий, смотрите События и слушатели событий.