以编程方式注销已切换的用户

时间:2016-10-10 13:11:13

标签: symfony fosuserbundle

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

  • 管理员登录
  • 向管理员显示非管理员用户列表
  • 管理员点击指向非管理员用户<a href="{{ path('_main_app', {'_switch_user': user.username}) }}">
  • 的链接
  • 管理员以用户身份完成任务
  • 管理员点击浏览器,直到它们再次出现在非管理员用户列表中

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

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

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

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

推荐的方法是什么?

3 个答案:

答案 0 :(得分:1)

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

这就是魔术发生的地方(并且“你已经切换到......消息被创建”)。问题是它并没有真正给你很多选择。实际上唯一正式的是成功切换后的事件。

但是您可以使用自己的实现完全替换此Listener。 在我自己的项目中用于为用户切换实现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 }

编辑:您需要复制整件事,因为所有相关功能都是私有的。它不漂亮,但我没有看到任何其他方式(除非你的用例可能在用户已经切换时根本不显示开关链接。)

答案 1 :(得分:0)

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

$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可以作为私有响应的第三个参数传递

答案 2 :(得分:0)

正如乔所说,Symfony的SwitchUserListener尤其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')替换为防火墙的用户提供商。不幸的是,我无法找到以编程方式获取当前防火墙用户的方法。