2026-11-07
À la fin de cette leçon, vous serez capable de :
quarkus.security.users) pour un usage local et de test.@RolesAllowed, @PermitAll, @DenyAll).quarkus:dev) sans avoir à provisionner de clés.Comment suivre cette leçon : ce document est le cœur conceptuel. Il s’accompagne d’un TP pratique (
05_java_Security_P_securing-quarkus.qmd) et d’une partie avancée optionnelle (05_java_Security_L_advanced.qmd) pour les approfondissements (OAuth2 / OpenID Connect, clés de production).
Une API REST exposée sur le réseau ne peut pas faire confiance à n’importe quel appelant. Avant de répondre, le serveur doit répondre à deux questions fondamentales, dans cet ordre :
Quarkus encapsule ces deux responsabilités dans un modèle de sécurité unifié : des mécanismes s’occupent de l’authentification, tandis que des permissions et des annotations s’occupent de l’autorisation. Le reste de cette leçon détaille chaque brique, en partant du plus simple (authentification de base) vers le plus courant en microservices (JWT).
Ces deux notions sont souvent confondues, alors qu’elles sont distinctes et complémentaires :
| Notion | Question posée | Moment | Exemples de mécanismes |
|---|---|---|---|
| Authentification | « Qui êtes-vous ? » | Avant l’accès à la ressource | mot de passe (Basic), JWT, OpenID Connect |
| Autorisation | « Avez-vous le droit ? » | Une fois l’identité établie | rôles (@RolesAllowed), permissions par chemin |
Règle de lecture : l’authentification produit une identité ; l’autorisation décide à partir de cette identité. On ne peut pas autoriser un appelant qu’on n’a pas d’abord authentifié.
Quarkus sépare clairement les rôles dans son modèle de sécurité :
SecurityIdentity : l’objet qui représente l’utilisateur authentifié pour une requête — son nom (subject) et l’ensemble de ses rôles. C’est lui que les autorisations consultent.Le flux type d’une requête est le suivant :
Authorization).SecurityIdentity est associée au contexte de la requête.401 Unauthorized (non authentifié) ou 403 Forbidden (authentifié mais non autorisé).Comportement par défaut : lorsque la sécurité est en place, Quarkus protège l’ensemble des endpoints HTTP. Une requête sans identité valide reçoit une réponse
401 Unauthorized. On assouplit ce comportement via des permissions HTTP (par exemple pour laisser un endpoint public).
Les permissions HTTP se décrivent par chemin, avec une politique :
# Exiger un utilisateur authentifié sur /api/*
quarkus.http.auth.permission.api.paths=/api/*
quarkus.http.auth.permission.api.policy=authenticated
# Exiger le rôle "admin" sur /admin/*
quarkus.http.auth.permission.admin.paths=/admin/*
quarkus.http.auth.permission.admin.policy=role:admin
# Rendre un endpoint public (aucune identité requise)
quarkus.http.auth.permission.public.paths=/public/*
quarkus.http.auth.permission.public.policy=permitLes politiques courantes : permit (tout le monde), authenticated (toute identité valide), deny (personne), et role:<nom> (un rôle précis).
L’authentification Basic transmet un identifiant et un mot de passe dans l’en-tête Authorization: Basic, encodés en base64. C’est le mécanisme le plus simple, adapté au développement et aux tests — jamais à la production sans TLS, car le couple identifiant/mot de passe est alors transporté en clair.
Pour l’activer, on configure le mécanisme basic et, pour le test, le realm embarqué de Quarkus :
# Activer le mécanisme Basic
quarkus.http.auth.basic=true
# Désactiver l'authentification proactive : le navigateur
# affiche la boîte de dialogue au premier accès protégé
quarkus.http.auth.proactive=false
# Realm embarqué (usage local / test uniquement)
quarkus.security.users.embedded.enabled=true
quarkus.security.users.embedded.plain-text=true
quarkus.security.users.embedded.users.alice=alice
quarkus.security.users.embedded.users.bob=bob
quarkus.security.users.embedded.roles.alice=admin
quarkus.security.users.embedded.roles.bob=userCet exemple déclare deux utilisateurs : alice (mot de passe alice, rôle admin) et bob (mot de passe bob, rôle user). Le realm embarqué est fourni par l’extension quarkus-elytron-security-properties-file et ne doit jamais être utilisé en production : il existe pour faciliter le développement et les tests.
On peut alors tester directement avec curl :
À retenir :
quarkus.http.auth.basicactive le mécanisme ;quarkus.security.users.embedded.*fournit la source d’identités de test. En production, on remplace le realm embarqué par un vrai annuaire (base de données, LDAP, fournisseur d’identité).
Plutôt que de protéger par chemin, on peut annoter directement les méthodes de ressource avec les annotations Jakarta de sécurité. C’est le moyen le plus lisible d’exprimer une autorisation fine.
Les trois annotations principales :
@RolesAllowed("rôle") : la méthode n’est accessible qu’aux identités possédant au moins un des rôles listés.@PermitAll : la méthode est accessible sans contrainte de rôle (l’authentification reste toutefois appliquée par défaut).@DenyAll : la méthode est inaccessible à tous.@DeclareRoles("rôle") : ne restreint pas l’accès, mais déclare l’ensemble des rôles utilisés par la classe (utile pour la validation et la lisibilité).package org.acme.security;
import jakarta.annotation.security.DenyAll;
import jakarta.annotation.security.PermitAll;
import jakarta.annotation.security.RolesAllowed;
import jakarta.inject.Inject;
import jakarta.ws.rs.GET;
import jakarta.ws.rs.Path;
import jakarta.ws.rs.Produces;
import jakarta.ws.rs.core.MediaType;
import org.eclipse.microprofile.jwt.JsonWebToken;
@Path("/api")
public class WhoAmIResource {
@Inject
JsonWebToken jwt;
@GET
@Path("/whoami")
@Produces(MediaType.TEXT_PLAIN)
public String whoAmI() {
return "connecté en tant que " + jwt.getName();
}
@GET
@Path("/admin")
@RolesAllowed("admin")
@Produces(MediaType.TEXT_PLAIN)
public String admin() {
return "zone admin";
}
@GET
@Path("/public")
@PermitAll
@Produces(MediaType.TEXT_PLAIN)
public String publicZone() {
return "zone publique";
}
@GET
@Path("/secret")
@DenyAll
@Produces(MediaType.TEXT_PLAIN)
public String secret() {
return "inaccessible";
}
}La sécurité par méthode s’applique en complément de l’authentification : l’annotation ne produit pas d’identité, elle filtre l’identité déjà établie. Un appelant non authentifié reçoit 401 ; un appelant authentifié mais sans le rôle exigé reçoit 403.
Chemin vs méthode : les permissions HTTP (
quarkus.http.auth.permission.*) sont commodes pour des règles grossières par préfixe ; la sécurité par méthode est préférable dès que l’autorisation dépend de la logique métier. On peut combiner les deux.
Les JWT (JSON Web Tokens) sont le format dominant pour l’authentification stateless en microservices : un token signé est émis par un fournisseur d’identité, puis présenté à chaque requête. Le serveur ne le stocke pas ; il le vérifie.
Un token est une chaîne de trois parties séparées par des points (.), chacune encodée en base64url :
L’en-tête décodé d’un token signé avec RSA + SHA-256 ressemble à :
La charge décodée porte les claims, dont les plus importants :
Les claims clés :
iss (issuer) : l’émetteur du token — Quarkus peut l’exiger pour relier le token à un fournisseur connu.sub (subject) : l’identifiant unique du sujet.upn (user principal name) : le claim préféré pour nommer le Principal dans l’API de sécurité.groups : les groupes / rôles du porteur — c’est ce que @RolesAllowed consulte.exp (expiration) : la date de péremption — un token expiré est rejeté.Quand un token est présenté (en-tête Authorization: Bearer <token>), Quarkus le valide en plusieurs étapes :
Authorization (mécanisme bearer).exp (non expiré), iss (émetteur attendu, si configuré), et éventuellement l’audience (aud).SecurityIdentity / JsonWebToken à partir des claims : sub devient le subject, upn le nom du principal, groups les rôles.La configuration minimale de validation :
# Clé publique utilisée pour vérifier la signature
mp.jwt.verify.publickey.location=META-INF/resources/publicKey.pem
# Émetteur attendu (le claim "iss" doit correspondre)
mp.jwt.verify.issuer=https://example.com/issuer
# Inclure la clé dans le binaire natif (GraalVM)
quarkus.native.resources.includes=META-INF/resources/publicKey.pemPrivilégiez le standard MicroProfile mp.jwt.verify.publickey.location (bloc ci-dessus) : c’est la voie la plus portable pour indiquer la clé publique de vérification.
Confiance asymétrique : seul l’émetteur détient la clé privée pour signer ; n’importe quel service qui détient la clé publique peut vérifier. C’est ce qui permet à des microservices de vérifier un token sans communiquer avec le fournisseur d’identité.
Une fois le token validé, on injecte l’interface MicroProfile JsonWebToken pour lire les claims dans la ressource :
package org.acme.security;
import jakarta.annotation.security.RolesAllowed;
import jakarta.inject.Inject;
import jakarta.ws.rs.GET;
import jakarta.ws.rs.Path;
import jakarta.ws.rs.Produces;
import jakarta.ws.rs.core.MediaType;
import org.eclipse.microprofile.jwt.JsonWebToken;
@Path("/secure")
public class SecureResource {
@Inject
JsonWebToken jwt;
@GET
@Path("/whoami")
@RolesAllowed("user")
@Produces(MediaType.TEXT_PLAIN)
public String whoAmI() {
return "sujet=" + jwt.getSubject() + " ; groupes=" + jwt.getGroups();
}
}jwt.getSubject() renvoie le claim sub, jwt.getGroups() renvoie le claim groups, et jwt.getClaim("...") permet de lire n’importe quel claim personnalisé.
Tester la sécurité JWT sans provisionner de clés est possible grâce à un confort dédié de Quarkus : en mode développement (et de test), si aucune clé de vérification n’est configurée, l’extension SmallRye JWT génère automatiquement une paire de clés RSA (2048 bits). La clé publique configure la validation ; la clé privée est mise à disposition pour signer des tokens de test.
On peut donc exposer temporairement un endpoint qui émet un token signé :
package org.acme.security;
import io.smallrye.jwt.build.Jwt;
import jakarta.ws.rs.GET;
import jakarta.ws.rs.Path;
@Path("/token")
public class TokenResource {
@GET
public String token() {
return Jwt.issuer("https://example.com/issuer")
.upn("alice")
.groups(java.util.Set.of("users", "admin"))
.sign();
}
}Émetteur par défaut : en mode développement, si
mp.jwt.verify.issuern’est pas défini, SmallRye JWT applique l’émetteur par défauthttps://quarkus.io/issuer. On peut le neutraliser en le positionnant àNONE.
On teste ensuite la boucle complète avec curl :
# 1. Récupérer un token signé (dev mode, clé auto-générée)
TOKEN=$(curl -s http://localhost:8080/token)
# 2. L'utiliser sur une ressource protégée
curl -H "Authorization: Bearer $TOKEN" http://localhost:8080/secure/whoami
# 3. Sans token : 401 Unauthorized
curl -i http://localhost:8080/secure/whoamiÀ retenir : la génération automatique de clés n’est active qu’en dev et test. En production, on fournit toujours une vraie clé publique (et l’émetteur signe avec la clé privée correspondante).
Pour un test automatisé, Quarkus fournit l’annotation @MockJwt (package io.quarkus.test.security.mock.token) : appliquée à une méthode de test @QuarkusTest, elle injecte une identité JWT portant les groupes indiqués, sans avoir à construire ni signer un token. C’est le complément naturel des tests manuels curl du mode dev.
quarkus.security.users.embedded) en production.permit / @PermitAll) les seuls endpoints publics.401 et 403 : non authentifié vs non autorisé — utile pour le débogage et pour le client.exp proche).E. Bruno - Sécurité : authentification et autorisation sous Quarkus