Je suis développeur web freelance et propose des formations à Symfony2 ! Contactez-moi pour en discuter.

Vous connaissez sans doute FOSUserBundle, un des bundle les plus connus et utilisés de Symfony2. Il permet de gérer l’enregistrement et l’authentification de vos utilisateurs de manière très simple, ce qui permet de se concentrer sur le code métier des applications.

La documentation du bundle est très complète et bien fournie, mais ne couvre pas un détail : lorsqu’un utilisateur se connecte dans votre application (avec son login/mot de passe par exemple), si l’authentification réussit, alors l’utilisateur est redirigé vers la page d’accueil.

Mais cela n’est pas forcément ce que l’on souhaite ! On peut vouloir qu’il soit redirigé ailleurs, par exemple vers sa page de profil. Et quand c’est comme ça, on fait comment ? C’est ce que je vais vous expliquer.

Si ce problème n’est pas indiqué dans la documentation du bundle, c’est car l’authentification est en fait gérée par symfony, et non par le bundle. Cela n’en reste pas moins un problème. Dustin Dobervich a proposé une première solution, il y a un bon moment. Cette solution a depuis été améliorée, mais je vais vous présenter les 2, car elles permettent de mieux comprendre comment fonctionne le framework en interne.

Si vous avez lu ma traduction des articles de Fabien Potencier sur les composants du framework, vous savez que moteur interne interne de Symfony2 utilise beaucoup les évènements. Il est possible « d’écouter » ces évènements, afin de réagir lorsqu’ils arrivent. Pour cela, on crée ce qu’il s’appelle un listener.

En ce qui nous concerne, la solution initiale proposée par Dustin consiste à écouter 2 évènements : security.interactive_login et kernel.response. Le premier évènement est créé après que l’authentification ai réussie. Le second est généré lorsqu’une réponse, n’importe laquelle, est créée.

Intuitivement, il suffit donc de réaliser une redirection lors du second évènement. Mais si nous faisons cela sans conditions, n’importe quelle page va nous rediriger vers la page de profil, car notre évènement va se déclencher à chaque fois.
Pour palier à celà, notre pouvons définir un flag qui indiquera, grâce à l’évènement security.interactive_login, lorsqu’il faudra ou non effectuer une redirection.

Cette solution fonctionne, et je vous invite à regarder le code proposé dans l’article original pour voir son implémentation (la problématique est légèrement différente). L’inconvénient, c’est qu’on écoute l’évènement kernel.response en permanence, ce qui ne sert à rien : exécuter du code inutilement n’est jamais une bonne chose. En effet, la condition « cette réponse fait-elle suite à une connection réussie ? » échoue la plupart du temps, car les utilisateurs ne passent pas leur temps à se connecter. Bref niveau performances c’est pas terrible (même si ça n’est pas forcément critique), et niveau conception c’est pas fou non plus.

Pour pallier à celà, nous n’allons écouter en permanence que l’évènement security.interactive_login, qui n’est pas déclenché très souvent, et uniquement quand nous en avons besoin. Lorsque cela arrive, nous allons demander d’écouter l’évènement kernel.response qui va arriver grâce au dispatcher d’évènements, afin de déclencher la redirection uniquement lorsque nous en avons besoin.

Nous allons créer notre listener dans KeiruaProd\FooBundle\Listener\LoginRedirectionListener.php, et y mettre le code suivant.


router = $router;
$this->dispatcher = $dispatcher;
}

public function onSecurityInteractiveLogin(InteractiveLoginEvent $event)
{
// On demande a écouter une fois l'évènement kernel.response
$this->dispatcher->addListener(KernelEvents::RESPONSE, array($this, 'redirectUserToProfilePage'));
}

public function redirectUserToProfilePage(FilterResponseEvent $event)
{
// on effectue la redirection
$response = new RedirectResponse($this->router->generate('KeiruaProdFooBundle_myprofile'));
$event->setResponse($response);
}
}

