Merge branch 'refonte-disconnect' into 'develop'

Refonte disconnect

See merge request easy-solutions/apps/easyportal!42
This commit is contained in:
Mathis Buchet 2026-02-27 13:03:17 +00:00
commit 4f7c1eb5de
5 changed files with 84 additions and 46 deletions

View File

@ -58,7 +58,7 @@ security:
check_path: app_login check_path: app_login
enable_csrf: true enable_csrf: true
default_target_path: app_index default_target_path: app_index
use_referer: true always_use_default_target_path: false
logout: logout:
path: app_logout path: app_logout
enable_csrf: false enable_csrf: false

View File

@ -72,7 +72,7 @@ Lorsqu'un utilisateur se déconnecte d'une application, il est **automatiquement
5. EasyPortal → Révocation des tokens OAuth2 5. EasyPortal → Révocation des tokens OAuth2
└─> Tous les access_token de l'utilisateur sont révoqués └─> 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 6. EasyPortal → Redirection vers /logout
└─> GET /logout └─> GET /logout
@ -81,16 +81,16 @@ Lorsqu'un utilisateur se déconnecte d'une application, il est **automatiquement
└─> Session détruite └─> Session détruite
8. LogoutSubscriber → Redirection vers EasyCheck 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 9. EasyCheck → Détecte from_portal=1
└─> Invalide la session (si elle existe encore) └─> Invalide la session (si elle existe encore)
└─> Redirection vers portail login └─> Redirection vers portail login avec redirect_app
10. EasyPortal → Affichage page login 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 └─> Redirection automatique vers EasyCheck /sso/login
``` ```
@ -146,13 +146,40 @@ OAUTH_PASSPHRASE='passphrase'
OAUTH_ENCRYPTION_KEY='encryption-key' 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 ## Endpoints
### EasyCheck - API Logout ### EasyCheck - API Logout
**Route** : `POST /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** : **Réponse** :
```json ```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 ## Points importants
### Sécurité ### Sécurité
@ -179,8 +200,9 @@ OAUTH_ENCRYPTION_KEY='encryption-key'
### Architecture ### 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 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 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 4. **Retour automatique** : Paramètre URL `redirect_app` 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 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

View File

@ -73,23 +73,8 @@ class SecurityController extends AbstractController
$this->logger->log(LogLevel::ERROR, 'Error during SSO logout: ' . $e->getMessage()); $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)'); $this->logger->info('Redirecting to app_logout (will trigger LogoutSubscriber)');
return $response; return $this->redirectToRoute('app_logout');
} }
#[Route(path: '/consent', name: 'app_consent')] #[Route(path: '/consent', name: 'app_consent')]

View File

@ -26,14 +26,24 @@ class LogoutSubscriber implements EventSubscriberInterface
public function onLogout(LogoutEvent $event): void public function onLogout(LogoutEvent $event): void
{ {
$request = $event->getRequest();
$user = $event->getToken()?->getUserIdentifier(); $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', [ $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 // Construire l'URL de redirection vers EasyCheck avec les paramètres
$easycheckLogoutUrl = $this->easycheckUrl . '/logout?from_portal=1'; $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', [ $this->logger->info('Redirecting to EasyCheck logout', [
'url' => $easycheckLogoutUrl, 'url' => $easycheckLogoutUrl,

View File

@ -73,22 +73,43 @@ 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(); $request = $event->getRequest();
$logoutOrigin = $request->cookies->get('logout_origin'); $redirectApp = $request->query->get('redirect_app');
if ($logoutOrigin === 'easycheck') { if ($redirectApp) {
$this->logger->info('User logged in after EasyCheck logout - redirecting back to EasyCheck', [ $this->logger->info('User logged in with redirect_app parameter', [
'user' => $user?->getUserIdentifier() 'user' => $user?->getUserIdentifier(),
'redirect_app' => $redirectApp
]); ]);
// Rediriger vers EasyCheck pour réinitialiser la session SSO // Mapper le nom de l'application vers son URL de connexion SSO
$response = new RedirectResponse($this->easycheckUrl . '/sso/login'); $appUrls = [
'easycheck' => $this->easycheckUrl . '/sso/login',
// Ajouter d'autres applications ici au fur et à mesure
];
// Supprimer le cookie après utilisation if (isset($appUrls[$redirectApp])) {
$response->headers->clearCookie('logout_origin', '/'); $redirectUrl = $appUrls[$redirectApp];
$event->setResponse($response); $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 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()
]);
} }
} }
} }