Symfony 4: выход из активного пользователя администратором

Пользователь-администратор в моем приложении Symfony 4.2 должен иметь возможность выйти из системы другого пользователя (не администратора). Я создал систему входа пользователя в зависимости от пакета безопасности Symfony (https://symfony.com/doc/current/security/form_login_setup.html).

Теперь я создаю панель администратора, где все пользователи должны быть перечислены с их онлайн-статусом (последняя активность).

Есть ли рекомендуемый способ перечислить активных пользователей и убить их сеанс, если это необходимо?

Я прочитал несколько таких сообщений: Symfony, как вернуть все вошли в систему Активные пользователи. Но ответы немного старше и касаются только списка активных пользователей.


person igi    schedule 25.07.2019    source источник
comment
Какой обработчик сеанса вы используете?   -  person atymic    schedule 25.07.2019
comment
Хороший вопрос. Я ничего не менял и не устанавливал для обработки сеанса. Так что я бы сказал по умолчанию, если есть по умолчанию. Я установил обработку входа пользователя с помощью команды make:user.   -  person igi    schedule 29.07.2019


Ответы (2)


Правильный способ - сохранить сеанс пользователя в базе данных.

https://symfony.com/doc/current/doctrine/pdo_session_storage.html (здесь приведен синтаксис создания таблицы базы данных. Также добавьте в таблицу user_id)

в framework.yml добавьте обработчик сеанса Pdo.

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();
    }
person EJTJ3    schedule 29.07.2019

Вот хороший способ убить пользовательские сеансы: используйте EventListener с событием onKernelRequest. В вашем основном коде: 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 (с геттером и сеттером).

Затем вы должны создать 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

    }
}

Надеюсь это поможет. Если повезет, так и будет. Ваше здоровье! :-)

person Hugues D    schedule 11.11.2019
comment
Обратите внимание, что если вы просто установите DateTimeInterface в своих объектах (например, $lastLogin), это не поможет. Вам действительно нужны два логических значения, такие как: 1/$locked (учетная запись заблокирована по запросу администратора) и 2/$online (пользователь в настоящее время находится в сети, чтобы мы знали, нужно ли нам выходить из него/ее по запросу администратора). Удачи - person Hugues D; 11.11.2019