跳到内容

如何模拟用户

编辑此页

有时,能够从一个用户切换到另一个用户而无需注销和重新登录是很有用的(例如,当您调试用户看到而您无法重现的内容时)。

警告

用户模拟与某些身份验证机制(例如 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

提示

您可以利用 Twig 函数 impersonation_path('thomas')

提示

除了添加 _switch_user 查询字符串参数之外,您还可以通过调整 parameter 设置在自定义 HTTP 标头中传递用户名。例如,要使用 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

提示

您可以利用 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 }

重定向到特定的目标路由

注意

它仅在有状态防火墙中工作。

此功能允许您通过 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
50
// 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\AccessDecisionManagerInterface;
use Symfony\Component\Security\Core\Authorization\Voter\Voter;
use Symfony\Component\Security\Core\User\UserInterface;

class SwitchToCustomerVoter extends Voter
{
    public function __construct(
        private AccessDecisionManagerInterface $accessDecisionManager,
    ) {
    }

    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 the user is anonymous or if the subject is not a user, do not grant access
        if (!$user instanceof UserInterface || !$subject instanceof UserInterface) {
            return false;
        }

        // you can still check for ROLE_ALLOWED_TO_SWITCH
        if ($this->accessDecisionManager->decide($token, ['ROLE_ALLOWED_TO_SWITCH'])) {
            return true;
        }

        // check for any roles you want
        if ($this->accessDecisionManager->decide($token, ['ROLE_TECH_SUPPORT'])) {
            return true;
        }

        /*
         * or use some custom data from your User object
        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',
                // assuming your User has some getLocale() method
                $event->getTargetUser()->getLocale()
            );
        }
    }

    public static function getSubscribedEvents(): array
    {
        return [
            // constant for security.switch_user
            SecurityEvents::SWITCH_USER => 'onSwitchUser',
        ];
    }
}

就是这样!如果您正在使用 默认 services.yaml 配置,Symfony 将自动发现您的服务并在每次发生用户切换时调用 onSwitchUser

有关事件订阅器的更多详细信息,请参阅 事件和事件监听器

这项工作,包括代码示例,根据 Creative Commons BY-SA 3.0 许可获得许可。
目录
    版本