启用 "remember me" 时在 Symfony 2 应用程序中注销用户

Log user out in Symfony 2 application when "remember me" is enabled

我正在寻找一种方法让用户退出 Symfony 2 应用程序,但找不到正确的方法。

我已经尝试过此处描述的方法: Symfony2: how to log user out manually in controller?

$this->get('security.context')->setToken(null);
$this->get('request')->getSession()->invalidate();

禁用“remember me”时它工作正常,但是,当我启用它时,它不工作。看起来用户被这个 cookie 自动重新验证了。

remember_me:
    key:      "%secret%"
    lifetime: 31536000
    path:     /
    domain:   ~
    always_remember_me: true

让用户退出 Symfony 2 应用程序的正确方法是什么?我是否需要从服务器端另外删除此 cookie?

您可能必须显式调用 session-storage 的 save() (Documentation) 方法。

Force the session to be saved and closed.

此外,您可以要求删除 session- and/or remember_me-cookies通过响应 headers.

session-cookie 的名称配置为 container-parameter framework.session.name 并且默认为您的 session.namephp.ini.

$cookieName = $this->container->getParameter('framework.session.name');
$response->headers->clearCookie( $cookieName );

remember_me-cookie 的名称可以在您的 security 配置中配置。

security:
    firewalls:
        your_firewall:
            remember_me: 
                name: neverforget # <- cookie-name

感谢 @nifr 我能够解决这个问题。这是将用户手动退出 Symfony 2 应用程序的防弹分步指南。

警告

Symfony 已经实现了注销用户和删除cookie 的功能。有一个 LogoutListener 将这些操作委托给几个注销处理程序:CookieClearingLogoutHandlerSessionLogoutHandler。我认为最好的做法是调用这些处理程序,而不是自己实现这种低级逻辑。但是,我找不到办法做到这一点。

解决方案

此解决方案适用于 Symfony 2.6。区别在于 security.token_storage.

  1. 添加两个附加参数以将“会话”和“记住我”的 cookie 名称存储到您的 parameters.yml:
# parameters.yml

parameters:
    session.name: SESS
    session.remember_me.name: LONGSESS
  1. 更新您的 config.yml 以使用会话名称的第一个参数:
# config.yml

framework:
    session:
        name: "%session.name%"
  1. 更新您的 security.yml 以使用第二个参数来记住我的会话名称:
# security.yml

security:
    firewalls:
        demo_secured_area:
            remember_me:
                name: "%session.remember_me.name%"
  1. 这是您可以用来注销当前用户的代码:

如果需要,您可以在内核事件侦听器中使用此类代码。

// SomeController.php

/**
 * @Route("/terminate", name="app.terminate")
 */
public function terminateAction()
{
    // Logging user out.
    $this->get('security.token_storage')->setToken(null);

    // Invalidating the session.
    $session = $this->get('request')->getSession();
    $session->invalidate();

    // Redirecting user to login page in the end.
    $response = $this->redirectToRoute('app.login');

    // Clearing the cookies.
    $cookieNames = [
        $this->container->getParameter('session.name'),
        $this->container->getParameter('session.remember_me.name'),
    ];
    foreach ($cookieNames as $cookieName) {
        $response->headers->clearCookie($cookieName);
    }

    return $response;
}

这是内核事件侦听器的实现,它会根据实体属性强制用户注销:Logging user out of Symfony 2 application using kernel event listener.

希望对您有所帮助。

@Slava Fomin II

Symfony already implements the functionality of logging user out and deleting cookies. There is a LogoutListener who delegates those action to couple of logout handlers: CookieClearingLogoutHandler and SessionLogoutHandler. I think the best course of action would be to call those handlers and not to implement such low-level logic yourself. However, I can't find a way to do this.

为什么不简单地创建一个调用这些服务的服务呢?

我调查了 Symfony\Component\Security\Http\Firewall\LogoutListener 并测试了他在注销期间调用了 2 个服务(Symfony 3.2.9)。

$tokenBasedRememberMeServices 顺便删除记住我的 cookie。

<?php declare(strict_types=1);

namespace MyProject\Security\Listener;

use Symfony\Component\Security\Core\Authentication\Token\Storage\TokenStorage;
use Symfony\Component\Security\Http\Logout\DefaultLogoutSuccessHandler;
use Symfony\Component\HttpFoundation\Request;
use Symfony\Component\Security\Http\Logout\SessionLogoutHandler;
use Symfony\Component\Security\Http\RememberMe\TokenBasedRememberMeServices;

final class LogoutListener
{
    private $sessionLogoutHandler;
    private $tokenBasedRememberMeServices;
    private $defaultLogoutSuccessHandler;
    private $tokenStorage;

    public function __construct(
        SessionLogoutHandler $sessionLogoutHandler,
        TokenBasedRememberMeServices $tokenBasedRememberMeServices,
        DefaultLogoutSuccessHandler $defaultLogoutSuccessHandler,
        TokenStorage $tokenStorage
    )
    {
        $this->sessionLogoutHandler = $sessionLogoutHandler;
        $this->tokenBasedRememberMeServices = $tokenBasedRememberMeServices;
        $this->defaultLogoutSuccessHandler = $defaultLogoutSuccessHandler;
        $this->tokenStorage = $tokenStorage;
    }

    public function logout(Request $request): void
    {
        $token = $this->tokenStorage->getToken();
        $response = $this->defaultLogoutSuccessHandler->onLogoutSuccess($request);
        $this->sessionLogoutHandler->logout($request, $response, $token);
        $this->tokenBasedRememberMeServices->logout($request, $response, $token);
    }

}