9.5 KiB
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
-
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
-
Backend (Port 8081):
- ✅ Démarre sans erreur
- ❌ Logs:
Bearer access token is not available - ❌ Rejette les requêtes avec 401 Unauthorized
Configuration Initiale (Insuffisante)
# 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
@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 actueljwt.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
@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:19AuditServiceClient.java:20SyncServiceClient.java:16
🧪 Test de la Solution
1. Recompiler le Frontend
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:
# Arrêter le frontend actuel (Ctrl+C)
mvn quarkus:dev
3. Test Complet
- Accéder à http://localhost:8080
- Se déconnecter (important pour obtenir un nouveau token)
- Se reconnecter avec
testuser/test123 - Naviguer vers http://localhost:8080/pages/user-manager/users/list.xhtml
- 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
-
Utilisateur s'authentifie via OIDC (Keycloak)
- PKCE flow avec S256
- Redirection vers Keycloak → Retour avec code → Échange contre tokens
-
Quarkus OIDC reçoit les tokens
access_token(contientrealm_access.roles)id_token(identité utilisateur)refresh_token(renouvellement)
-
Token injecté dans contexte CDI
JsonWebTokenbean disponible via@Inject- Contient toutes les claims du token
-
JSF Bean appelle REST Client
- Ex:
userServiceClient.searchUsers(criteria)
- Ex:
-
AuthHeaderFactory intercepte l'appel
- Méthode
update()appelée avant l'envoi HTTP - Injecte
Authorization: Bearer {access_token}
- Méthode
-
Backend reçoit la requête
BearerAuthenticationMechanismextrait 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
-
Backend retourne les données
- HTTP 200 OK + JSON response
🎯 Points Importants
Pourquoi JsonWebToken et pas d'autres solutions ?
- ✅ Native Quarkus - Fait partie du stack OIDC standard
- ✅ Thread-safe - Géré par CDI avec contexte de requête
- ✅ Type-safe - Interface fortement typée
- ✅ 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
- Automatique - Aucun code dans les beans JSF
- Centralisé - Une seule classe factory
- Réutilisable - Fonctionne pour tous les REST Clients
- Maintenable - Facile à déboguer et à tester
- Performant - Aucune copie du token, juste une référence
📝 Checklist de Validation
Après implémentation de cette solution:
Frontend
AuthHeaderFactory.javacréé dansclient/filter/- Tous les REST Clients annotés avec
@RegisterClientHeaders - Compilation Maven réussie
- 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:
# 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:
- Vérifier que l'utilisateur est authentifié
- Se déconnecter et reconnecter
- 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:
quarkus.http.auth.permission.public.paths=/,/index.xhtml,...
quarkus.http.auth.permission.authenticated.paths=/pages/user-manager/*
📚 Références
Quarkus Documentation
Keycloak
Auteur: Claude Code Date: 2025-12-05 Version: 1.0.0