简体   繁体   中英

avoid browser back button after symfony logout

I´m working in a symfony project. It almost ready, the authentication process flow fine... but after an user logout he can back to the system by pressing the back button in the browser.

The user cant use the system, if he click in some link o refresh the page the login page appear. But he can see the information that last page of the application.

It is a way to prevent that behaivour with symfony? BR...

security.yaml

security:
    encoders:
        App\Entity\Usuario:
            algorithm: bcrypt
    # https://symfony.com/doc/current/security.html#where-do-users-come-from-user-providers
    providers:
        # used to reload user from session & other features (e.g. switch_user)
        app_user_provider:
            entity:
                class: App\Entity\Usuario
                property: usuario
    firewalls:
        dev:
            pattern: ^/(_(profiler|wdt)|css|images|js)/
            security: false
        main:
            anonymous: lazy
            provider: app_user_provider
            form_login:
                login_path: sac_login
                check_path: sac_login
                csrf_token_generator: security.csrf.token_manager
                default_target_path: sac_login
                always_use_default_target_path: true
            logout:
                path: sac_logout
            guard:
                authenticators:
                    - App\Security\LoginFormAuthenticator
            # activate different ways to authenticate
            # https://symfony.com/doc/current/security.html#firewalls-authentication

            # https://symfony.com/doc/current/security/impersonating_user.html
            # switch_user: true

    # Easy way to control access for large sections of your site
    # Note: Only the *first* access control that matches will be used
    access_control:
        - { path: ^/admin, roles: ROLE_ADMIN }
        - { path: ^/sala, roles: ROLE_SALA }
        - { path: ^/estacionario, roles: ROLE_ESTACIONARIO }
        - { path: ^/registro, roles: ROLE_REGISTRO }
        - { path: ^/direccion, roles: ROLE_DIRECTIVO }
        - { path: ^/transcripcion, roles: ROLE_TRANSCRIPCION }
        - { path: ^/reprografia, roles: ROLE_REPROGRAFIA }

PD1: after the Bossman's answer the the user can logout and click on the browser back button, the log in page stay, but if another user with diferent role make login an error 403 appear because the app redirect to the url that we suposed to avoid.

PD2: the app have no unsecured area, all users need to login to access their zone of work. I accomplish that by saving in the user table the route of that zone an load it in the moment when the symfony authentication system arrive to the onAuthenticationSuccess using the following code:

public function onAuthenticationSuccess(Request $request, TokenInterface $token, $providerKey)
    {
        if ($targetPath = $this->getTargetPath($request->getSession(), $providerKey)) {
            return new RedirectResponse($targetPath);
        }

        $user = $this->entityManager->getRepository(Usuario::class)->findOneBy(['usuario' => $request->request->get('_username')]);
        $ruta = $user->getRuta();

        return new RedirectResponse($this->urlGenerator->generate($ruta));
    }

This is normal behaviour by browsers, but if you really need to control this, then you can achieve this with an event listener and setting the response headers so those pages won't be cached by the browser.

This listener will only set the headers depending on the controller, so you can set it for only admin pages, like App\Controller\AdminController for example...

Place file in src/EventListener/ResponseHeaderListener.php .

// src/EventListener/ResponseHeaderListener.php
namespace App\EventListener;

use Symfony\Component\HttpKernel\Event\ResponseEvent;
use Symfony\Component\HttpKernel\Event\ControllerEvent;
use Symfony\Component\EventDispatcher\EventSubscriberInterface;
use App\Controller\AdminController;

class ResponseHeaderListener implements EventSubscriberInterface
{
    private $controller;

    public static function getSubscribedEvents()
    {
        return array(
            'kernel.controller' => 'onKernelController',
            'kernel.response' => 'onKernelResponse'
        );
    }

    public function onKernelController(ControllerEvent $event)
    {
        $this->controller = $event->getController();
    }

    public function onKernelResponse(ResponseEvent $event)
    {
        if (!$event->isMasterRequest() || !is_array($this->controller)) {
            return;
        }

        if ($this->controller[0] instanceof AdminController) {
            $response = $event->getResponse();
            
            // Set response headers
            $response->headers->add(array(
                'Cache-Control' => 'nocache, no-store, max-age=0, must-revalidate',
                'Pragma' => 'no-cache'
            ));
        }
    }
}

Tweak and modify to your needs.

To accomplish what I want, first I need to create de ResponseHeaderListener class according to Bossman's answer but with an small change because my system has no unsecured area, all the controllers need to pass by the listener:

<?php

namespace App\EventListener;

use Symfony\Component\EventDispatcher\EventSubscriberInterface;
use Symfony\Component\HttpKernel\Event\ControllerEvent;
use Symfony\Component\HttpKernel\Event\ResponseEvent;

class ResponseHeaderListener implements EventSubscriberInterface
{
    private $controller;

    public static function getSubscribedEvents()
    {
        return array(
            'kernel.controller' => 'onKernelController',
            'kernel.response' => 'onKernelResponse'
        );
    }

    public function onKernelController(ControllerEvent $event)
    {
        $this->controller = $event->getController();
    }

    public function onKernelResponse(ResponseEvent $event)
    {
        if (!$event->isMasterRequest() || !is_array($this->controller)) {
            return;
        }

        $response = $event->getResponse();
        $response->headers->add(array(
            'Cache-Control' => 'nocache, no-store, max-age=0, must-revalidate',
            'Pragma' => 'no-cache'
        ));

    }
}

Second, update the security.yaml file with the following keys under the main firewall:

form_login:
    login_path: sac_login
    check_path: sac_login
    always_use_default_target_path: true

where sac_login is the symfony route to the login page.

Third and final step, comment or delete the following code:

    if ($targetPath = $this->getTargetPath($request->getSession(), $providerKey)) {
        return new RedirectResponse($targetPath);
    }

in the onAuthenticationSuccess function that belongs to the loginFormAuthenticator class.

Hope this answer can help others with similiar doubt. Really I read about this problem, not only in symfony, and for days I struggle with a solution. Thanks to @Bossman for all the patience and of course, his help!!!

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM