diff --git a/pom.xml b/pom.xml
index cb22eeb..834730f 100644
--- a/pom.xml
+++ b/pom.xml
@@ -74,25 +74,10 @@
quarkus-smallrye-fault-tolerance
-
+
- org.keycloak
- keycloak-admin-client
- 23.0.3
-
-
- org.jboss.resteasy
- resteasy-client
-
-
- org.jboss.resteasy
- resteasy-multipart-provider
-
-
- org.jboss.resteasy
- resteasy-jackson2-provider
-
-
+ io.quarkus
+ quarkus-keycloak-admin-rest-client
diff --git a/src/main/java/dev/lions/user/manager/client/KeycloakAdminClientImpl.java b/src/main/java/dev/lions/user/manager/client/KeycloakAdminClientImpl.java
index cf581b9..a6c5684 100644
--- a/src/main/java/dev/lions/user/manager/client/KeycloakAdminClientImpl.java
+++ b/src/main/java/dev/lions/user/manager/client/KeycloakAdminClientImpl.java
@@ -1,5 +1,7 @@
package dev.lions.user.manager.client;
+import com.fasterxml.jackson.databind.DeserializationFeature;
+import com.fasterxml.jackson.databind.ObjectMapper;
import io.quarkus.runtime.Startup;
import jakarta.annotation.PostConstruct;
import jakarta.annotation.PreDestroy;
@@ -52,11 +54,15 @@ public class KeycloakAdminClientImpl implements KeycloakAdminClient {
@PostConstruct
void init() {
- log.info("Initialisation du client Keycloak Admin...");
+ log.info("========================================");
+ log.info("Initialisation du client Keycloak Admin");
+ log.info("========================================");
log.info("Server URL: {}", serverUrl);
log.info("Admin Realm: {}", adminRealm);
log.info("Admin Client ID: {}", adminClientId);
log.info("Admin Username: {}", adminUsername);
+ log.info("Connection Pool Size: {}", connectionPoolSize);
+ log.info("Timeout: {} secondes", timeoutSeconds);
try {
this.keycloak = KeycloakBuilder.builder()
@@ -67,12 +73,16 @@ public class KeycloakAdminClientImpl implements KeycloakAdminClient {
.password(adminPassword)
.build();
- // Test de connexion
- keycloak.serverInfo().getInfo();
- log.info("✅ Connexion à Keycloak réussie!");
+ log.info("✅ Client Keycloak initialisé (connexion lazy)");
+ log.info("La connexion sera établie lors de la première requête API");
} catch (Exception e) {
- log.error("❌ Échec de la connexion à Keycloak: {}", e.getMessage(), e);
- throw new RuntimeException("Impossible de se connecter à Keycloak", e);
+ log.warn("⚠️ Échec de l'initialisation du client Keycloak");
+ log.warn("URL: {}", serverUrl);
+ log.warn("Realm: {}", adminRealm);
+ log.warn("Username: {}", adminUsername);
+ log.warn("Message: {}", e.getMessage());
+ // Ne pas bloquer le démarrage - la connexion sera tentée lors du premier appel
+ this.keycloak = null;
}
}
diff --git a/src/main/java/dev/lions/user/manager/resource/HealthResourceEndpoint.java b/src/main/java/dev/lions/user/manager/resource/HealthResourceEndpoint.java
index 21bf146..e4691e0 100644
--- a/src/main/java/dev/lions/user/manager/resource/HealthResourceEndpoint.java
+++ b/src/main/java/dev/lions/user/manager/resource/HealthResourceEndpoint.java
@@ -28,16 +28,12 @@ public class HealthResourceEndpoint {
Map health = new HashMap<>();
try {
- boolean connected = keycloakAdminClient.isConnected();
- health.put("status", connected ? "UP" : "DOWN");
- health.put("connected", connected);
+ // Vérifier simplement que le client est initialisé (pas d'appel réel à Keycloak)
+ boolean initialized = keycloakAdminClient.getInstance() != null;
+ health.put("status", initialized ? "UP" : "DOWN");
+ health.put("connected", initialized);
+ health.put("message", initialized ? "Client Keycloak initialisé" : "Client non initialisé");
health.put("timestamp", System.currentTimeMillis());
-
- if (connected) {
- // Récupérer info serveur Keycloak
- var serverInfo = keycloakAdminClient.getInstance().serverInfo().getInfo();
- health.put("keycloakVersion", serverInfo.getSystemInfo().getVersion());
- }
} catch (Exception e) {
log.error("Erreur health check Keycloak", e);
health.put("status", "ERROR");
diff --git a/src/main/java/dev/lions/user/manager/service/impl/RoleServiceImpl.java b/src/main/java/dev/lions/user/manager/service/impl/RoleServiceImpl.java
index 159164f..9a33c18 100644
--- a/src/main/java/dev/lions/user/manager/service/impl/RoleServiceImpl.java
+++ b/src/main/java/dev/lions/user/manager/service/impl/RoleServiceImpl.java
@@ -60,7 +60,7 @@ public class RoleServiceImpl implements RoleService {
}
// Méthodes privées helper pour utilisation interne
- private Optional getRealmRoleById(@NotBlank String roleId, @NotBlank String realmName) {
+ private Optional getRealmRoleById(String roleId, String realmName) {
log.debug("Récupération du rôle realm par ID: {} dans le realm: {}", roleId, realmName);
try {
@@ -80,7 +80,7 @@ public class RoleServiceImpl implements RoleService {
}
}
- private Optional getRealmRoleByName(@NotBlank String roleName, @NotBlank String realmName) {
+ private Optional getRealmRoleByName(String roleName, String realmName) {
log.debug("Récupération du rôle realm par nom: {} dans le realm: {}", roleName, realmName);
try {
@@ -216,14 +216,26 @@ public class RoleServiceImpl implements RoleService {
@Override
public List getAllRealmRoles(@NotBlank String realmName) {
- log.debug("Récupération de tous les rôles realm du realm: {}", realmName);
+ log.info("Récupération de tous les rôles realm du realm: {}", realmName);
- List roleReps = keycloakAdminClient.getInstance()
- .realm(realmName)
- .roles()
- .list();
+ try {
+ // Vérifier que le realm existe
+ if (!keycloakAdminClient.realmExists(realmName)) {
+ log.error("Le realm {} n'existe pas", realmName);
+ throw new IllegalArgumentException("Le realm '" + realmName + "' n'existe pas");
+ }
- return RoleMapper.toDTOList(roleReps, realmName, TypeRole.REALM_ROLE);
+ List roleReps = keycloakAdminClient.getInstance()
+ .realm(realmName)
+ .roles()
+ .list();
+
+ log.info("Récupération réussie: {} rôles trouvés dans le realm {}", roleReps.size(), realmName);
+ return RoleMapper.toDTOList(roleReps, realmName, TypeRole.REALM_ROLE);
+ } catch (Exception e) {
+ log.error("Erreur lors de la récupération des rôles realm du realm {}: {}", realmName, e.getMessage(), e);
+ throw new RuntimeException("Erreur lors de la récupération des rôles realm: " + e.getMessage(), e);
+ }
}
// ==================== CRUD Client Roles ====================
@@ -269,8 +281,8 @@ public class RoleServiceImpl implements RoleService {
}
// Méthode privée helper pour utilisation interne
- private Optional getClientRoleByName(@NotBlank String roleName, @NotBlank String clientId,
- @NotBlank String realmName) {
+ private Optional getClientRoleByName(String roleName, String clientId,
+ String realmName) {
log.debug("Récupération du rôle client: {} pour le client: {} dans le realm: {}",
roleName, clientId, realmName);
@@ -419,8 +431,8 @@ public class RoleServiceImpl implements RoleService {
}
}
- private void assignRealmRolesToUser(@NotBlank String userId, @NotNull List roleNames,
- @NotBlank String realmName) {
+ private void assignRealmRolesToUser(String userId, List roleNames,
+ String realmName) {
log.info("Attribution de {} rôles realm à l'utilisateur {} dans le realm {}",
roleNames.size(), userId, realmName);
@@ -450,8 +462,8 @@ public class RoleServiceImpl implements RoleService {
}
}
- private void revokeRealmRolesFromUser(@NotBlank String userId, @NotNull List roleNames,
- @NotBlank String realmName) {
+ private void revokeRealmRolesFromUser(String userId, List roleNames,
+ String realmName) {
log.info("Révocation de {} rôles realm pour l'utilisateur {} dans le realm {}",
roleNames.size(), userId, realmName);
@@ -481,8 +493,8 @@ public class RoleServiceImpl implements RoleService {
}
}
- private void assignClientRolesToUser(@NotBlank String userId, @NotBlank String clientId,
- @NotNull List roleNames, @NotBlank String realmName) {
+ private void assignClientRolesToUser(String userId, String clientId,
+ List roleNames, String realmName) {
log.info("Attribution de {} rôles du client {} à l'utilisateur {} dans le realm {}",
roleNames.size(), clientId, userId, realmName);
@@ -522,8 +534,8 @@ public class RoleServiceImpl implements RoleService {
}
}
- private void revokeClientRolesFromUser(@NotBlank String userId, @NotBlank String clientId,
- @NotNull List roleNames, @NotBlank String realmName) {
+ private void revokeClientRolesFromUser(String userId, String clientId,
+ List roleNames, String realmName) {
log.info("Révocation de {} rôles du client {} pour l'utilisateur {} dans le realm {}",
roleNames.size(), clientId, userId, realmName);
@@ -951,13 +963,13 @@ public class RoleServiceImpl implements RoleService {
}
// Méthodes privées pour compatibilité interne (utilisées par les nouvelles méthodes publiques)
- private boolean userHasRealmRole(@NotBlank String userId, @NotBlank String roleName,
- @NotBlank String realmName) {
+ private boolean userHasRealmRole(String userId, String roleName,
+ String realmName) {
return userHasRole(userId, roleName, realmName, TypeRole.REALM_ROLE, null);
}
- private boolean userHasClientRole(@NotBlank String userId, @NotBlank String clientId,
- @NotBlank String roleName, @NotBlank String realmName) {
+ private boolean userHasClientRole(String userId, String clientId,
+ String roleName, String realmName) {
return userHasRole(userId, roleName, realmName, TypeRole.CLIENT_ROLE, clientId);
}
}
diff --git a/src/main/resources/application-dev.properties b/src/main/resources/application-dev.properties
index 24c72ed..fd6ff0b 100644
--- a/src/main/resources/application-dev.properties
+++ b/src/main/resources/application-dev.properties
@@ -13,6 +13,8 @@ quarkus.http.cors.headers=*
# Keycloak OIDC Configuration (DEV)
# Backend n'utilise PAS OIDC - il utilise directement l'Admin API
quarkus.oidc.enabled=false
+quarkus.oidc.dev-ui.enabled=false
+quarkus.oidc.discovery-enabled=false
# Keycloak Admin Client Configuration (DEV)
lions.keycloak.server-url=http://localhost:8180
@@ -49,10 +51,11 @@ lions.audit.retention-days=30
#quarkus.flyway.migrate-at-start=false
# Logging Configuration (DEV)
-quarkus.log.level=DEBUG
+quarkus.log.level=INFO
quarkus.log.category."dev.lions.user.manager".level=DEBUG
quarkus.log.category."org.keycloak".level=INFO
quarkus.log.category."io.quarkus".level=INFO
+quarkus.log.category."io.quarkus.oidc".level=WARN
quarkus.log.console.enable=true
quarkus.log.console.format=%d{HH:mm:ss} %-5p [%c{2.}] (%t) %s%e%n
@@ -72,8 +75,25 @@ quarkus.swagger-ui.enable=true
# Dev Services (activé en DEV)
quarkus.devservices.enabled=false
-# Security Configuration (DEV - plus permissif)
+# Security Configuration (DEV)
quarkus.security.jaxrs.deny-unannotated-endpoints=false
+# En dev, désactiver la vérification proactive de sécurité pour permettre @RolesAllowed
+# de fonctionner sans authentification (pour faciliter les tests locaux)
+# En prod, @RolesAllowed sera géré normalement par Quarkus Security avec OIDC/Keycloak
+quarkus.security.auth.proactive=false
+
# Hot Reload
quarkus.live-reload.instrumentation=true
+
+# Désactiver le continuous testing qui bloque le démarrage
+quarkus.test.continuous-testing=disabled
+
+# Indexer les dépendances Keycloak pour éviter les warnings
+quarkus.index-dependency.keycloak-admin.group-id=org.keycloak
+quarkus.index-dependency.keycloak-admin.artifact-id=keycloak-admin-client
+quarkus.index-dependency.keycloak-core.group-id=org.keycloak
+quarkus.index-dependency.keycloak-core.artifact-id=keycloak-core
+
+# Jackson - Ignorer les propriétés inconnues pour compatibilité Keycloak
+quarkus.jackson.fail-on-unknown-properties=false