我有一个应用程序,管理员经常代表普通用户。流程如下:
<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'}
,但这似乎非常不令人满意。
推荐的方法是什么?
答案 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());
}
请注意:
$this->get('my.user_provider')
替换为防火墙的用户提供商。不幸的是,我无法找到以编程方式获取当前防火墙用户的方法。