是否可以使用 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;
}
}
我正在使用的网站在导航栏中包含一个供超级用户使用的搜索框,这样他们就可以 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;
}
}