如何模拟一个用户
有时,能够从一个用户切换到另一个用户却毋须登出和再次登入,是有必要的(例如,在你调试或者尝试搞清一个“某用户可见,你却不能重现”的bug时)。
模拟用户不可兼容 预认证防火墙。原因是,模拟过程的认证状态,需要被保持在服务器端,而预认证信息(SSL_CLIENT_S_DN_Email
,REMOTE_USER
或其他)是在每一次请求时发送的。
激活 switch_user
防火墙监听,即轻松搞定模拟用户:
|
# app/config/security.yml
security:
# ...
firewalls:
main:
# ...
switch_user: true |
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
|
<!-- app/config/security.xml -->
<?xml version="1.0" encoding="UTF-8"?>
<srv:container xmlns="Http://symfony.com/schema/dic/security"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns:srv="http://symfony.com/schema/dic/services"
xsi:schemaLocation="http://symfony.com/schema/dic/services
http://symfony.com/schema/dic/services/services-1.0.xsd">
<config>
<!-- ... -->
<firewall name="main">
<!-- ... -->
<switch-user />
</firewall>
</config>
</srv:container> |
|
// app/config/security.php
$container->loadFromExtension('security', array(
// ...
'firewalls' => array(
'main'=> array(
// ...
'switch_user' => true,
),
),
)); |
要切到其他用户,只需为当前URL添加一个查询字符串(query string)即 _switch_user
参数,同时把用户名作为其值:
|
http://example.com/somewhere?_switch_user=thomas |
要切回原来的用户,使用特殊的 _exit
用户名:
|
http://example.com/somewhere?_switch_user=_exit |
在模拟期间,用户被赋予了一个特殊的role,叫做 ROLE_PREVIOUS_ADMIN
。在模板中,比如,该角色可以用于显示一个“退出模拟用户”之连接:
|
{% if is_grAnted('ROLE_PREVIOUS_ADMIN') %}
<a href="{{ path('homepage', {'_switch_user': '_exit'}) }}">Exit impersonation</a>
{% endif %} |
|
<?php if ($view['security']->isGranted('ROLE_PREVIOUS_ADMIN')): ?>
<a href="<?php echo $view['router']->path('homepage', array(
'_switch_user' => '_exit',
) ?>">
Exit impersonation
</a>
<?php endif ?> |
某些情况下,你可能需要去获取“正在模拟别人的”用户对象,而不是“正被别人模拟的”用户对象。使用以下码段去遍历用户的roles,直至找到一个(持有) SwitchUserRole
的对象:
1
2
3
4
5
6
7
8
9
10
11
12
13
|
use Symfony\Component\Security\Core\Role\SwitchUserRole;
$authChecker = $this->get('security.authorization_checker');
$tokenStorage = $this->get('security.token_storage');
if ($authChecker->isGranted('ROLE_PREVIOUS_ADMIN')) {
foreach ($tokenStorage->getToken()->getRoles() as $role) {
if ($role instanceof SwitchUserRole) {
$impersonatingUser = $role->getSource()->getUser();
break;
}
}
} |
当然,这个功能需要基于一个小型用户组才能使用。默认时,访问仅限于有 ROLE_ALLOWED_TO_SWITCH
role的用户。这个角色的名称可以通过设置 role
来修改。为强化安全性,你还可以通过 parameter
来更改查询参数名称(query parameter name):
|
# app/config/security.yml
security:
# ...
firewalls:
main:
# ...
switch_user: { role: ROLE_ADMIN, parameter: _want_to_be_this_user } |
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
|
<!-- app/config/security.xml -->
<?xml version="1.0" encoding="UTF-8"?>
<srv:container xmlns="http://symfony.com/schema/dic/security"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns:srv="http://symfony.com/schema/dic/services"
xsi:schemaLocation="http://symfony.com/schema/dic/services
http://symfony.com/schema/dic/services/services-1.0.xsd">
<config>
<!-- ... -->
<firewall name="main">
<!-- ... -->
<switch-user role="ROLE_ADMIN" parameter="_want_to_be_this_user" />
</firewall>
</config>
</srv:container> |
1
2
3
4
5
6
7
8
9
10
11
12
13
14
|
// app/config/security.php
$container->loadFromExtension('security', array(
// ...
'firewalls' => array(
'main'=> array(
// ...
'switch_user' => array(
'role' => 'ROLE_ADMIN',
'parameter' => '_want_to_be_this_user',
),
),
),
)); |
事件 ¶
firewall在用户模拟完成之后,立即派遣 security.switch_user
事件。SwitchUserEvent 被传入监听(listener),你可以用该事件来获取现在正在模拟的用户(译注:被模拟的用户)。
当你在模拟一个用户时, 把locale信息“粘连”到用户的session周期中 一文中的locale将不再更新。下面的代码示例将展示如何改变“被粘连的locale(the sticky locale)”:
|
# app/config/services.yml
services:
app.switch_user_listener:
class: AppBundle\EventListener\SwitchUserListener
tags:
- { name: kernel.event_listener, event: security.switch_user, method: onSwitchUser |
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
|
<!-- app/config/services.xml -->
<?xml version="1.0" encoding="UTF-8" ?>
<container xmlns="http://symfony.com/schema/dic/services"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://symfony.com/schema/dic/services
http://symfony.com/schema/dic/services/services-1.0.xsd"
>
<services>
<service id="app.switch_user_listener"
class="AppBundle\EventListener\SwitchUserListener"
>
<tag name="kernel.event_listener"
event="security.switch_user"
method="onSwitchUser"
/>
</service>
</services>
</container> |
|
// app/config/services.php
$container
->register('app.switch_user_listener', 'AppBundle\EventListener\SwitchUserListener')
->addTag('kernel.event_listener', array('event' => 'security.switch_user', 'method' => 'onSwitchUser'))
; |
监听器的实现过程,已经假设你 User
entity拥有一个 getLocale()
方法。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
|
// src/AppBundle/EventListener/SwitchUserListener.php
namespace AppBundle\EventListener;
use Symfony\Component\Security\Http\Event\SwitchUserEvent;
class SwitchUserListener
{
public function onSwitchUser(SwitchUserEvent $event)
{
$event->getRequest()->getSession()->set(
'_locale',
$event->getTargetUser()->getLocale()
);
}
} |