Log user out in Symfony 2 application when “rememb

2019-01-18 14:29发布

问题:

I'm looking for a way to log user out of Symfony 2 application, but could not find a way to do it properly.

I've tried an approach described here: Symfony2: how to log user out manually in controller?

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

It's working fine when "remember me" is disabled, however, when I enable it, it's not working. It looks like user is automatically re-authenticated back again by this cookie.

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

What is the proper way to log user out of Symfony 2 application? Do I need to additionally delete this cookie from server-side?

回答1:

You may have to call the session-storage's save() (Documentation) method explicitly.

Force the session to be saved and closed.

Further you can request to delete the session- and/or remember_me-cookies via response headers.

The session-cookie's name is configured as the container-parameter framework.session.name and defaults to the session.name value from your php.ini.

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

The remember_me-cookie's name can be configured in your security configuration.

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


回答2:

Thanks to @nifr I was able to resolve this issue. Here's the bulletproof step-by-step guide to log user out of Symfony 2 application manually.

Warning

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.

Solution

This solution is for Symfony 2.6. The difference is in security.token_storage.

  1. Add two additional parameters to store cookie names for «session» and «remember me» to your parameters.yml:
# parameters.yml

parameters:
    session.name: SESS
    session.remember_me.name: LONGSESS
  1. Update your config.yml to use the first parameter for session name:
# config.yml

framework:
    session:
        name: "%session.name%"
  1. Update your security.yml to use the second parameter for remember me session name:
# security.yml

security:
    firewalls:
        demo_secured_area:
            remember_me:
                name: "%session.remember_me.name%"
  1. Here's the code you can use to log current user out:

You can use such code inside of a kernel event listener, if you want so.

// 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;
}

Here's the implementation of kernel event listener which will force users to log out basing on entity property: Logging user out of Symfony 2 application using kernel event listener.

I hope it helps.



回答3:

@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.

Why not simply create a service that calls those?

I looked into Symfony\Component\Security\Http\Firewall\LogoutListener and tested that he calls 2 services during logout (Symfony 3.2.9).

$tokenBasedRememberMeServices by the way deletes the remember-me 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);
    }

}