在Symfony的FOSUserBundle中检测重复登录

时间:2016-11-01 08:07:46

标签: php symfony fosuserbundle

我在Symfony项目中使用FOSUserBundle,我需要删除重复登录。如果用户从其他系统登录,我希望他/她的其他会话断开连接。

你可以帮我一路走来吗?

2 个答案:

答案 0 :(得分:1)

简单方法:

activeSessionId字段映射到User类:

/**
 * @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事件,并将会话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 }

答案 1 :(得分:0)

嗯,我想,这是可能的,但不是开箱即用,需要进行一些调整。

让我们将问题分解为微小的一点:

  1. 会话管理驱动程序

    您需要将会话管理驱动程序设置为DB。这可以是任何类型的DBMS(例如MySQL)。这可以确保您的会话可以被查询。

  2. 用户名栏

    默认情况下,会话跟踪数据库表不包含用户名列。您需要ALTER TABLE才能添加username。确保它是NULL - 能够,否则Symfony根本无法写会话。

  3. 登录成功处理程序

    为防火墙定义登录成功处理程序(在security.yml内)。该处理程序需要:

    • 提取当前会话ID
    • 使用当前登录的用户
    • 更新数据库表中的记录
    • 执行DELETE FROM <session_table> WHERE username = <username> AND session_id != <current_session_id>
  4. 我可能错过了一些东西,但粗略地说,就是这样。

    希望它能帮助您实际编写自己的解决方案,