Migration complète vers PrimeFaces Freya - Corrections des incompatibilités et intégration de primefaces-freya-extension
This commit is contained in:
301
SOLUTION_PROPAGATION_TOKEN.md
Normal file
301
SOLUTION_PROPAGATION_TOKEN.md
Normal file
@@ -0,0 +1,301 @@
|
||||
# Solution: Propagation du Token JWT depuis JSF vers Backend
|
||||
|
||||
**Date**: 2025-12-05
|
||||
**Problème**: 401 Unauthorized lors des appels frontend → backend malgré authentification OIDC réussie
|
||||
|
||||
---
|
||||
|
||||
## 🔍 Analyse du Problème
|
||||
|
||||
### Symptômes Observés
|
||||
1. **Frontend** (Port 8080):
|
||||
- ✅ Authentification OIDC réussie avec PKCE
|
||||
- ✅ Token JWT reçu avec tous les rôles dans `realm_access.roles`
|
||||
- ❌ Erreur: `Received: 'Unauthorized, status code 401'` lors des appels API
|
||||
|
||||
2. **Backend** (Port 8081):
|
||||
- ✅ Démarre sans erreur
|
||||
- ❌ Logs: `Bearer access token is not available`
|
||||
- ❌ Rejette les requêtes avec 401 Unauthorized
|
||||
|
||||
### Configuration Initiale (Insuffisante)
|
||||
```properties
|
||||
# application.properties:56
|
||||
quarkus.rest-client."lions-user-manager-api".bearer-token-propagation=true
|
||||
```
|
||||
|
||||
### Pourquoi ça ne fonctionnait pas ?
|
||||
La propriété `bearer-token-propagation=true` ne fonctionne QUE pour:
|
||||
- ✅ Appels **backend → backend** (service-to-service)
|
||||
- ❌ Appels **JSF managed bean → backend** (notre cas)
|
||||
|
||||
**Raison technique**: Les managed beans JSF s'exécutent dans un contexte serveur différent où le token OIDC n'est pas automatiquement injecté dans les appels REST Client.
|
||||
|
||||
---
|
||||
|
||||
## ✅ Solution Implémentée
|
||||
|
||||
### 1. Création de `AuthHeaderFactory`
|
||||
Factory personnalisé qui intercepte TOUS les appels REST Client et ajoute automatiquement le header Authorization avec le token JWT.
|
||||
|
||||
**Fichier**: `lions-user-manager-client-quarkus-primefaces-freya/src/main/java/dev/lions/user/manager/client/filter/AuthHeaderFactory.java`
|
||||
|
||||
```java
|
||||
@ApplicationScoped
|
||||
public class AuthHeaderFactory implements ClientHeadersFactory {
|
||||
|
||||
private static final Logger LOGGER = Logger.getLogger(AuthHeaderFactory.class.getName());
|
||||
|
||||
@Inject
|
||||
JsonWebToken jwt;
|
||||
|
||||
@Override
|
||||
public MultivaluedMap<String, String> update(
|
||||
MultivaluedMap<String, String> incomingHeaders,
|
||||
MultivaluedMap<String, String> clientOutgoingHeaders) {
|
||||
|
||||
MultivaluedMap<String, String> result = new MultivaluedHashMap<>();
|
||||
|
||||
try {
|
||||
// Vérifier si le JWT est disponible et non expiré
|
||||
if (jwt != null && jwt.getRawToken() != null && !jwt.getRawToken().isEmpty()) {
|
||||
String token = jwt.getRawToken();
|
||||
result.add("Authorization", "Bearer " + token);
|
||||
LOGGER.fine("Token Bearer ajouté au header Authorization");
|
||||
} else {
|
||||
LOGGER.warning("Token JWT non disponible ou vide");
|
||||
}
|
||||
} catch (Exception e) {
|
||||
LOGGER.severe("Erreur lors de l'ajout du token Bearer: " + e.getMessage());
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
**Points clés**:
|
||||
- `@ApplicationScoped` - Bean CDI singleton
|
||||
- `@Inject JsonWebToken jwt` - Injecte le token OIDC actuel
|
||||
- `jwt.getRawToken()` - Récupère le token brut (chaîne Base64)
|
||||
- Ajoute `Authorization: Bearer {token}` à chaque requête
|
||||
|
||||
### 2. Enregistrement sur tous les REST Clients
|
||||
Ajout de l'annotation `@RegisterClientHeaders(AuthHeaderFactory.class)` sur chaque interface REST Client.
|
||||
|
||||
#### UserServiceClient
|
||||
```java
|
||||
@Path("/api/users")
|
||||
@RegisterRestClient(configKey = "lions-user-manager-api")
|
||||
@RegisterClientHeaders(AuthHeaderFactory.class) // ← AJOUTÉ
|
||||
@Produces(MediaType.APPLICATION_JSON)
|
||||
@Consumes(MediaType.APPLICATION_JSON)
|
||||
public interface UserServiceClient {
|
||||
// ...
|
||||
}
|
||||
```
|
||||
|
||||
#### Autres REST Clients modifiés
|
||||
- `RoleServiceClient.java:19`
|
||||
- `AuditServiceClient.java:20`
|
||||
- `SyncServiceClient.java:16`
|
||||
|
||||
---
|
||||
|
||||
## 🧪 Test de la Solution
|
||||
|
||||
### 1. Recompiler le Frontend
|
||||
```bash
|
||||
cd lions-user-manager-client-quarkus-primefaces-freya
|
||||
mvn compile
|
||||
```
|
||||
|
||||
**Résultat attendu**: BUILD SUCCESS ✅
|
||||
|
||||
### 2. Redémarrer le Frontend (si nécessaire)
|
||||
Si le frontend ne recharge pas automatiquement les changements:
|
||||
```bash
|
||||
# Arrêter le frontend actuel (Ctrl+C)
|
||||
mvn quarkus:dev
|
||||
```
|
||||
|
||||
### 3. Test Complet
|
||||
1. Accéder à http://localhost:8080
|
||||
2. **Se déconnecter** (important pour obtenir un nouveau token)
|
||||
3. **Se reconnecter** avec `testuser` / `test123`
|
||||
4. Naviguer vers http://localhost:8080/pages/user-manager/users/list.xhtml
|
||||
5. **Vérifier**: La liste des utilisateurs se charge sans erreur 401
|
||||
|
||||
### 4. Vérification des Logs
|
||||
|
||||
#### Frontend - Token propagé
|
||||
```
|
||||
FINE Token Bearer ajouté au header Authorization
|
||||
```
|
||||
|
||||
#### Backend - Token reçu et validé
|
||||
```
|
||||
DEBUG [io.qu.oi.ru.BearerAuthenticationMechanism] Token validation succeeded
|
||||
```
|
||||
|
||||
Si vous voyez encore `Bearer access token is not available` → le token n'est toujours pas propagé (problème de contexte CDI ou hot reload).
|
||||
|
||||
---
|
||||
|
||||
## 📊 Comparaison Avant/Après
|
||||
|
||||
### AVANT (avec bearer-token-propagation uniquement)
|
||||
```
|
||||
Frontend JSF Bean → REST Client → Backend
|
||||
↓
|
||||
❌ Pas de token
|
||||
↓
|
||||
401 Unauthorized
|
||||
```
|
||||
|
||||
### APRÈS (avec AuthHeaderFactory)
|
||||
```
|
||||
Frontend JSF Bean → REST Client → AuthHeaderFactory
|
||||
↓
|
||||
@Inject JsonWebToken
|
||||
↓
|
||||
Authorization: Bearer {token}
|
||||
↓
|
||||
Backend
|
||||
↓
|
||||
✅ Token validé
|
||||
↓
|
||||
200 OK + Données
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 🔧 Architecture Technique
|
||||
|
||||
### Flux d'exécution complet
|
||||
|
||||
1. **Utilisateur s'authentifie** via OIDC (Keycloak)
|
||||
- PKCE flow avec S256
|
||||
- Redirection vers Keycloak → Retour avec code → Échange contre tokens
|
||||
|
||||
2. **Quarkus OIDC reçoit les tokens**
|
||||
- `access_token` (contient `realm_access.roles`)
|
||||
- `id_token` (identité utilisateur)
|
||||
- `refresh_token` (renouvellement)
|
||||
|
||||
3. **Token injecté dans contexte CDI**
|
||||
- `JsonWebToken` bean disponible via `@Inject`
|
||||
- Contient toutes les claims du token
|
||||
|
||||
4. **JSF Bean appelle REST Client**
|
||||
- Ex: `userServiceClient.searchUsers(criteria)`
|
||||
|
||||
5. **AuthHeaderFactory intercepte l'appel**
|
||||
- Méthode `update()` appelée avant l'envoi HTTP
|
||||
- Injecte `Authorization: Bearer {access_token}`
|
||||
|
||||
6. **Backend reçoit la requête**
|
||||
- `BearerAuthenticationMechanism` extrait le token
|
||||
- Valide la signature JWT avec clé publique Keycloak
|
||||
- Extrait les rôles depuis `realm_access.roles`
|
||||
- Autorise l'accès si rôles suffisants
|
||||
|
||||
7. **Backend retourne les données**
|
||||
- HTTP 200 OK + JSON response
|
||||
|
||||
---
|
||||
|
||||
## 🎯 Points Importants
|
||||
|
||||
### Pourquoi JsonWebToken et pas d'autres solutions ?
|
||||
1. ✅ **Native Quarkus** - Fait partie du stack OIDC standard
|
||||
2. ✅ **Thread-safe** - Géré par CDI avec contexte de requête
|
||||
3. ✅ **Type-safe** - Interface fortement typée
|
||||
4. ✅ **Validation automatique** - Token déjà validé par Quarkus OIDC
|
||||
|
||||
### Alternatives (non retenues)
|
||||
- ❌ `SecurityContext` - Ne contient pas le token brut
|
||||
- ❌ `OidcSession` - Trop couplé à la session HTTP
|
||||
- ❌ Header manuel dans chaque méthode - Code dupliqué et fragile
|
||||
- ❌ Filter JAX-RS - Plus complexe, moins naturel avec REST Client
|
||||
|
||||
### Avantages de cette solution
|
||||
1. **Automatique** - Aucun code dans les beans JSF
|
||||
2. **Centralisé** - Une seule classe factory
|
||||
3. **Réutilisable** - Fonctionne pour tous les REST Clients
|
||||
4. **Maintenable** - Facile à déboguer et à tester
|
||||
5. **Performant** - Aucune copie du token, juste une référence
|
||||
|
||||
---
|
||||
|
||||
## 📝 Checklist de Validation
|
||||
|
||||
Après implémentation de cette solution:
|
||||
|
||||
### Frontend
|
||||
- [x] `AuthHeaderFactory.java` créé dans `client/filter/`
|
||||
- [x] Tous les REST Clients annotés avec `@RegisterClientHeaders`
|
||||
- [x] Compilation Maven réussie
|
||||
- [x] Aucune erreur de hot reload
|
||||
|
||||
### Runtime
|
||||
- [ ] Se déconnecter puis reconnecter pour obtenir nouveau token
|
||||
- [ ] Naviguer vers la liste des utilisateurs
|
||||
- [ ] Vérifier logs frontend: "Token Bearer ajouté au header Authorization"
|
||||
- [ ] Vérifier logs backend: "Token validation succeeded"
|
||||
- [ ] Liste des utilisateurs s'affiche sans erreur 401
|
||||
|
||||
### Backend
|
||||
- [ ] Backend accepte les requêtes avec token
|
||||
- [ ] Rôles correctement extraits et appliqués
|
||||
- [ ] Pas de logs "Bearer access token is not available"
|
||||
|
||||
---
|
||||
|
||||
## 🐛 Troubleshooting
|
||||
|
||||
### Problème: Token toujours pas propagé après changements
|
||||
**Cause**: Hot reload Quarkus n'a pas détecté les changements de factory
|
||||
|
||||
**Solution**:
|
||||
```bash
|
||||
# Arrêter le frontend (Ctrl+C)
|
||||
cd lions-user-manager-client-quarkus-primefaces-freya
|
||||
mvn clean compile quarkus:dev
|
||||
```
|
||||
|
||||
### Problème: "Token JWT non disponible ou vide"
|
||||
**Cause**: Contexte CDI ne trouve pas le JsonWebToken
|
||||
|
||||
**Solution**:
|
||||
1. Vérifier que l'utilisateur est authentifié
|
||||
2. Se déconnecter et reconnecter
|
||||
3. Vérifier logs OIDC: token doit être présent
|
||||
|
||||
### Problème: 401 sur certaines pages mais pas d'autres
|
||||
**Cause**: Chemins publics mal configurés
|
||||
|
||||
**Solution**: Vérifier `application.properties`:
|
||||
```properties
|
||||
quarkus.http.auth.permission.public.paths=/,/index.xhtml,...
|
||||
quarkus.http.auth.permission.authenticated.paths=/pages/user-manager/*
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 📚 Références
|
||||
|
||||
### Quarkus Documentation
|
||||
- [Quarkus OIDC Token Propagation](https://quarkus.io/guides/security-openid-connect-client-reference#token-propagation)
|
||||
- [Quarkus REST Client](https://quarkus.io/guides/rest-client)
|
||||
- [ClientHeadersFactory](https://download.eclipse.org/microprofile/microprofile-rest-client-2.0/microprofile-rest-client-spec-2.0.html#_clientheadersfactory)
|
||||
|
||||
### Keycloak
|
||||
- [JWT Token Structure](https://www.keycloak.org/docs/latest/securing_apps/#_token-exchange)
|
||||
- [Realm Roles vs Client Roles](https://www.keycloak.org/docs/latest/server_admin/#realm-roles)
|
||||
|
||||
---
|
||||
|
||||
**Auteur**: Claude Code
|
||||
**Date**: 2025-12-05
|
||||
**Version**: 1.0.0
|
||||
Reference in New Issue
Block a user