Ce code fait tout ce qui a été décrit plus haut : il demande à écouter le message kernel.response lorsque la méthode onSecurityInteractiveLogin est appelée. redirectUserToProfilePage se charge de rediriger l’utilisateur vers sa page de profil. A noter au passage l’utilisation du service de routage et du dispatcher d’évènements, que nous allons devoir injecter.

C’est bien beau tout ça, mais pour le moment, ce code n’est pas executé, car il n’y a aucune raison que la méthode onSecurityInteractiveLogin soit appelée. Il faut maintenant la reférencer, afin que symfony appelle lorsque c’est nécessaire. Il suffit de créer un service grâce à un fichier KeiruaProd\FooBundle\Ressources\config\loginlistener.xml, qui contient ce qui suit :









Ce fichier de configuration crée un service, que nous allons par la suite injecter dans le container de services.

Les tags permettent de préciser que ce service écoute les évènements, de dire lesquels et de préciser quelle méthode appeler. Il y a un paquet de noms de tags, référez vous à la documentation pour en savoir plus. En l’occurence on crée un listener qui, lors de l’évènement security.interactive_login, demande à appeler la méthode « onSecurityInteractiveLogin ». La section « argument » permet de préciser les arguments à passer au constructeur. Ici nous fournissons au constructeur le service de routage, référencé par son identifiant « router », ainsi que le service de dispatcher d’évènements. Au passage, si vous cherchez le nom d’un service, n’oubliez pas la commande app/console container:debug !

Nous avons configuré notre service. Plus qu’à le référencer dans l’application !

Ajoutez, dans la section imports de votre fichier app/config.yml une ligne du genre :

imports:
- { resource: "@KeiruaProdFooBundle/Resources/config/loginlistener.xml" }

Et voila ! Mission accomplie, on est maintenant capable de rediriger l’utilisateur vers sa page de profil quand il se connecte, que ce soit via FOSUserBundle ou par une autre méthode.

5 Réponses à “Redirection après login sous Symfony2”

  1. Greg* Dit:

    Bien expliqué, ça file bien 🙂

    Attention à l’orthographe et la grammaire…

    Petite question : l’événement en question est interactive_login. Le mot qui m’intrigue est _interactive_ : Quid des APIs d’une application ?
    C’est peut-être hors sujet remarque.

  2. Keirua Dit:

    Thanks ! Concernant grammaire/orthographe, j’ai relu pourtant, mais j’ai quand même laissé passer quelques erreurs.

    Concernant ta question ça dépend ce que tu veux dire.

    Quand je dis que l’authentification est faite par SF2 et non par le bundle, c’est pas complètement vrai. Ce qui se passe, c’est qu’un bundle se charge de l’authentification, puis file la main à symfony2 (et dans cet article, on intervient encore après). Là j’ai parlé de FOSUserBundle parce que c’est un des plus utilisé, mais ça aurait pu être une API chargée d’appeler un web service ou autre.

    Une fois que ce service a réalisé l’authentification de l’utilisateur (via ce qu’il connait, que ce soit webservice, base de donnée, fichier texte, fonction aléatoire…), il crée un utilisateur qu’il met dans la session, et file les clés à symfony.

    Là, le composant de sécurité entre en jeu, et à partir des informations de configuration, fait quelques manips sur l’utilisateur dans la session pour gérer les niveaux de droits, et déclenche l’évènement réussi/raté.

    Mais sinon oui, tu peux tout à fait faire de l’authentification non interactive, après, c’est une question de besoin.

  3. Hakkou Dit:

    Ce n’est pas plus simple de passer par le default_target_path ?

    http://symfony.com/doc/master/cookbook/security/form_login.html#redirecting-after-success

  4. Keirua Dit:

    C’est effectivement bien plus simple d’utiliser ce paramètre de configuration !
    J’ai découvert son existence après avoir écrit cet article, je vais faire un autre article pour en parler, ainsi que pour parler de 2-3 autres trucs sur les redirections.

  5. maniT4c Dit:

    En effet le default_target_path est la solution la plus simple. Par contre l’article est très intéressant en soit car il explique bien les mécaniques utilisées par Symfony.

Répondre

Unable to load the Are You a Human PlayThru™. Please contact the site owner to report the problem.