Points d'extension et coût de maintenance
Connaître les SPI les plus utilisés (authenticator, required action, user storage, event listener, protocol mapper), le rythme des versions de Keycloak et ce que chaque extension coûte à chaque montée de version.
Sur cette page
Keycloak se prolonge par du code Java : de nouveaux facteurs d’authentification, une base d’utilisateurs existante, un format de jeton imposé. Chaque extension résout un besoin, puis coûte à chaque montée de version. Ce cours donne les repères pour arbitrer entre paramétrage et extension, et pour chiffrer la maintenance de ce qui est retenu.
Le mécanisme des SPI
Une SPI (Service Provider Interface) est un point d’extension. L’implémenter demande trois éléments :
- une factory (
…ProviderFactory), instanciée une seule fois, qui lit la configuration et porte l’identifiant du provider ; - un provider, créé par la factory à chaque requête, qui fait le travail ;
- un fichier
META-INF/services/<interface de la factory>qui déclare la factory.
Le tout est livré dans un JAR déposé dans providers/. En mode optimisé, une commande kc.sh build intègre les providers ; le mode dev le fait au démarrage. La console du realm master liste tous les providers chargés (menu utilisateur, Realm info, onglet Provider info).
Le projet Maven importe la nomenclature keycloak-parent de la version visée. Le code est donc lié à une version précise de Keycloak.
Les cinq SPI à connaître
| SPI | À quoi elle sert | Exemple de besoin | Stabilité annoncée |
|---|---|---|---|
| Authenticator | Ajouter une étape à un flux de connexion | Vérifier un code reçu par un canal maison, interroger un service de risque | Interne |
| Required action | Imposer une action à l’utilisateur après connexion | Accepter une charte, compléter un attribut | Interne (fait partie de l’Authentication SPI) |
| User storage | Brancher une source d’utilisateurs externe | Base applicative existante, migration au fil de l’eau | Pas de mention interne |
| Event listener | Réagir aux événements | Pousser vers un SIEM ou un bus | Interne |
| Protocol mapper | Calculer un claim ou un attribut SAML | Claim dérivé d’un référentiel externe | Pas de chapitre SPI dédié ; dépend de classes du serveur |
Pour l’Authentication SPI comme pour l’Event Listener SPI, la documentation indique qu’il s’agit d’une SPI interne : stable jusqu’ici, mais susceptible de changer à n’importe quelle version sans préavis. C’est l’information la plus importante pour chiffrer la maintenance.
À quoi ressemble le code
Un authenticator qui refuse les comptes suspendus :
public class StatutAuthenticator implements Authenticator {
@Override
public void authenticate(AuthenticationFlowContext context) {
UserModel user = context.getUser();
if ("suspendu".equals(user.getFirstAttribute("statut"))) {
context.failure(AuthenticationFlowError.USER_DISABLED);
return;
}
context.success();
}
@Override
public void action(AuthenticationFlowContext context) {
}
@Override
public boolean requiresUser() {
return true;
}
@Override
public boolean configuredFor(KeycloakSession session, RealmModel realm, UserModel user) {
return true;
}
@Override
public void setRequiredActions(KeycloakSession session, RealmModel realm, UserModel user) {
}
@Override
public void close() {
}
}
Il faut y ajouter une AuthenticatorFactory (identifiant, libellé, exigences possibles, configuration), le fichier de services, des tests. Et surtout, ce besoin précis se paramètre sans code : un sous-flux conditionnel avec la condition sur attribut utilisateur, suivi de l’étape de refus d’accès. C’est l’objet du lab.
Un protocol mapper qui produit un claim dérivé d’un attribut :
public class CodeServiceMapper extends AbstractOIDCProtocolMapper
implements OIDCAccessTokenMapper, OIDCIDTokenMapper, UserInfoTokenMapper {
@Override
protected void setClaim(IDToken token, ProtocolMapperModel mappingModel,
UserSessionModel userSession, KeycloakSession session,
ClientSessionContext clientSessionCtx) {
String service = userSession.getUser().getFirstAttribute("service");
token.getOtherClaims().put("code_service",
service == null ? "INCONNU" : service.toUpperCase());
}
}
La classe complète déclare aussi son identifiant, sa catégorie, son texte d’aide et ses propriétés de configuration. Là encore, un mapper d’attribut natif couvre le cas simple ; le code ne se justifie que pour un calcul ou un appel externe.
Paramétrer d’abord
| Besoin | Réponse native à essayer | Extension si… |
|---|---|---|
| MFA selon le rôle ou l’application | Sous-flux conditionnels, niveaux d’authentification (ACR) | Le critère vient d’un service externe |
| Refuser certains comptes | Condition sur attribut ou rôle, refus d’accès | La règle exige un appel externe |
| Claim spécifique | Mappers d’attribut, de rôle, de groupe, valeur codée en dur | Le claim se calcule ou vient d’ailleurs |
| Base d’utilisateurs existante | Fédération LDAP, import avec empreintes PBKDF2 ou Argon2 | Base SQL ou API maison, format d’empreinte non lu nativement |
| Traces vers le SOC | Journaux serveur en JSON | Format ou transport imposé |
| Charte graphique | Thème hérité d’un thème fourni | Pas d’extension Java : c’est un thème |
Le rythme des versions
Les dates de publication de la branche 26 montrent une cadence régulière : 26.0 en octobre 2024, puis une version mineure environ tous les trois mois (26.1 en janvier 2025, 26.2 en avril, 26.3 en juillet, 26.4 fin septembre, 26.5 en janvier 2026, 26.6 en avril, 26.7 en juillet, 26.8 en octobre 2026). Entre deux, des versions correctives sortent toutes les deux à quatre semaines. Les versions mineures de la branche 26 ont apporté des fonctions majeures (organisations, permissions v2, SCIM) : « mineure » ne veut pas dire « sans impact ».
Le projet communautaire publie ses correctifs sur les versions récentes. Un support long sur une version figée relève d’une offre commerciale, ou de ce que l’hébergeur garantit : c’est à vérifier avec lui.
Le Upgrading Guide
Le Upgrading Guide décrit, version par version, ce qui change : Breaking changes, Notable changes, Deprecated features, Removed features. Le guide pose que les versions mineures et correctives n’introduisent de rupture que pour corriger un bug ; il en liste pourtant pour des versions correctives. Il faut donc le lire à chaque montée, correctifs compris.
La procédure générale : sauvegarder la base, arrêter, déployer la nouvelle version, laisser Keycloak migrer le schéma au démarrage, puis recompiler et redéployer extensions et thèmes contre la nouvelle version.
Les risques et comment les réduire
| Risque | Exemple | Parade |
|---|---|---|
| Changement d’une SPI interne | Signature modifiée, classe déplacée | Code minimal, recompilation et tests à chaque version |
| Dépendance à des classes d’implémentation | Usage de classes de keycloak-services | S’en tenir aux interfaces documentées |
| Comportement modifié sans erreur de compilation | Un événement change de nom ou de contenu | Tests d’intégration sur un vrai Keycloak (conteneur) |
| Thème décalé | Nouveau champ absent d’un gabarit réécrit | Hériter plutôt que réécrire, tester chaque page |
| Montée de version bloquée | L’extension retarde l’application d’un correctif de sécurité | Recette des extensions préparée à l’avance sur les versions candidates |
Pour chiffrer l’enveloppe, classez chaque extension : simple (mapper, listener sans état), moyenne (authenticator avec formulaire, required action), complexe (user storage, flux appelant des services externes). Multipliez ensuite l’effort de recette par le nombre de montées de version prévues par an, environ quatre mineures plus des correctifs.
Inventorier les SPI et préférer le paramétrage
30 min
Installer le lab-
Récupérez un jeton d’administration et listez les SPI chargées avec leur indicateur interne :
KC=http://localhost:8080 TOKEN=$(curl -s -d client_id=admin-cli -d username=admin -d password=admin \ -d grant_type=password "$KC/realms/master/protocol/openid-connect/token" | jq -r .access_token) curl -s -H "Authorization: Bearer $TOKEN" "$KC/admin/serverinfo" \ | jq -r '.providers | to_entries[] | "\(.key) internal=\(.value.internal)"' \ | grep -Ei 'authenticator|required-action|storage|eventsListener|protocol-mapper' -
Notez quelles SPI sont marquées internes, puis listez les authenticators disponibles et repérez les conditions (
conditional-…) et le refus d’accès :curl -s -H "Authorization: Bearer $TOKEN" "$KC/admin/serverinfo" \ | jq -r '.providers.authenticator.providers | keys[]' -
Dans le realm
approfondir, ouvrez Realm settings, onglet User profile, et créez un attributstatutmodifiable par les administrateurs seulement. -
Dans Authentication, dupliquez le flux
browsersous le nombrowser-statut. Dans le sous-flux des formulaires, après le formulaire identifiant et mot de passe, ajoutez un sous-flux conditionnel contenant la condition sur attribut utilisateur (attributstatut, valeur attenduesuspendu) puis l’étape de refus d’accès. Le sous-flux est en Conditional, la condition et le refus en Required. Les libellés exacts des étapes varient un peu selon la version. -
Liez
browser-statutcomme flux navigateur (Bind flow). -
Donnez à un utilisateur de test l’attribut
statutàsuspendu, puis connectez-vous avec lui sur http://localhost:8080/realms/approfondir/account : l’accès est refusé. Passez l’attribut àactif: l’accès est accordé. -
Remettez le flux
browserd’origine comme flux navigateur. Rédigez en deux lignes pourquoi l’authenticator Java du cours n’est pas nécessaire ici.
À retenir pour l’architecture
Chaque extension doit passer par une question : le paramétrage natif suffit-il ? Si non, classez-la, notez la SPI utilisée et sa stabilité, et inscrivez sa recette dans le plan de montée de version. Vérifiez avec l’hébergeur ses conditions d’acceptation des extensions et son calendrier de montée de version : ce sont eux qui fixent, en pratique, le rythme de votre maintenance.
Natif, extension ou hébergeur
Natif : fourni par Keycloak, à configurer. Extension : demande du code à maintenir (un SPI, le mécanisme de plugin de Keycloak). Hébergeur : dépend de la plateforme qui héberge Keycloak, à vérifier avec elle.
| Capacité | Verdict | Précision |
|---|---|---|
| Conditions de flux natives (attribut, rôle, facteur configuré) et refus d'accès | Natif | Couvrent beaucoup de besoins d'authentification sans code. À vérifier avant toute extension. |
| Authenticator et required action sur mesure | Extension | Authentication SPI, classée interne par Keycloak : peut changer à chaque version. |
| User Storage SPI (base applicative, migration au fil de l'eau) | Extension | SPI documentée sans mention interne, plus stable, mais le code est le plus volumineux. |
| Protocol mapper sur mesure | Extension | Code court, mais il dépend de classes d'implémentation du serveur. |
| Event listener sur mesure | Extension | Event Listener SPI, classée interne. |
| Déploiement d'extensions et calendrier des montées de version | Hébergeur | Conditions d'acceptation, recette et délai de montée de version sont à convenir avec l'hébergeur. |
Vérifier ce que vous avez retenu
4 questions
-
Un besoin consiste à refuser la connexion aux comptes dont l'attribut statut vaut suspendu. Quelle est la première réponse d'architecte ?
Keycloak fournit une condition sur attribut utilisateur et un refus d'accès. Une extension n'est justifiée que si le paramétrage ne couvre pas le besoin.
-
Deux extensions sont proposées : un user storage provider et un authenticator. Laquelle porte le plus fort risque de rupture lors d'une montée de version de Keycloak, à taille égale ?
La documentation marque l'Authentication SPI comme interne : elle peut changer sans préavis. Le User Storage SPI ne porte pas cette mention.
-
Keycloak publie une version mineure environ chaque trimestre et des correctifs toutes les quelques semaines. Qu'en déduire pour l'enveloppe de maintenance ?
Chaque version peut casser une extension. La maintenance se chiffre en recettes récurrentes, pas en un développement unique.
-
Pourquoi lire les sections Breaking changes du Upgrading Guide même pour une version de correctif ?
Le guide précise que les versions mineures et correctives n'introduisent des ruptures que pour corriger des bugs, mais il en liste effectivement pour des correctifs.