在 Symfony 的 FOSUserBundle 中检测重复登录
Detecting duplicate logins in Symfony's FOSUserBundle
我在我的 Symfony 项目中使用 FOSUserBundle,我需要删除重复的登录。如果用户从不同的系统登录,我希望 his/her 断开其他会话。
你能帮我一下吗?
嗯,我想,这是可能的,但不是开箱即用的,需要一些调整。
让我们把问题分解成微小的部分:
会话管理驱动程序
您需要将会话管理驱动程序设置为数据库。这可以是任何类型的 DBMS(例如 MySQL
)。这确保您的会话可以被查询。
用户名栏
默认情况下,会话跟踪数据库table 不包括用户名列。您需要 ALTER TABLE
才能添加 username
。确保它是 NULL
-able,否则 Symfony
根本无法写入会话。
登录成功处理程序
为您的防火墙定义登录成功处理程序(在 security.yml
内)。该处理程序需要:
- 提取当前会话 ID
- 用当前登录的用户
更新数据库table中的记录
- 执行
DELETE FROM <session_table> WHERE username = <username> AND session_id != <current_session_id>
我可能漏掉了什么,但大致就是这样。
希望它能帮助您实际编写自己的解决方案,
简单的方法:
将 activeSessionId
字段映射到您的 User
class:
/**
* @ORM\Entity
* @ORM\Table(name="fos_user")
*/
class User extends BaseUser
{
/**
* @ORM\Id
* @ORM\Column(type="integer")
* @ORM\GeneratedValue(strategy="AUTO")
*/
protected $id;
/**
* @ORM\Column(type="string", length=255, nullable=true)
*/
protected $activeSessionId;
public function loginWithSessId($sessionId)
{
$this->activeSessionId = $sessionId;
}
public function logout()
{
$this->activeSessionId = null;
}
public function getActiveSessId()
{
return $this->activeSessionId;
}
}
然后监听每次用户登录时都会触发的security.interactive_login事件,并与用户一起保存session id的引用:
namespace AppBundle\Security;
use Symfony\Component\EventDispatcher\EventSubscriberInterface;
use Symfony\Component\Security\Http\Event\InteractiveLoginEvent;
use Symfony\Component\Security\Http\SecurityEvents;
use FOS\UserBundle\Model\UserManagerInterface;
class LoginListener implements EventSubscriberInterface
{
private $userManager;
public function __construct(UserManagerInterface $userManager)
{
$this->userManager = $userManager;
}
public static function getSubscribedEvents()
{
return array(
SecurityEvents::INTERACTIVE_LOGIN => 'onSecurityInteractiveLogin',
);
}
public function onSecurityInteractiveLogin(InteractiveLoginEvent $event)
{
$user = $event->getAuthenticationToken()->getUser();
$session = $event->getRequest()->getSession();
$user->loginWithSessId($session->getId());
$this->userManager->updateUser($user);
}
}
然后您可以使用以下方式注册侦听器:
<service id="app_bundle.security.login_listener" class="AppBundle\Security\LoginListener">
<argument type="service" id="fos_user.user_manager"/>
<tag name="kernel.event_subscriber" />
</service>
或
# app/config/services.yml
services:
app_bundle.security.login_listener:
class: AppBundle\Security\LoginListener
arguments: ['@fos_user.user_manager']
tags:
- { name: kernel.event_subscriber }
现在您的 User
实体知道哪个会话是最后一个会话,您可以创建一个 security.authentication.success
事件的侦听器,并检查当前会话 ID 是否与最后一个活动会话 ID 匹配。如果不是,则它不再是活动会话。
namespace AppBundle\Security;
use Symfony\Component\Security\Core\AuthenticationEvents;
use Symfony\Component\EventDispatcher\EventSubscriberInterface;
use Symfony\Component\Security\Core\Event\AuthenticationEvent;
use Symfony\Component\HttpFoundation\RequestStack;
use FOS\UserBundle\Model\UserManagerInterface;
class AuthenticationListener implements EventSubscriberInterface
{
private $requestStack;
private $userManager;
public function __construct(RequestStack $requestStack, UserManagerInterface $userManager)
{
$this->requestStack = $requestStack;
$this->userManager = $userManager;
}
public static function getSubscribedEvents()
{
return array(
AuthenticationEvents::AUTHENTICATION_SUCCESS => 'onAuthenticationSuccess',
);
}
public function onAuthenticationSuccess(AuthenticationEvent $event)
{
$token = $event->getAuthenticationToken();
$sessionId = $this->requestStack->getMasterRequest()->getSession()->getId();
$activeSessId = $token->getUser()->getActiveSessId();
if ($activeSessId && $sessionId !== $activeSessId) {
$token->setAuthenticated(false); // Sets the authenticated flag.
}
}
}
最后:
<service id="app_bundle.security.auth_listener" class="AppBundle\Security\AuthenticationListener">
<argument type="service" id="request_stack"/>
<argument type="service" id="fos_user.user_manager"/>
<tag name="kernel.event_subscriber" />
</service>
或
# app/config/services.yml
services:
app_bundle.security.auth_listener:
class: AppBundle\Security\AuthenticationListener
arguments: ['@request_stack', '@fos_user.user_manager']
tags:
- { name: kernel.event_subscriber }
我在我的 Symfony 项目中使用 FOSUserBundle,我需要删除重复的登录。如果用户从不同的系统登录,我希望 his/her 断开其他会话。
你能帮我一下吗?
嗯,我想,这是可能的,但不是开箱即用的,需要一些调整。
让我们把问题分解成微小的部分:
会话管理驱动程序
您需要将会话管理驱动程序设置为数据库。这可以是任何类型的 DBMS(例如
MySQL
)。这确保您的会话可以被查询。用户名栏
默认情况下,会话跟踪数据库table 不包括用户名列。您需要
ALTER TABLE
才能添加username
。确保它是NULL
-able,否则Symfony
根本无法写入会话。登录成功处理程序
为您的防火墙定义登录成功处理程序(在
security.yml
内)。该处理程序需要:- 提取当前会话 ID
- 用当前登录的用户 更新数据库table中的记录
- 执行
DELETE FROM <session_table> WHERE username = <username> AND session_id != <current_session_id>
我可能漏掉了什么,但大致就是这样。
希望它能帮助您实际编写自己的解决方案,
简单的方法:
将 activeSessionId
字段映射到您的 User
class:
/**
* @ORM\Entity
* @ORM\Table(name="fos_user")
*/
class User extends BaseUser
{
/**
* @ORM\Id
* @ORM\Column(type="integer")
* @ORM\GeneratedValue(strategy="AUTO")
*/
protected $id;
/**
* @ORM\Column(type="string", length=255, nullable=true)
*/
protected $activeSessionId;
public function loginWithSessId($sessionId)
{
$this->activeSessionId = $sessionId;
}
public function logout()
{
$this->activeSessionId = null;
}
public function getActiveSessId()
{
return $this->activeSessionId;
}
}
然后监听每次用户登录时都会触发的security.interactive_login事件,并与用户一起保存session id的引用:
namespace AppBundle\Security;
use Symfony\Component\EventDispatcher\EventSubscriberInterface;
use Symfony\Component\Security\Http\Event\InteractiveLoginEvent;
use Symfony\Component\Security\Http\SecurityEvents;
use FOS\UserBundle\Model\UserManagerInterface;
class LoginListener implements EventSubscriberInterface
{
private $userManager;
public function __construct(UserManagerInterface $userManager)
{
$this->userManager = $userManager;
}
public static function getSubscribedEvents()
{
return array(
SecurityEvents::INTERACTIVE_LOGIN => 'onSecurityInteractiveLogin',
);
}
public function onSecurityInteractiveLogin(InteractiveLoginEvent $event)
{
$user = $event->getAuthenticationToken()->getUser();
$session = $event->getRequest()->getSession();
$user->loginWithSessId($session->getId());
$this->userManager->updateUser($user);
}
}
然后您可以使用以下方式注册侦听器:
<service id="app_bundle.security.login_listener" class="AppBundle\Security\LoginListener">
<argument type="service" id="fos_user.user_manager"/>
<tag name="kernel.event_subscriber" />
</service>
或
# app/config/services.yml
services:
app_bundle.security.login_listener:
class: AppBundle\Security\LoginListener
arguments: ['@fos_user.user_manager']
tags:
- { name: kernel.event_subscriber }
现在您的 User
实体知道哪个会话是最后一个会话,您可以创建一个 security.authentication.success
事件的侦听器,并检查当前会话 ID 是否与最后一个活动会话 ID 匹配。如果不是,则它不再是活动会话。
namespace AppBundle\Security;
use Symfony\Component\Security\Core\AuthenticationEvents;
use Symfony\Component\EventDispatcher\EventSubscriberInterface;
use Symfony\Component\Security\Core\Event\AuthenticationEvent;
use Symfony\Component\HttpFoundation\RequestStack;
use FOS\UserBundle\Model\UserManagerInterface;
class AuthenticationListener implements EventSubscriberInterface
{
private $requestStack;
private $userManager;
public function __construct(RequestStack $requestStack, UserManagerInterface $userManager)
{
$this->requestStack = $requestStack;
$this->userManager = $userManager;
}
public static function getSubscribedEvents()
{
return array(
AuthenticationEvents::AUTHENTICATION_SUCCESS => 'onAuthenticationSuccess',
);
}
public function onAuthenticationSuccess(AuthenticationEvent $event)
{
$token = $event->getAuthenticationToken();
$sessionId = $this->requestStack->getMasterRequest()->getSession()->getId();
$activeSessId = $token->getUser()->getActiveSessId();
if ($activeSessId && $sessionId !== $activeSessId) {
$token->setAuthenticated(false); // Sets the authenticated flag.
}
}
}
最后:
<service id="app_bundle.security.auth_listener" class="AppBundle\Security\AuthenticationListener">
<argument type="service" id="request_stack"/>
<argument type="service" id="fos_user.user_manager"/>
<tag name="kernel.event_subscriber" />
</service>
或
# app/config/services.yml
services:
app_bundle.security.auth_listener:
class: AppBundle\Security\AuthenticationListener
arguments: ['@request_stack', '@fos_user.user_manager']
tags:
- { name: kernel.event_subscriber }