Implement multi-application redirect handling using redirect_app parameter
This commit is contained in:
parent
2d72515f0c
commit
7133e76561
|
|
@ -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
|
||||||
|
|
@ -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')]
|
||||||
|
|
|
||||||
|
|
@ -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,
|
||||||
|
|
|
||||||
|
|
@ -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();
|
$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 {
|
} else {
|
||||||
// Pas de cookie logout_origin : laisser Symfony gérer la redirection par défaut
|
$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', [
|
$this->logger->info('Normal login - using default target path', [
|
||||||
'user' => $user?->getUserIdentifier()
|
'user' => $user?->getUserIdentifier()
|
||||||
]);
|
]);
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue