Redirection apres un login/logout

Aujourd’hui on va parler de Symfony2. Je vais vous présenter la solution pour mettre en place une redirection en fonction du rôle, après une connexion ou déconnexion d’un utilisateur. Nous baserons notre gestion d’utilisateur sur FOSUserBundle.

Nous n’allons pas traiter de installation de ce bundle car la documentation est très bien faite et de plus, un bon nombre de tutos existe sur internet.

Si vous n’utilisez pas le bundle FOSUserBundle, vous pouvez quand même suivre ce HowTo car en vérité il s’agit ici de manipuler les events du coeur de Symfony.

 

Avant de commencer, voici une petite explication de ce que nous souhaitons faire :

  • Nous avons trois rôles: ROLE_USER, ROLE_COMMERCIAL, ROLE_ADMINISTRATEUR
  • Chaque rôle doit renvoyer vers une espace réservé à chacun deux lors de la connexion :
    • ROLE_USER : /membre/
    • ROLE_COMMERCIAL : /crm/
    • ROLE_ADMINISTRATEUR : /backoffice/
  • Lors de la déconnexion, les utilisateurs du groupe ROLE_USER seront redirigés vers la homepage du site, par contre les utilisateurs des groupes ROLE_COMMERCIAL et ROLE_ADMINISTRATEUR seront redirigés vers la page /login

Création des fichiers de redirection

Dans une premier temps nous allons créer deux fichiers qui viendront se placer dans le répertoire Company/UserBundle/Redirection/.

Nous créons le fichier AfterLoginRedirection.php qui sera appelé juste après l’authentification d’un utilisateur mais avant la génération de la réponse:

<?php
/**
 * @copyright  Copyright (c) 2009-2014 Steven TITREN - www.webaki.com
 * @package    Webaki\UserBundle\Redirection
 * @author     Steven Titren <contact@webaki.com>
 */

namespace Webaki\UserBundle\Redirection;

use Symfony\Component\HttpFoundation\RedirectResponse;
use Symfony\Component\HttpFoundation\Request;
use Symfony\Component\Routing\RouterInterface;
use Symfony\Component\Security\Core\Authentication\Token\TokenInterface;
use Symfony\Component\Security\Http\Authentication\AuthenticationSuccessHandlerInterface;

class AfterLoginRedirection implements AuthenticationSuccessHandlerInterface
{
    /**
     * @var \Symfony\Component\Routing\RouterInterface
     */
    private $router;

    /**
     * @param RouterInterface $router
     */
    public function __construct(RouterInterface $router)
    {
        $this->router = $router;
    }

    /**
     * @param Request $request
     * @param TokenInterface $token
     * @return RedirectResponse
     */
    public function onAuthenticationSuccess(Request $request, TokenInterface $token)
    {
        // On récupère la liste des rôles d'un utilisateur
        $roles = $token->getRoles();
        // On transforme le tableau d'instance en tableau simple
        $rolesTab = array_map(function($role){ 
          return $role->getRole(); 
        }, $roles);
        // S'il s'agit d'un admin ou d'un super admin on le redirige vers le backoffice
        if (in_array('ROLE_ADMIN', $rolesTab, true) || in_array('ROLE_SUPER_ADMIN', $rolesTab, true))
            $redirection = new RedirectResponse($this->router->generate('backoffice_homepage'));
        // sinon, s'il s'agit d'un commercial on le redirige vers le CRM
        elseif (in_array('ROLE_COMMERCIAL', $rolesTab, true))
            $redirection = new RedirectResponse($this->router->generate('crm_homepage'));
        // sinon il s'agit d'un membre
        else
            $redirection = new RedirectResponse($this->router->generate('member_homepage'));

        return $redirection;
    }
}

Ce code est relativement simple je ne pense pas devoir vous l’expliquer 😉
Toutefois, faite attention aux lignes de transformations du tableau d’instance en tableau simple:

$rolesTab = array_map(function($role){ 
    return $role->getRole(); 
}, $roles);

Vous rencontrez une erreur si vous utilisez une version de PHP < 5.3.0. Vous allez devoir exporter la closure dans une « vrai » fonction.
Je vous laisse regarder la documentation pour plus d’info.

