以编程方式注销切换用户

Log out of a switched user programmatically

我有一个应用程序,其中管理员经常代表普通用户行事。流程如下:

此时管理页面显示一个按钮<a href="{{ path('_admin', {'_switch_user': '_exit'}) }}">Exit {{app.user.username}} </a>

但是,管理员用户经常忘记点击它,而是点击另一个非管理员用户。当他们这样做时,他们会收到一条消息 You are already switched to User XXX。显然这是有意为之,但对于管理员用户来说仍然不是很好的体验。

允许管理员以这种方式切换用户的推荐方法是什么?我认为每次他们到达普通用户列表时调用 {'_switch_user': '_exit'} 是一个不错的选择(同时强制永远不缓存此页面),但我看不到以编程方式执行此操作的方法。

另一种方法可能是访问Javascript中的URL{'_switch_user': '_exit'},但这似乎不太令人满意。

推荐的方法是什么?

如果您想强制不缓存响应,您可以在控制器级别执行此操作。 您可以按如下方式呈现私人回复:

$expire = new \DateTime('now');
$expire->sub(new \DateInterval('P1Y'));
$response = new Response();
$response->setPrivate();
$response->setExpires($expire);
$response->headers->add(array('Pragma' => 'no-cache'));

然后在你的控制器中使用 render 方法渲染你的视图,yan 可以作为第三个参数传递给你的私有响应

您应该看看 SwitchUserListener Class (Symfony\Component\Security\Http\Firewall\SwitchUserListener)。

这就是奇迹发生的地方(以及 "you are already switched to... message is created")。问题是它并没有给你太多选择来连接它。其实官方唯一的就是切换成功后的事件。

但是您可以用您自己的实现完全替换此监听器。 在我自己的项目中使用它来实现用户切换的 ip 限制(acls 不起作用或者我弄错了)

只需创建您自己的并在您的 services.yml

中替换它
security.authentication.switchuser_listener:
    class: AppBundle\Services\Security\SwitchUserListener
    public: false
    abstract: true
    arguments: ['@security.token_storage', '', '@security.user_checker', '', '@security.access.decision_manager', '@?logger', '_switch_user', 'ROLE_ALLOWED_TO_SWITCH', '@?event_dispatcher']
    tags:
        - { name: monolog.logger, channel: security }

编辑:您需要复制整个内容,因为所有相关功能都是私有的。它不是很漂亮,但我看不到任何其他方式(除了在你的用例中,当用户已经切换时可能根本不显示 switch-link。)

正如 Joe 所说,Symfony 的 SwitchUserListener especially SwitchUserListener::attemptExitUser 拥有以编程方式注销模拟用户所需的所有信息。

这是它的要点(在控制器内部使用):

$authorizationChecker = $this->get('security.authorization_checker');

if ($authorizationChecker->isGranted('ROLE_PREVIOUS_ADMIN')) {
    // Code from Symfony\Component\Security\Http\Firewall\SwitchUserListener
    $tokenStorage = $this->get('security.token_storage');

    $originalToken = false;
    if ($currentToken = $tokenStorage->getToken()) {
        foreach ($currentToken->getRoles() as $role) {
            if ($role instanceof SwitchUserRole) {
                $originalToken = $role->getSource();

                break;
            }
        }
    }

    if ($currentToken === null || $originalToken === false) {
        throw new AuthenticationCredentialsNotFoundException('Could not find original Token object.');
    }

    if ($this->has('event_dispatcher') && $originalToken->getUser() instanceof UserInterface) {
        $eventDispatcher = $this->get('event_dispatcher');
        // Attention: Put your user provider here. I wasn't able to find a way to
        //            get the current firewall's user provider programatically.
        $userProvider = $this->get('my.user_provider');

        $originalUser = $userProvider->refreshUser($originalToken->getUser());
        $switchEvent  = new SwitchUserEvent($request, $originalUser);
        $eventDispatcher->dispatch(SecurityEvents::SWITCH_USER, $switchEvent);
    }

    $tokenStorage->setToken($originalToken);

    // Redirect to the current action so that the request's user is the original one.
    return $this->redirect($request->getRequestUri());
}

请注意:

  • 这在 Symfony 2.8 中进行了测试,但对于 3.x 甚至 4.x
  • 应该没有太大区别
  • 您必须将 $this->get('my.user_provider') 替换为防火墙的用户提供商。不幸的是,我无法找到以编程方式获取当前防火墙用户的方法。