fix: Affichage dynamique utilisateur et déconnexion Keycloak

- UserSessionBean: Getters dynamiques pour nom, email, rôle
- Suppression @PostConstruct pour récupération temps réel
- Déconnexion: Redirection directe vers Keycloak logout endpoint
- Topbar: Bouton logout avec p:commandButton ajax=false

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude <noreply@anthropic.com>
This commit is contained in:
dahoud
2025-11-09 00:03:31 +00:00
parent df0243d4f8
commit a5e553cec0
2 changed files with 100 additions and 72 deletions

View File

@@ -24,8 +24,6 @@ import java.io.Serializable;
*/ */
@Named("userSession") @Named("userSession")
@SessionScoped @SessionScoped
@Getter
@Setter
@Slf4j @Slf4j
public class UserSessionBean implements Serializable { public class UserSessionBean implements Serializable {
@@ -38,75 +36,80 @@ public class UserSessionBean implements Serializable {
@IdToken @IdToken
JsonWebToken idToken; JsonWebToken idToken;
private String nomComplet;
private String email;
private String avatarUrl;
private String role;
private int nombreNotificationsNonLues;
private int nombreMessagesNonLus;
/** /**
* Initialise les données de l'utilisateur connecté depuis le token OIDC. * Récupère le nom complet de l'utilisateur depuis le token OIDC.
* Méthode dynamique qui récupère les informations à chaque appel.
*/ */
@PostConstruct public String getNomComplet() {
public void init() {
try { try {
// Récupération depuis le token OIDC/JWT if (securityIdentity != null && securityIdentity.getPrincipal() != null && idToken != null) {
if (securityIdentity != null && securityIdentity.getPrincipal() != null) {
log.info("Initialisation des informations utilisateur depuis OIDC");
// Nom complet (preferred_username ou name) // Nom complet (preferred_username ou name)
nomComplet = idToken.getClaim("name"); String nom = idToken.getClaim("name");
if (nomComplet == null || nomComplet.trim().isEmpty()) { if (nom == null || nom.trim().isEmpty()) {
nomComplet = idToken.getClaim("preferred_username"); nom = idToken.getClaim("preferred_username");
} }
if (nomComplet == null || nomComplet.trim().isEmpty()) { if (nom == null || nom.trim().isEmpty()) {
nomComplet = securityIdentity.getPrincipal().getName(); nom = securityIdentity.getPrincipal().getName();
} }
return nom != null ? nom : "Utilisateur";
// Email
email = idToken.getClaim("email");
// Avatar par défaut
avatarUrl = "/resources/freya-layout/images/avatar-profilemenu.png";
// Rôles (premier rôle trouvé)
if (securityIdentity.getRoles() != null && !securityIdentity.getRoles().isEmpty()) {
role = securityIdentity.getRoles().iterator().next();
// Formatage du rôle pour affichage (enlever préfixes)
role = role.replace("_", " ").replace("-", " ");
role = capitalizeWords(role);
} else {
role = "Utilisateur";
}
log.info("Utilisateur connecté: {} ({})", nomComplet, email);
} else {
log.warn("SecurityIdentity non disponible, utilisation des valeurs par défaut");
setDefaultValues();
} }
// TODO: Récupérer le nombre réel de notifications et messages depuis l'API
nombreNotificationsNonLues = 0;
nombreMessagesNonLus = 0;
} catch (Exception e) { } catch (Exception e) {
log.error("Erreur lors de l'initialisation du UserSession", e); log.error("Erreur lors de la récupération du nom complet", e);
setDefaultValues();
} }
return "Utilisateur";
} }
/** /**
* Valeurs par défaut si OIDC non disponible. * Récupère l'email de l'utilisateur depuis le token OIDC.
*/ */
private void setDefaultValues() { public String getEmail() {
nomComplet = "Utilisateur"; try {
email = "utilisateur@btpxpress.com"; if (securityIdentity != null && securityIdentity.getPrincipal() != null && idToken != null) {
avatarUrl = "/resources/freya-layout/images/avatar-profilemenu.png"; String email = idToken.getClaim("email");
role = "Utilisateur"; return email != null ? email : "utilisateur@btpxpress.com";
nombreNotificationsNonLues = 0; }
nombreMessagesNonLus = 0; } catch (Exception e) {
log.error("Erreur lors de la récupération de l'email", e);
}
return "utilisateur@btpxpress.com";
}
/**
* Retourne l'URL de l'avatar (par défaut).
*/
public String getAvatarUrl() {
return "/resources/freya-layout/images/avatar-profilemenu.png";
}
/**
* Récupère le rôle de l'utilisateur depuis SecurityIdentity.
*/
public String getRole() {
try {
if (securityIdentity != null && securityIdentity.getRoles() != null && !securityIdentity.getRoles().isEmpty()) {
String role = securityIdentity.getRoles().iterator().next();
// Formatage du rôle pour affichage (enlever préfixes)
role = role.replace("_", " ").replace("-", " ");
return capitalizeWords(role);
}
} catch (Exception e) {
log.error("Erreur lors de la récupération du rôle", e);
}
return "Utilisateur";
}
/**
* Nombre de notifications non lues (TODO: implémenter via API).
*/
public int getNombreNotificationsNonLues() {
return 0;
}
/**
* Nombre de messages non lus (TODO: implémenter via API).
*/
public int getNombreMessagesNonLus() {
return 0;
} }
/** /**
@@ -130,17 +133,18 @@ public class UserSessionBean implements Serializable {
/** /**
* Retourne les initiales de l'utilisateur pour l'avatar. * Retourne les initiales de l'utilisateur pour l'avatar.
* *
* @return Les initiales (ex: "JD" pour "Jean Dupont") * @return Les initiales (ex: "JD" pour "Jean Dupont")
*/ */
public String getInitiales() { public String getInitiales() {
String nomComplet = getNomComplet();
if (nomComplet == null || nomComplet.trim().isEmpty()) { if (nomComplet == null || nomComplet.trim().isEmpty()) {
return "U"; return "U";
} }
String[] parts = nomComplet.trim().split("\\s+"); String[] parts = nomComplet.trim().split("\\s+");
if (parts.length >= 2) { if (parts.length >= 2) {
return String.valueOf(parts[0].charAt(0)).toUpperCase() + return String.valueOf(parts[0].charAt(0)).toUpperCase() +
String.valueOf(parts[1].charAt(0)).toUpperCase(); String.valueOf(parts[1].charAt(0)).toUpperCase();
} else if (parts.length == 1) { } else if (parts.length == 1) {
return parts[0].substring(0, Math.min(2, parts[0].length())).toUpperCase(); return parts[0].substring(0, Math.min(2, parts[0].length())).toUpperCase();
@@ -150,21 +154,44 @@ public class UserSessionBean implements Serializable {
/** /**
* Action de déconnexion OIDC/Keycloak. * Action de déconnexion OIDC/Keycloak.
* Redirige vers l'endpoint de logout Keycloak pour détruire la session.
* *
* @return Null pour déclencher une redirection externe * @return Null pour déclencher une redirection externe
*/ */
public String deconnecter() { public String deconnecter() {
try { try {
log.info("Déconnexion de l'utilisateur: {}", nomComplet); log.info("Déconnexion de l'utilisateur: {}", getNomComplet());
// La déconnexion OIDC est gérée automatiquement par Quarkus
// via le endpoint /q/oidc/logout
// Redirection vers le logout endpoint
jakarta.faces.context.FacesContext facesContext = jakarta.faces.context.FacesContext.getCurrentInstance(); jakarta.faces.context.FacesContext facesContext = jakarta.faces.context.FacesContext.getCurrentInstance();
jakarta.faces.context.ExternalContext externalContext = facesContext.getExternalContext(); jakarta.faces.context.ExternalContext externalContext = facesContext.getExternalContext();
// Rediriger vers l'endpoint de logout OIDC de Quarkus // Construction de l'URL de logout Keycloak
String logoutUrl = externalContext.getRequestContextPath() + "/q/oidc/logout"; String keycloakLogoutUrl = "https://security.lions.dev/realms/btpxpress/protocol/openid-connect/logout";
externalContext.redirect(logoutUrl);
// URL de redirection après logout
String baseUrl = externalContext.getRequestScheme() + "://" +
externalContext.getRequestServerName() + ":" +
externalContext.getRequestServerPort() +
externalContext.getRequestContextPath();
String postLogoutRedirectUri = baseUrl + "/";
// Construire l'URL complète avec les paramètres
StringBuilder logoutUrl = new StringBuilder(keycloakLogoutUrl);
logoutUrl.append("?post_logout_redirect_uri=").append(java.net.URLEncoder.encode(postLogoutRedirectUri, "UTF-8"));
// Ajouter le id_token_hint si disponible
if (idToken != null && idToken.getRawToken() != null) {
logoutUrl.append("&id_token_hint=").append(java.net.URLEncoder.encode(idToken.getRawToken(), "UTF-8"));
}
log.info("Redirection vers Keycloak logout: {}", keycloakLogoutUrl);
// Invalider la session HTTP locale
externalContext.invalidateSession();
// Rediriger vers Keycloak logout
externalContext.redirect(logoutUrl.toString());
facesContext.responseComplete(); facesContext.responseComplete();
return null; return null;

View File

@@ -107,10 +107,11 @@
</li> </li>
<li> <li>
<h:form> <h:form>
<h:commandLink action="#{userSession.deconnecter()}" styleClass="logout-link"> <p:commandButton action="#{userSession.deconnecter()}"
<i class="pi pi-sign-out"></i> value="Logout"
<span>Logout</span> styleClass="logout-link p-button-text"
</h:commandLink> ajax="false"
icon="pi pi-sign-out"/>
</h:form> </h:form>
</li> </li>
</ul> </ul>