Nous créons le fichier AfterLogoutRedirection.php qui sera appelé juste après la déconnexion d’un utilisateur mais avant la génération de la réponse:

<?php
/**
 * @copyright  Copyright (c) 2009-2014 Steven TITREN - www.webaki.com
 * @package    Webaki\UserBundle\Redirection
 * @author     Steven Titren <contact@webaki.com>
 */

namespace Webaki\UserBundle\Redirection;

use Symfony\Component\HttpFoundation\RedirectResponse;
use Symfony\Component\HttpFoundation\Request;
use Symfony\Component\Routing\RouterInterface;
use Symfony\Component\Security\Core\SecurityContextInterface;
use Symfony\Component\Security\Http\Logout\LogoutSuccessHandlerInterface;

class AfterLogoutRedirection implements LogoutSuccessHandlerInterface
{
    /**
     * @var \Symfony\Component\Routing\RouterInterface
     */
    private $router;

    /**
     * @var \Symfony\Component\Security\Core\SecurityContextInterface
     */
    private $security;

    /**
     * @param SecurityContextInterface $security
     */
    public function __construct(RouterInterface $router, SecurityContextInterface $security)
    {
        $this->router = $router;
        $this->security = $security;
    }

    /**
     * @param Request $request
     * @return RedirectResponse
     */
    public function onLogoutSuccess(Request $request)
    {
        // On récupère la liste des rôles d'un utilisateur
        $roles = $this->security->getToken()->getRoles();
        // On transforme le tableau d'instance en tableau simple
        $rolesTab = array_map(function($role){ 
            return $role->getRole(); 
        }, $roles);
        // Si c'est un commercial, admin ou un super admin on redirige vers la page de login. Ici nous utilisons la route de FOSUserBundle.
        if (in_array('ROLE_COMMERCIAL', $rolesTab, true) || in_array('ROLE_ADMIN', $rolesTab, true) || in_array('ROLE_SUPER_ADMIN', $rolesTab, true))
            $response = new RedirectResponse($this->router->generate('fos_user_security_login'));
        // Sinon on redirige vers la homepage
        else
            $response = new RedirectResponse($this->router->generate('homepage'));

        return $response;
    }
}

Remarquer ici que nous avons passé au constructeur d’un deuxième paramètre. En effet lors de la déconnexion, le méthode onLogoutSuccess ne nous permet pas de récupérer le token directement comme pour le login.

Mise en place des services

Une fois que nous avons ces deux fichiers, nous allons devoir les transformer en services Symfony pour pouvoir ensuite les utiliser dans la configuration de le firewall de notre application.

Pour cela, nous allons modifier notre fichier services.yml qui ce trouve toujours dans notre bundle:

services:
  # [...] Vos autres services
  redirect.after.login:
    class: Webaki\UserBundle\Redirection\AfterLoginRedirection
    arguments: [@router]

  redirect.after.logout:
    class: Webaki\UserBundle\Redirection\AfterLogoutRedirection
    arguments: [@router, @security.context]

Nous injections le service du @router pour pouvoir générer nos URL dans nos classes de redirections.
Pour le logout, nous ajoutons le service @security.context pour pouvoir récupérer les rôles d’un utilisateur.

Modification du firewall

Maintenant que nos services sont configurés et fonctionnels, nous allons pouvoir modifier le firewall de notre application pour que Symfony utilise les bonnes redirections.

Pour ce faire nous devons modifier notre fichier security.yml pour ajouter deux success_handler comme ceci:

security:
  # [...]
  firewalls:
    # [...]
    main:
      # [...]
      form_login:
        # [...]
        success_handler: redirect.after.login
      logout:
        # [...]
        success_handler: redirect.after.logout

Maintenant lorsqu’un utilisateur ce connecte il sera automatiquement redirigé vers l’espace qui lui est réservé.

Pensez également à modifier access_control pour limiter l’accès des espaces de chacun en fonction des bons rôles sinon cela ne servira à rien 😉

Vous pouvez retrouver le code complet sur mon Gist.



J’espère que cela vous servira.
A bientôt.