feat: Initial lions-user-manager project structure
Phase 1 & 2 Implementation (40% completion) Module server-api (✅ COMPLETED - 15 files): - DTOs complets (User, Role, Audit, Search) - Enums (StatutUser, TypeRole, TypeActionAudit) - Service interfaces (User, Role, Audit, Sync) - ValidationConstants - 100% compilé et testé Module server-impl-quarkus (🔄 EN COURS - 7 files): - KeycloakAdminClient avec Circuit Breaker, Retry, Timeout - UserServiceImpl avec 25+ méthodes - UserResource REST API (12 endpoints) - Health checks Keycloak - Configurations dev/prod séparées - Mappers UserDTO <-> Keycloak UserRepresentation Module client (⏳ À FAIRE - 0 files): - Configuration PrimeFaces Freya à venir - Interface utilisateur JSF à venir Infrastructure: - Maven multi-modules (parent + 3 enfants) - Quarkus 3.15.1 - Keycloak Admin Client 23.0.3 - PrimeFaces 14.0.5 - Documentation complète (README, PROGRESS_REPORT) Contraintes respectées: - ZÉRO accès direct DB Keycloak (Admin API uniquement) - Multi-realm avec délégation - Résilience (Circuit Breaker, Retry) - Sécurité (@RolesAllowed, OIDC) - Observabilité (Health, Metrics) 🤖 Generated with Claude Code Co-Authored-By: Claude <noreply@anthropic.com>
This commit is contained in:
@@ -0,0 +1,63 @@
|
||||
package dev.lions.user.manager.client;
|
||||
|
||||
import org.keycloak.admin.client.Keycloak;
|
||||
import org.keycloak.admin.client.resource.RealmResource;
|
||||
import org.keycloak.admin.client.resource.UsersResource;
|
||||
import org.keycloak.admin.client.resource.RolesResource;
|
||||
|
||||
/**
|
||||
* Interface pour le client Keycloak Admin
|
||||
* Abstraction pour faciliter les tests et la gestion du cycle de vie
|
||||
*/
|
||||
public interface KeycloakAdminClient {
|
||||
|
||||
/**
|
||||
* Récupère l'instance Keycloak
|
||||
* @return instance Keycloak
|
||||
*/
|
||||
Keycloak getInstance();
|
||||
|
||||
/**
|
||||
* Récupère une ressource Realm
|
||||
* @param realmName nom du realm
|
||||
* @return RealmResource
|
||||
*/
|
||||
RealmResource getRealm(String realmName);
|
||||
|
||||
/**
|
||||
* Récupère la ressource Users d'un realm
|
||||
* @param realmName nom du realm
|
||||
* @return UsersResource
|
||||
*/
|
||||
UsersResource getUsers(String realmName);
|
||||
|
||||
/**
|
||||
* Récupère la ressource Roles d'un realm
|
||||
* @param realmName nom du realm
|
||||
* @return RolesResource
|
||||
*/
|
||||
RolesResource getRoles(String realmName);
|
||||
|
||||
/**
|
||||
* Vérifie si la connexion à Keycloak est active
|
||||
* @return true si connecté
|
||||
*/
|
||||
boolean isConnected();
|
||||
|
||||
/**
|
||||
* Vérifie si un realm existe
|
||||
* @param realmName nom du realm
|
||||
* @return true si le realm existe
|
||||
*/
|
||||
boolean realmExists(String realmName);
|
||||
|
||||
/**
|
||||
* Ferme la connexion Keycloak
|
||||
*/
|
||||
void close();
|
||||
|
||||
/**
|
||||
* Force la reconnexion
|
||||
*/
|
||||
void reconnect();
|
||||
}
|
||||
@@ -0,0 +1,163 @@
|
||||
package dev.lions.user.manager.client;
|
||||
|
||||
import io.quarkus.runtime.Startup;
|
||||
import jakarta.annotation.PostConstruct;
|
||||
import jakarta.annotation.PreDestroy;
|
||||
import jakarta.enterprise.context.ApplicationScoped;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import org.eclipse.microprofile.config.inject.ConfigProperty;
|
||||
import org.eclipse.microprofile.faulttolerance.CircuitBreaker;
|
||||
import org.eclipse.microprofile.faulttolerance.Retry;
|
||||
import org.eclipse.microprofile.faulttolerance.Timeout;
|
||||
import org.keycloak.admin.client.Keycloak;
|
||||
import org.keycloak.admin.client.KeycloakBuilder;
|
||||
import org.keycloak.admin.client.resource.RealmResource;
|
||||
import org.keycloak.admin.client.resource.RolesResource;
|
||||
import org.keycloak.admin.client.resource.UsersResource;
|
||||
|
||||
import jakarta.ws.rs.NotFoundException;
|
||||
import java.time.temporal.ChronoUnit;
|
||||
|
||||
/**
|
||||
* Implémentation du client Keycloak Admin
|
||||
* Utilise Circuit Breaker, Retry et Timeout pour la résilience
|
||||
*/
|
||||
@ApplicationScoped
|
||||
@Startup
|
||||
@Slf4j
|
||||
public class KeycloakAdminClientImpl implements KeycloakAdminClient {
|
||||
|
||||
@ConfigProperty(name = "lions.keycloak.server-url")
|
||||
String serverUrl;
|
||||
|
||||
@ConfigProperty(name = "lions.keycloak.admin-realm")
|
||||
String adminRealm;
|
||||
|
||||
@ConfigProperty(name = "lions.keycloak.admin-client-id")
|
||||
String adminClientId;
|
||||
|
||||
@ConfigProperty(name = "lions.keycloak.admin-username")
|
||||
String adminUsername;
|
||||
|
||||
@ConfigProperty(name = "lions.keycloak.admin-password")
|
||||
String adminPassword;
|
||||
|
||||
@ConfigProperty(name = "lions.keycloak.connection-pool-size", defaultValue = "10")
|
||||
Integer connectionPoolSize;
|
||||
|
||||
@ConfigProperty(name = "lions.keycloak.timeout-seconds", defaultValue = "30")
|
||||
Integer timeoutSeconds;
|
||||
|
||||
private Keycloak keycloak;
|
||||
|
||||
@PostConstruct
|
||||
void init() {
|
||||
log.info("Initialisation du client Keycloak Admin...");
|
||||
log.info("Server URL: {}", serverUrl);
|
||||
log.info("Admin Realm: {}", adminRealm);
|
||||
log.info("Admin Client ID: {}", adminClientId);
|
||||
log.info("Admin Username: {}", adminUsername);
|
||||
|
||||
try {
|
||||
this.keycloak = KeycloakBuilder.builder()
|
||||
.serverUrl(serverUrl)
|
||||
.realm(adminRealm)
|
||||
.clientId(adminClientId)
|
||||
.username(adminUsername)
|
||||
.password(adminPassword)
|
||||
.build();
|
||||
|
||||
// Test de connexion
|
||||
keycloak.serverInfo().getInfo();
|
||||
log.info("✅ Connexion à Keycloak réussie!");
|
||||
} catch (Exception e) {
|
||||
log.error("❌ Échec de la connexion à Keycloak: {}", e.getMessage(), e);
|
||||
throw new RuntimeException("Impossible de se connecter à Keycloak", e);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
@Retry(maxRetries = 3, delay = 2, delayUnit = ChronoUnit.SECONDS)
|
||||
@Timeout(value = 30, unit = ChronoUnit.SECONDS)
|
||||
@CircuitBreaker(requestVolumeThreshold = 10, failureRatio = 0.5, delay = 5000)
|
||||
public Keycloak getInstance() {
|
||||
if (keycloak == null) {
|
||||
log.warn("Instance Keycloak null, tentative de réinitialisation...");
|
||||
init();
|
||||
}
|
||||
return keycloak;
|
||||
}
|
||||
|
||||
@Override
|
||||
@Retry(maxRetries = 3, delay = 2, delayUnit = ChronoUnit.SECONDS)
|
||||
@Timeout(value = 30, unit = ChronoUnit.SECONDS)
|
||||
@CircuitBreaker(requestVolumeThreshold = 10, failureRatio = 0.5, delay = 5000)
|
||||
public RealmResource getRealm(String realmName) {
|
||||
try {
|
||||
return getInstance().realm(realmName);
|
||||
} catch (Exception e) {
|
||||
log.error("Erreur lors de la récupération du realm {}: {}", realmName, e.getMessage());
|
||||
throw new RuntimeException("Impossible de récupérer le realm: " + realmName, e);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
@Retry(maxRetries = 3, delay = 2, delayUnit = ChronoUnit.SECONDS)
|
||||
@Timeout(value = 30, unit = ChronoUnit.SECONDS)
|
||||
@CircuitBreaker(requestVolumeThreshold = 10, failureRatio = 0.5, delay = 5000)
|
||||
public UsersResource getUsers(String realmName) {
|
||||
return getRealm(realmName).users();
|
||||
}
|
||||
|
||||
@Override
|
||||
@Retry(maxRetries = 3, delay = 2, delayUnit = ChronoUnit.SECONDS)
|
||||
@Timeout(value = 30, unit = ChronoUnit.SECONDS)
|
||||
@CircuitBreaker(requestVolumeThreshold = 10, failureRatio = 0.5, delay = 5000)
|
||||
public RolesResource getRoles(String realmName) {
|
||||
return getRealm(realmName).roles();
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isConnected() {
|
||||
try {
|
||||
if (keycloak == null) {
|
||||
return false;
|
||||
}
|
||||
keycloak.serverInfo().getInfo();
|
||||
return true;
|
||||
} catch (Exception e) {
|
||||
log.warn("Keycloak non connecté: {}", e.getMessage());
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean realmExists(String realmName) {
|
||||
try {
|
||||
getRealm(realmName).toRepresentation();
|
||||
return true;
|
||||
} catch (NotFoundException e) {
|
||||
return false;
|
||||
} catch (Exception e) {
|
||||
log.error("Erreur lors de la vérification de l'existence du realm {}: {}", realmName, e.getMessage());
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
@PreDestroy
|
||||
@Override
|
||||
public void close() {
|
||||
if (keycloak != null) {
|
||||
log.info("Fermeture de la connexion Keycloak...");
|
||||
keycloak.close();
|
||||
keycloak = null;
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void reconnect() {
|
||||
log.info("Reconnexion à Keycloak...");
|
||||
close();
|
||||
init();
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user