是否可以使用 Symfony `?_switch_user` 而不必在模拟之间 "logout"?

Is it possible to use Symfony `?_switch_user` without having to "logout" between impersonations?

我正在使用的网站在导航栏中包含一个供超级用户使用的搜索框,这样他们就可以 select 来自通过 selected 用户名的一组普通用户使用 Symfony 的 ?_switch_user= 功能来模拟。

我正在使用 jQuery 到 return 当前页面的路由,并为所需的用户附加适当的 ?_switch_user=username,如下所示:

树枝:

{% if is_granted('ROLE_PREVIOUS_ADMIN') %}
      <form class="navbar-form navbar-left" role="search">
            <div class="form-group">
                 <input type="text" id="search-names" class="form-control" placeholder="User name">
            </div>
      </form>
      <li><a href="{{ path( app.request.get('_route'), {'_switch_user':'_exit'})  }}">~Return To Admin~<span class="sr-only">Return To Admin</span></a></li>
{% endif %}

jQuery:

$(function() {
  $("#search-names").autocomplete({
        source: "{{ path('usersearch') }}",
        minLength: 2,
        select: function (event, matched) {
             console.log(matched)
             window.location = window.location + '?_switch_user=' + matched.item.value
        }
  });
})

在上面,我必须包含 ~Return To Admin~ link 以便超级用户可以 'logout' 每个模拟 - 否则 Symfony return 是一个错误说另一个切换用户已经登录。

如果他们可以从一个普通用户切换到另一个普通用户,而不必每次都请求 ?_switch_user=_exit,那将更加“(超级)用户友好”(尽管我仍将按钮保留为当他们执行仅限管理员的任务时需要它)

有没有简单的方法可以做到这一点?我发现一篇文章建议通过创建一个新的侦听器“Making impersonating a user more friendly”(他们文章中的 'second feature')来解决这个问题,但是我无法让它工作,我想知道它是否是由于 Symfony3 的结构差异?

我终于抽出空闲时间继续将您 link 编写的代码改编为 Symfony 3。下面的代码应该使 Return 到 Admin 的工作如 link 以及可以在已经切换后直接切换到另一个用户。

<?php
namespace AppBundle\Listener;


use Symfony\Component\Security\Core\Exception\AccessDeniedException;
use Symfony\Component\Security\Core\User\UserInterface;
use Symfony\Component\Security\Core\User\UserProviderInterface;
use Symfony\Component\Security\Core\User\UserCheckerInterface;
use Symfony\Component\Security\Core\Authorization\AccessDecisionManagerInterface;
use Psr\Log\LoggerInterface;
use Symfony\Component\HttpKernel\Event\GetResponseEvent;
use Symfony\Component\Security\Core\Exception\AuthenticationException;
use Symfony\Component\HttpFoundation\RedirectResponse;
use Symfony\Component\HttpFoundation\Request;
use Symfony\Component\Security\Core\Role\SwitchUserRole;
use Symfony\Component\Security\Core\Authentication\Token\UsernamePasswordToken;
use Symfony\Component\Security\Core\Authentication\Token\Storage\TokenStorageInterface;
use Symfony\Component\Security\Core\Exception\AuthenticationCredentialsNotFoundException;
use Symfony\Component\Security\Core\Authentication\Token\TokenInterface;
use Symfony\Component\Security\Http\Event\SwitchUserEvent;
use Symfony\Component\Security\Http\SecurityEvents;
use Symfony\Component\Security\Http\Firewall\SwitchUserListener;
use Symfony\Component\EventDispatcher\EventDispatcherInterface;

class MySwitchUserListener extends SwitchUserListener
{
    private $useOverrideUri = true;

    public function handle(GetResponseEvent $event)
    {
        $request = $event->getRequest();

        if( !$request->get( $this->usernameParameter ) )
            return;

        if('_exit' === $request->get( $this->usernameParameter ) )
            $this->tokenStorage->setToken( $this->attemptExitUser( $request ) );
        else{
            try{
                $this->tokenStorage->setToken( $this->attemptSwitchUser( $request ) );
            }catch( AuthenticationException $e ){
                throw new \LogicException(sprintf('Switch User failed: "%s"', $e->getMessage()));
            }
        }

        $request->query->remove( $this->usernameParameter );

    $overrideUri = $session->get( 'onSwitchURI', NULL );
    if( $request->get( 'returnTo' ) ){
        $session->set( 'onSwitchURI', $request->get( 'returnTo' ) );
        $request->query->remove( 'returnTo' );
    }
    else
        $session->remove( 'onSwitchURI' );

        $request->server->set( 'QUERY_STRING', http_build_query( $request->query->all() ) );
        $response = new RedirectResponse( $request->getUri(), 302 );
        $event->setResponse( $response );
    }

    private function attemptSwitchUser(Request $request)
    {
        $token = $this->tokenStorage->getToken();

        $originalToken = $this->getOriginalToken($token);

        if (false !== $originalToken) {
            if ($token->getUsername() === $request->get($this->usernameParameter)) {
                return $token;
            }
            $token = $originalToken;
            $this->useOverrideUri = false;
        }
        if (false === $this->accessDecisionManager->decide($token, array($this->role)))
            throw new AccessDeniedException();

        $username = $request->get($this->usernameParameter);

        if(null !== $this->logger)
            $this->logger->info('Attempting to switch to user.', array('username' => $username));

        $user = $this->provider->loadUserByUsername($username);
        $this->userChecker->checkPostAuth($user);
        $roles = $user->getRoles();
        $roles[] = new SwitchUserRole( 'ROLE_PREVIOUS_ADMIN', $token );
        $token = new UsernamePasswordToken( $user, $user->getPassword(), $this->providerKey, $roles );
        if (null !== $this->dispatcher) {
            $switchEvent = new SwitchUserEvent($request, $token->getUser());
            $this->dispatcher->dispatch(SecurityEvents::SWITCH_USER, $switchEvent);
        }
        return $token;
    }

}