2026-11-07
→ Leçon de base :
05_java_Security_L_Introduction.qmd— pose les notions fondamentales de la sécurité applicative : authentification, rôles et protection d’endpoints HTTP. → TP associé :05_java_Security_P_securing-quarkus.qmd— exercice pratique de sécurisation d’une application Quarkus avec un fournisseur d’identité externe.
Cette leçon d’approfondissement reprend le socle de la leçon de base et pousse plus loin les mécanismes de sécurité : OAuth2 / OpenID Connect (OIDC), l’intégration Keycloak (dev et prod), l’anatomie et la vérification des JWT, et l’autorisation avancée (sécurité par méthode et security policies). Elle suppose que vous avez lu la leçon de base et manipulé les primitives @RolesAllowed / quarkus.http.auth.permission.* de l’introduction.
OAuth 2.0 est un protocole de délégation d’accès : un client obtient, au nom d’un utilisateur ou d’une application, un jeton d’accès (access token) pour atteindre une ressource protégée. Il ne définit ni la méthode d’authentification de l’utilisateur, ni le format du jeton. Les rôles clés : la ressource (l’API protégée, resource server), le client (l’application consommatrice) et le serveur d’autorisation (émet les jetons, ex. Keycloak). Les grants usuels : authorization code (flux navigateur), client credentials (application-à-application) et refresh token (renouvellement).
OpenID Connect (OIDC) ajoute à OAuth 2.0 une couche d’identité : il standardise un id_token (un JWT signé) qui décrit l’utilisateur authentifié. Concrètement, OIDC permet à l’application de savoir qui s’est connecté (identité) en plus de savoir ce qu’il est autorisé à faire (accès). C’est la combinaison des deux qui fait OIDC, et c’est ce que Quarkus consomme via l’extension quarkus-oidc.
quarkus-oidcL’extension quarkus-oidc fait de Quarkus un resource server OIDC : elle vérifie les jetons Bearer reçus sur l’en-tête Authorization et les traduit en identité injectable. Le minimum pour la mettre en route :
application-type distingue deux postures : service (resource server, jeton Bearer) et web-app (ajoute la redirection navigateur et la connexion OIDC avec session).
Keycloak est le serveur d’autorisation OIDC de référence. On l’intègre différemment selon l’environnement.
Quarkus embarque des commandes dédiées pour ne pas avoir à installer Keycloak à la main. En mode développement, on démarre un Keycloak local puis on configure l’application pour y pointer :
Le realm par défaut fourni par quarkus keycloak dev est quarkus (client quarkus), ce qui correspond à la configuration quarkus.oidc.auth-server-url / client-id montrée plus haut. En dev, on peut aussi se passer d’un client secret pour un client public (browser).
En production, l’application pointe vers le Keycloak d’entreprise (URL stable, TLS), et le secret du client n’est jamais versionné : il est injecté via la configuration externalisée (vault, variables d’environnement, CI/CD). On utilise typiquement le grant client credentials pour un service backend :
Pour administrer le realm (créer des clients, des rôles, des groupes) depuis l’application, Quarkus propose l’admin client quarkus-keycloak-admin-client, piloté par la famille de propriétés quarkus.keycloak.admin-client.* (auth-server-url, realm, client-id, username, password).
Rappel — voir l’Introduction, section « JWT avec SmallRye » : un jeton est
header.payload.signature, chaque partie encodée en Base64URL. Le payload porte ici des claims standard et des claims propres au fournisseur OIDC :
Lecture des claims principaux :
sub — identifiant unique de l’utilisateur (subject).iss — émetteur du jeton (URL du realm).aud — audience à laquelle le jeton est destiné.exp / iat — expiration et date d’émission (en secondes Unix).azp — client ayant demandé le jeton (authorized party).preferred_username, email, name — claims d’identité OIDC.groups — claim custom Keycloak contenant les groupes/rôles de l’utilisateur.Quand Quarkus reçoit un jeton Bearer, l’extension quarkus-oidc le vérifie avant de l’accepter : elle valide la signature (contre le jeu de clés publiques JWKS de l’émetteur), puis les claims d’identité attendus. Ces attentes se configurent explicitement :
Un jeton dont l’iss ne correspond pas, dont l’aud n’inclut pas l’audience attendue, ou dont l’exp est dépassé, est rejeté (401). C’est ce qui distingue une vérification sérieuse d’un simple « le header est présent ».
Côté application, l’identité vérifiée s’injecte. Le type MicroProfile JWT org.eclipse.microprofile.jwt.JsonWebToken donne accès aux claims ; les annotations jakarta.annotation.security.* appliquent l’autorisation :
import jakarta.annotation.security.RolesAllowed;
import jakarta.inject.Inject;
import jakarta.ws.rs.GET;
import jakarta.ws.rs.Path;
import jakarta.ws.rs.core.Response;
import org.eclipse.microprofile.jwt.JsonWebToken;
@Path("/api/profile")
public class ProfileResource {
@Inject
JsonWebToken jwt;
@GET
@RolesAllowed("user")
public Response me() {
String username = jwt.getClaim("preferred_username");
return Response.ok("Bonjour " + username).build();
}
}Rappel — voir l’Introduction, section « Sécurité par méthode » : les annotations Jakarta
@RolesAllowed,@PermitAll,@DenyAllrestreignent chaque méthode JAX-RS.
Pour qu’un endpoint sans annotation devienne refusé par défaut (et non implicitement public), on active :
Les security policies associent des chemins (expressions régulières) à une politique nommée. C’est le levier déclaratif pour protéger des groupes d’endpoints sans toucher au code :
Les politiques intégrées usuelles : http-basic, oidc, role-group, ainsi que les préfixes role: / roles: (p. ex. roles:admin) pour l’autorisation par rôle. La politique role: fonctionne aussi bien pour les utilisateurs de base (Basic auth) que pour les utilisateurs OIDC — dans le second cas, les rôles sont dérivés du claim du JWT via quarkus.oidc.roles.role-claim-path. Un chemin qui ne correspond à aucune politique reste, par défaut, accessible — d’où l’intérêt de coupler cela avec deny-unannotated-endpoints pour une posture fail-safe.
Pour une règle d’autorisation qui dépasse « un rôle parmi N », on enregistre une politique custom : un bean CDI implémentant io.quarkus.security.identity.SecurityPolicy, référencé par son nom dans la configuration :
import io.quarkus.security.identity.SecurityIdentity;
import io.quarkus.security.identity.SecurityPolicy;
import jakarta.enterprise.context.ApplicationScoped;
import jakarta.inject.Named;
@ApplicationScoped
@Named("admin-only")
public class AdminOnlyPolicy implements SecurityPolicy {
@Override
public boolean check(SecurityIdentity identity) {
return identity.hasRole("admin");
}
}La méthode check reçoit l’SecurityIdentity vérifiée et décide de l’autorisation ; elle peut combiner plusieurs conditions (rôle, claim, origine) que les politiques intégrées ne couvrent pas seules.
Pour relier les parties : OAuth2 délègue l’accès, OIDC y ajoute l’identité (id_token), Keycloak joue le serveur d’autorisation (dev : quarkus keycloak dev, prod : URL stable + secret externalisé), le JWT transporte les claims vérifiés (signature, iss, aud, exp), et l’autorisation combine annotations Jakarta par méthode, security policies par chemin et politiques custom pour les règles composites. La leçon de base (05_java_Security_L_Introduction.qmd) fournit les notions de départ ; le TP (05_java_Security_P_securing-quarkus.qmd) permet de mettre ces mécanismes en pratique.
E. Bruno - Sécurité : Approfondissement