Symfony 4:由管理员注销活动用户

Posted

技术标签:

【中文标题】Symfony 4:由管理员注销活动用户【英文标题】:Symfony 4: Logout active user by admin 【发布时间】:2019-12-03 10:30:57 【问题描述】:

我的 Symfony 4.2 应用程序中的管理员用户应该能够注销另一个(非管理员)用户。我根据 Symfony 安全包 (https://symfony.com/doc/current/security/form_login_setup.html) 创建了一个用户登录系统。

现在我正在构建一个管理仪表板,其中必须列出所有用户的在线状态(上次活动)。

是否有推荐的方法来列出活跃用户并在需要时终止他们的会话?

我读过一些这样的帖子:Symfony how to return all logged in Active Users。但答案有点老了,只是列出了活跃用户。

【问题讨论】:

您使用的是什么会话处理程序? 好问题。我没有为会话处理更改或安装任何东西。因此,如果有默认值,我会说“默认值”。我通过 make:user 命令安装了用户登录处理。 【参考方案1】:

这是杀死用户会话的好方法: 将EventListeneronKernelRequest 事件一起使用。在您的主代码中:public function onKernelRequest(KernelEvent $event)

$request = $event->getRequest();
$token = $this->container->get('security.token_storage')->getToken();

if ($token === null)  // somehow
        return;


if ($token->getUser()->isLocked() === true) 
        // you must implement a boolean flag on your user Entities, which the admins can set to false
        $this->container->get('security.token_storage')->setToken(); // default is null, therefore null
        $request->getSession()->invalidate(); // these lines will invalidate user session on next request
        return;
 

现在,关于您的另一个问题:如何列出用户的在线状态?很简单,您的用户实体应该实现另一个布尔标志,例如 isOnline(带有 getter 和 setter)。

接下来,您应该创建一个LoginListener(无需实现任何接口)。在你的主代码中:

public function onSecurityInteractiveLogin(InteractiveLoginEvent $event) 
       $user = $event->getAuthenticationToken()->getUser();
       if ($user instanceof UserInterface) 
             // set isOnline flag === true
             // you will need to fetch the $user with the EntityManager ($this->em)
             // make sure it exists, set the flag and then
             $this->em->flush();
       

您的第三个事件应该是LogoutListener,您将在其中设置isOnline flag === false

当用户请求注销时,Symfony 调用 LogoutListener(作为处理程序)。 但是你可以自己写:

class LogoutListener implements LogoutHandlerInterface 

 public function logout(Request $request, Response $response, TokenInterface $token): void
    
        $user = $token->getUser();
        if (!$user instanceof UserInterface)  /** return if user is somehow anonymous
                * this should not happen here, unless... reasons */
                return;
        

       // else
      $username = $user->getUsername(); // each user class must implement getUsername()
      // get the entity Manager ($this->em, injected in your constructor)
      // get your User repository
      $repository = $this->em->getRepository(MyUser::class);
      $user = $repository->findOneBy(['username' => $username]); // find one by username
      $user->setIsOnline(false);
      $this->em->flush(); // done, you've recorded a logout

    

希望这会有所帮助。运气好的话,它会的。干杯! :-)

【讨论】:

请注意,如果您只是在您的实体中设置一个 DateTimeInterface(例如 $lastLogin),它将无济于事。您确实需要两个布尔值,例如:1/ $locked(管理员请求锁定帐户)和 2/ $online(用户当前在线 - 以便我们知道是否需要根据管理员请求将他/她注销)。祝你好运【参考方案2】:

正确的做法是将用户会话存储在数据库中。

https://symfony.com/doc/current/doctrine/pdo_session_storage.html(这里是数据库表的创建语法,还要在表中添加一个user_id)

在 framework.yml 中添加 Pdo Session Handler。

session:    
    handler_id: Symfony\Component\HttpFoundation\Session\Storage\Handler\PdoSessionHandler
    cookie_secure: auto
    cookie_samesite: lax

在 service.yml 添加监听器并注册会话处理程序

 # Handlers
    Symfony\Component\HttpFoundation\Session\Storage\Handler\PdoSessionHandler:
        arguments:
            - !service  class: PDO, factory: 'database_connection:getWrappedConnection' 
            -  lock_mode: 1 

    # Listeners
    App\Listener\SessionListener:
        tags:
            - name: kernel.event_listener, event: kernel.request, method: onRequestListener

创建一个新的监听器

class SessionListener


    /**
     * @var TokenStorageInterface
     */
    private $tokenStorage;

    /**
     * @var EntityManagerInterface
     */
    private $em;

    /**
     * @var SessionInterface
     */
    private $session;

    public function __construct(
        TokenStorageInterface $tokenStorage,
        EntityManagerInterface $em,
        SessionInterface $session
    ) 
        $this->tokenStorage = $tokenStorage;
        $this->em = $em;     
        $this->session = $session;
       

    public function onRequestListener(GetResponseEvent $event): void
    

        // If its not te master request or token is null
        if (!$event->isMasterRequest() || $this->tokenStorage->getToken() === null) 
            return;
        

        /** @var User $user */
        $user = $this->tokenStorage->getToken()->getUser();

        // Check if user is logged in
        if (!$user instanceof User) 
            return;
        

        $connection = $this->em->getConnection();

        try 
            $stmt = $connection->prepare('UPDATE `sessions` SET `user_id` = :userId WHERE `sess_id` = :sessionId');
            $stmt->execute([
                'userId' => $user->getId(),
                'sessionId' => $this->session->getId(),
            ]);
         catch (DBALException $e) 
        
     
 

现在只需删除该用户的会话。

 /**
     * @var EntityManagerInterface
     */
    private $em;

    public function __construct(EntityManagerInterface $em)
    
        $this->em = $em;
    

    public function delete(User $user): void
    
        $sessions = $this->em->getRepository(Session::class)->findBy([
            'user' => $user,
        ]);

        foreach ($sessions as $session) 
            $this->em->remove($session);
        

        $this->em->flush();
    

【讨论】:

以上是关于Symfony 4:由管理员注销活动用户的主要内容,如果未能解决你的问题,请参考以下文章

如何从管理员端注销所有活动的登录用户

远程和以编程方式注销活动域用户

Symfony2:创建注销链接

Symfony 4.4 Auth0 如何从应用程序中完全注销用户

在 Sonata symfony 4 中创建管理员用户

Symfony 4:如何在防火墙中为用户/管理员提供多个提供者?