From 7133e765612e68369b954a34808e588e14eb21f2 Mon Sep 17 00:00:00 2001 From: mathis Date: Fri, 27 Feb 2026 14:01:23 +0100 Subject: [PATCH] Implement multi-application redirect handling using redirect_app parameter --- docs/SSO_SLO_Documentation.md | 52 ++++++++++++++++++------- src/Controller/SecurityController.php | 17 +------- src/EventListener/LogoutSubscriber.php | 16 ++++++-- src/EventSubscriber/LoginSubscriber.php | 40 +++++++++++++------ 4 files changed, 79 insertions(+), 46 deletions(-) diff --git a/docs/SSO_SLO_Documentation.md b/docs/SSO_SLO_Documentation.md index 5b93d81..ff52fe1 100644 --- a/docs/SSO_SLO_Documentation.md +++ b/docs/SSO_SLO_Documentation.md @@ -72,7 +72,7 @@ Lorsqu'un utilisateur se déconnecte d'une application, il est **automatiquement 5. EasyPortal → Révocation des tokens OAuth2 └─> Tous les access_token de l'utilisateur sont révoqués - └─> Cookie logout_origin=easycheck créé (5 min) + └─> Paramètre redirect_app=easycheck propagé dans l'URL 6. EasyPortal → Redirection vers /logout └─> GET /logout @@ -81,16 +81,16 @@ Lorsqu'un utilisateur se déconnecte d'une application, il est **automatiquement └─> Session détruite 8. LogoutSubscriber → Redirection vers EasyCheck - └─> GET https://check.../logout?from_portal=1 + └─> GET https://check.../logout?from_portal=1&redirect_app=easycheck 9. EasyCheck → Détecte from_portal=1 └─> Invalide la session (si elle existe encore) - └─> Redirection vers portail login + └─> Redirection vers portail login avec redirect_app 10. EasyPortal → Affichage page login - └─> GET /login + └─> GET /login?redirect_app=easycheck -11. Utilisateur se reconnecte → LoginSubscriber détecte cookie logout_origin +11. Utilisateur se reconnecte → LoginSubscriber détecte redirect_app=easycheck └─> Redirection automatique vers EasyCheck /sso/login ``` @@ -146,13 +146,40 @@ OAUTH_PASSPHRASE='passphrase' OAUTH_ENCRYPTION_KEY='encryption-key' ``` +## Système de redirection multi-applications + +### Paramètre `redirect_app` + +Pour gérer plusieurs applications SSO (EasyCheck, EasyAudit, EasyMaintenance, etc.), le système utilise un **paramètre URL `redirect_app`** au lieu de cookies. + +**Avantages** : +- ✅ Fonctionne avec plusieurs onglets ouverts simultanément +- ✅ Chaque onglet garde son contexte de déconnexion +- ✅ Pas de conflit entre applications +- ✅ Facilement extensible pour de nouvelles applications + +**Fonctionnement** : +1. Lors de la déconnexion depuis une application, elle envoie `redirect_app=nom_app` +2. Ce paramètre est propagé dans toutes les redirections de logout +3. Il arrive sur la page `/login?redirect_app=nom_app` +4. Après reconnexion, l'utilisateur est redirigé vers l'application d'origine + +**Configuration dans LoginSubscriber** : +```php +$appUrls = [ + 'easycheck' => $easycheckUrl . '/sso/login', + 'easyaudit' => $easyauditUrl . '/sso/login', + 'easymaintenance' => $easymaintenanceUrl . '/sso/login', +]; +``` + ## Endpoints ### EasyCheck - API Logout **Route** : `POST /api/logout` -**Description** : Endpoint API pour invalider la session EasyCheck sans redirection. Utilisé par le portail lors du SLO. +**Description** : Endpoint API pour invalider la session EasyCheck sans redirection (non utilisé actuellement, prévu pour usage futur). **Réponse** : ```json @@ -162,12 +189,6 @@ OAUTH_ENCRYPTION_KEY='encryption-key' } ``` -**Comportement** : -- Invalide la session utilisateur -- Ne redirige pas (contrairement à `/logout`) -- Timeout de 2 secondes côté portail -- Si l'appel échoue, le logout du portail continue quand même - ## Points importants ### Sécurité @@ -179,8 +200,9 @@ OAUTH_ENCRYPTION_KEY='encryption-key' ### Architecture -1. **Pas de boucles infinies** : Utilisation de paramètres `from_portal` et `from_easycheck` pour éviter les boucles de redirections +1. **Pas de boucles infinies** : Utilisation du paramètre `from_portal` pour éviter les boucles de redirections 2. **Déconnexion bidirectionnelle** : Chaque application invalide la session de l'autre lors de la déconnexion 3. **Flux prévisible** : Chaque déconnexion suit un chemin clair avec des paramètres explicites -4. **Retour automatique** : Cookie `logout_origin` pour rediriger l'utilisateur vers l'application d'origine après reconnexion -5. **Single Logout complet** : Les deux sessions (portail + EasyCheck) sont toujours invalidées, quelle que soit l'origine de la déconnexion \ No newline at end of file +4. **Retour automatique** : Paramètre URL `redirect_app` pour rediriger l'utilisateur vers l'application d'origine après reconnexion +5. **Single Logout complet** : Toutes les sessions (portail + applications) sont toujours invalidées, quelle que soit l'origine de la déconnexion +6. **Multi-applications** : Support natif de plusieurs applications SSO sans conflit entre onglets \ No newline at end of file diff --git a/src/Controller/SecurityController.php b/src/Controller/SecurityController.php index 784cc74..e987bb8 100644 --- a/src/Controller/SecurityController.php +++ b/src/Controller/SecurityController.php @@ -73,23 +73,8 @@ class SecurityController extends AbstractController $this->logger->log(LogLevel::ERROR, 'Error during SSO logout: ' . $e->getMessage()); } - // Mémoriser l'origine de la déconnexion via cookie (avant la destruction de session) - $response = $this->redirectToRoute('app_logout'); - $fromEasycheck = $request->query->get('from_easycheck'); - if ($fromEasycheck) { - $response->headers->setCookie( - \Symfony\Component\HttpFoundation\Cookie::create('logout_origin') - ->withValue('easycheck') - ->withExpires(new \DateTime('+5 minutes')) - ->withPath('/') - ->withSecure(false) - ->withHttpOnly(true) - ); - $this->logger->info('Logout origin cookie set to EasyCheck'); - } - $this->logger->info('Redirecting to app_logout (will trigger LogoutSubscriber)'); - return $response; + return $this->redirectToRoute('app_logout'); } #[Route(path: '/consent', name: 'app_consent')] diff --git a/src/EventListener/LogoutSubscriber.php b/src/EventListener/LogoutSubscriber.php index 2e0e2c4..c5b7051 100644 --- a/src/EventListener/LogoutSubscriber.php +++ b/src/EventListener/LogoutSubscriber.php @@ -26,14 +26,24 @@ class LogoutSubscriber implements EventSubscriberInterface public function onLogout(LogoutEvent $event): void { + $request = $event->getRequest(); $user = $event->getToken()?->getUserIdentifier(); + // Récupérer le paramètre redirect_app depuis la requête initiale (sso_logout) + $redirectApp = $request->query->get('redirect_app'); + $this->logger->info('LogoutSubscriber triggered - redirecting to EasyCheck for logout', [ - 'user' => $user + 'user' => $user, + 'redirect_app' => $redirectApp ]); - // Redirection vers EasyCheck pour invalider sa session, puis EasyCheck redirigera vers le portail - $easycheckLogoutUrl = $this->easycheckUrl . '/logout?from_portal=1'; + // Construire l'URL de redirection vers EasyCheck avec les paramètres + $params = ['from_portal' => '1']; + if ($redirectApp) { + $params['redirect_app'] = $redirectApp; + } + + $easycheckLogoutUrl = $this->easycheckUrl . '/logout?' . http_build_query($params); $this->logger->info('Redirecting to EasyCheck logout', [ 'url' => $easycheckLogoutUrl, diff --git a/src/EventSubscriber/LoginSubscriber.php b/src/EventSubscriber/LoginSubscriber.php index 5a3ea29..c63f9af 100644 --- a/src/EventSubscriber/LoginSubscriber.php +++ b/src/EventSubscriber/LoginSubscriber.php @@ -73,24 +73,40 @@ class LoginSubscriber implements EventSubscriberInterface } } - // Vérifier si l'utilisateur vient d'une déconnexion depuis EasyCheck + // Vérifier si un paramètre redirect_app est présent dans l'URL $request = $event->getRequest(); - $logoutOrigin = $request->cookies->get('logout_origin'); + $redirectApp = $request->query->get('redirect_app'); - if ($logoutOrigin === 'easycheck') { - $this->logger->info('User logged in after EasyCheck logout - redirecting back to EasyCheck', [ - 'user' => $user?->getUserIdentifier() + if ($redirectApp) { + $this->logger->info('User logged in with redirect_app parameter', [ + 'user' => $user?->getUserIdentifier(), + 'redirect_app' => $redirectApp ]); - // Rediriger vers EasyCheck pour réinitialiser la session SSO - $response = new RedirectResponse($this->easycheckUrl . '/sso/login'); + // Mapper le nom de l'application vers son URL de connexion SSO + $appUrls = [ + 'easycheck' => $this->easycheckUrl . '/sso/login', + // Ajouter d'autres applications ici au fur et à mesure + ]; - // Supprimer le cookie après utilisation - $response->headers->clearCookie('logout_origin', '/'); - - $event->setResponse($response); + if (isset($appUrls[$redirectApp])) { + $redirectUrl = $appUrls[$redirectApp]; + + $this->logger->info('Redirecting to application after login', [ + 'user' => $user?->getUserIdentifier(), + 'app' => $redirectApp, + 'url' => $redirectUrl + ]); + + $event->setResponse(new RedirectResponse($redirectUrl)); + } else { + $this->logger->warning('Unknown redirect_app parameter, using default target path', [ + 'redirect_app' => $redirectApp, + 'user' => $user?->getUserIdentifier() + ]); + } } else { - // Pas de cookie logout_origin : laisser Symfony gérer la redirection par défaut + // Pas de paramètre redirect_app : laisser Symfony gérer la redirection par défaut $this->logger->info('Normal login - using default target path', [ 'user' => $user?->getUserIdentifier() ]);