Как олицетворить пользователя

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

Caution

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

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

  • YAML
    1
    2
    3
    4
    5
    6
    7
    8
    # app/config/security.yml
    security:
        # ...
    
        firewalls:
            main:
                # ...
                switch_user: true
    
  • XML
     1
     2
     3
     4
     5
     6
     7
     8
     9
    10
    11
    12
    13
    14
    15
    16
    17
    <!-- app/config/security.xml -->
    <?xml version="1.0" encoding="UTF-8"?>
    <srv:container xmlns="http://symfony.com/schema/dic/security"
        xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
        xmlns:srv="http://symfony.com/schema/dic/services"
        xsi:schemaLocation="http://symfony.com/schema/dic/services
            http://symfony.com/schema/dic/services/services-1.0.xsd">
    
        <config>
            <!-- ... -->
    
            <firewall name="main">
                <!-- ... -->
                <switch-user />
            </firewall>
        </config>
    </srv:container>
    
  • PHP
     1
     2
     3
     4
     5
     6
     7
     8
     9
    10
    11
    // app/config/security.php
    $container->loadFromExtension('security', array(
        // ...
    
        'firewalls' => array(
            'main'=> array(
                // ...
                'switch_user' => true,
            ),
        ),
    ));
    

Чтобы переключиться на другого пользователя, просто добавьте строку запроса с параметром _switch_user и имя пользователя, как значения текущего URL:

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

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

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

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

  • Twig
    1
    2
    3
    {% if is_granted('ROLE_PREVIOUS_ADMIN') %}
        <a href="{{ path('homepage', {'_switch_user': '_exit'}) }}">Exit impersonation</a>
    {% endif %}
    
  • PHP
    1
    2
    3
    4
    5
    6
    7
    <?php if ($view['security']->isGranted('ROLE_PREVIOUS_ADMIN')): ?>
        <a href="<?php echo $view['router']->path('homepage', array(
            '_switch_user' => '_exit',
        )) ?>">
            Exit impersonation
        </a>
    <?php endif ?>
    

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

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
use Symfony\Component\Security\Core\Role\SwitchUserRole;

$authChecker = $this->get('security.authorization_checker');
$tokenStorage = $this->get('security.token_storage');

if ($authChecker->isGranted('ROLE_PREVIOUS_ADMIN')) {
    foreach ($tokenStorage->getToken()->getRoles() as $role) {
        if ($role instanceof SwitchUserRole) {
            $impersonatingUser = $role->getSource()->getUser();
            break;
        }
    }
}

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

  • YAML
    1
    2
    3
    4
    5
    6
    7
    8
    # app/config/security.yml
    security:
        # ...
    
        firewalls:
            main:
                # ...
                switch_user: { role: ROLE_ADMIN, parameter: _want_to_be_this_user }
    
  • XML
     1
     2
     3
     4
     5
     6
     7
     8
     9
    10
    11
    12
    13
    14
    15
    16
    <!-- app/config/security.xml -->
    <?xml version="1.0" encoding="UTF-8"?>
    <srv:container xmlns="http://symfony.com/schema/dic/security"
        xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
        xmlns:srv="http://symfony.com/schema/dic/services"
        xsi:schemaLocation="http://symfony.com/schema/dic/services
            http://symfony.com/schema/dic/services/services-1.0.xsd">
        <config>
            <!-- ... -->
    
            <firewall name="main">
                <!-- ... -->
                <switch-user role="ROLE_ADMIN" parameter="_want_to_be_this_user" />
            </firewall>
        </config>
    </srv:container>
    
  • PHP
     1
     2
     3
     4
     5
     6
     7
     8
     9
    10
    11
    12
    13
    14
    // app/config/security.php
    $container->loadFromExtension('security', array(
        // ...
    
        'firewalls' => array(
            'main'=> array(
                // ...
                'switch_user' => array(
                    'role' => 'ROLE_ADMIN',
                    'parameter' => '_want_to_be_this_user',
                ),
            ),
        ),
    ));
    

События

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

Статья Making the Locale "Sticky" during a User's Session не обновляет локаль, когда вы олицетваоряете пользователя. Если вы хотите быть уверенными в обновлении локали при переключении между пользователями, добавьте подписчика событий к этому событию:

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

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

class SwitchUserSusbcriber implements EventSubscriberInterface
{
    public function onSwitchUser(SwitchUserEvent $event)
    {
        $event->getRequest()->getSession()->set(
            '_locale',
            // предполагая, что ваш User имеет некоторый метод getLocale()
            $event->getTargetUser()->getLocale()
        );
    }

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

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

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

Эта документация является переводом официальной документации Symfony и предоставляется по свободной лицензии CC BY-SA 3.0.