Cette page suppose une application Jakarta REST 4.0 déjà fonctionnelle sur Cassini (ou tout autre runtime JAX-RS 4.0 compatible) avec Vauban comme container CDI. Elle décrit l’ajout de Cervantes : dépendances, configuration MP-Config minimale, annotation @RolesAllowed sur une ressource, premier appel avec et sans bearer token.
Prérequis
-
Java 25 (LTS) — Temurin recommandé.
-
Maven 3.9.16 — épinglé par
.sdkmanrcà la racine de Cervantes (cd cervantes && sdk env). -
Une application JAX-RS 4.0 déjà déployée, avec au moins une classe
Applicationet une ressource@Path. -
Vauban activé comme container CDI 4.1 Lite.
-
Ravel activé comme implémentation MicroProfile Config (Cervantes lit les clés
mp.jwt.*via la SPI standard).
sdk env
./mvnw -ntp install -DskipTests
Dépendances Maven
Pour une application JAX-RS classique, déclarer les deux modules d’intégration (cervantes-cdi-vauban + cervantes-jaxrs) suffit : ils tirent transitivement cervantes-core, cervantes-api et cervantes-mp-jwt-api.
<dependencies>
<dependency>
<groupId>io.vidocq.cervantes</groupId>
<artifactId>cervantes-cdi-vauban</artifactId>
<version>0.1.0-SNAPSHOT</version>
</dependency>
<dependency>
<groupId>io.vidocq.cervantes</groupId>
<artifactId>cervantes-jaxrs</artifactId>
<version>0.1.0-SNAPSHOT</version>
</dependency>
</dependencies>
|
Dans une application packagée par Vidocq Runtime, il suffit d’inclure le wrapper |
Préparer la clé publique de vérification
Cervantes valide la signature du token avec la clé publique de l’émetteur. Deux modes typiques :
-
JWKS — l’émetteur publie son jeu de clés sur
https://issuer.example.com/.well-known/jwks.json; la résolution parkidpermet la rotation transparente. -
PEM — clé publique X.509 inline dans la configuration, ou pointée par fichier. Plus simple pour les tests, moins souple en production.
Exemple : générer une paire RSA pour les tests, et extraire la clé publique en PEM.
openssl genpkey -algorithm RSA -pkeyopt rsa_keygen_bits:2048 -out private.pem
openssl rsa -in private.pem -pubout -out public.pem
Place public.pem dans src/main/resources/META-INF/keys/ de l’application — Cervantes sait charger une classpath: location.
Configuration MicroProfile Config
Placer les clés dans META-INF/microprofile-config.properties :
# Clé publique de vérification — au choix :
# - URL JWKS (production), ou
# - chemin classpath/file (tests).
mp.jwt.verify.publickey.location=classpath:/META-INF/keys/public.pem
# Émetteur attendu (claim "iss") — obligatoire pour la conformité spec
mp.jwt.verify.issuer=https://issuer.example.com
# Audiences acceptées (claim "aud") — séparées par virgule
mp.jwt.verify.audiences=orders-api
# Optionnel : algorithme attendu (défaut RS256)
mp.jwt.verify.publickey.algorithm=RS256
Toutes ces clés sont définies par la spec MP JWT 2.1 §9. Voir Référence pour la liste exhaustive.
Annoter la ressource JAX-RS
Une ressource minimale, protégée par @RolesAllowed et injectant à la fois le principal JsonWebToken complet et un claim isolé.
package io.example.orders;
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.Claim;
import org.eclipse.microprofile.jwt.JsonWebToken;
@Path("/orders")
public class OrderResource {
@Inject
JsonWebToken jwt; // principal validé (anonyme si pas de token)
@Inject
@Claim("email")
String email; // claim isolé, typé String
@GET
@RolesAllowed("manager")
@Produces(MediaType.APPLICATION_JSON)
public String list() {
return """
{ "caller": "%s", "email": "%s", "groups": %s }
""".formatted(jwt.getName(), email, jwt.getGroups());
}
}
Aucun filtre à déclarer manuellement : la BCE Vauban et l’auto-découverte @Provider côté Cassini activent JwtAuthenticationFilter (priorité AUTHENTICATION) et RolesAllowedDynamicFeature au démarrage.
Premier appel : trois cas
# 1. Sans bearer token — la requête est anonyme, @RolesAllowed refuse → 401
curl -i http://localhost:8080/orders
# 2. Avec un bearer token signé par la mauvaise clé → 401
curl -i -H "Authorization: Bearer eyJ.invalid.signature" http://localhost:8080/orders
# 3. Avec un bearer token valide, groupes incluant "manager" → 200
TOKEN=$(./scripts/forge-token.sh manager@example.com manager)
curl -i -H "Authorization: Bearer $TOKEN" http://localhost:8080/orders
Un token forgé pour un utilisateur sans rôle manager renverra 403 Forbidden.
|
Pour un script |
Le pipeline déclenché côté serveur
-
Chappe reçoit la requête HTTP et passe le
ContainerRequestContextà Cassini. -
JwtAuthenticationFilter(@PreMatching, prioritéAUTHENTICATION) litAuthorization: Bearer …, extrait le token brut. -
DefaultJwtValidatordécode le header (Base64url) → choisit la clé viakid→ vérifie la signature viajava.security.Signature→ valide les claims (iss,aud,exp,nbf,iat). -
Le
JsonWebTokenvalidé est posé surJsonWebTokenContext(@RequestScoped) et sur un nouveauJwtSecurityContextinjecté dans la requête JAX-RS viasetSecurityContext. -
RolesAllowedRequestFilterconsulteSecurityContext.isUserInRole("manager")— qui interrogeJsonWebToken.getGroups(). -
La ressource s’exécute.
@Inject JsonWebToken jwtet@Inject @Claim("email") String emailrésolvent vers le token courant via les beans synthétiques de la BCE Vauban.
Trois sources de clé combinables
| Source | Description | Activation |
|---|---|---|
PEM inline |
Clé publique X.509 collée dans la propriété de config. |
|
Location (fichier ou URL) |
Chemin |
|
JWKS multi-clés |
Jeu de clés JSON, résolution par |
Même clé |
Étape suivante
-
Concepts — JWT, claims, signature, JWK Set, rotation.
-
Cas d’usage —
@RolesAllowedavancé,@Claimtypé, JWE, cookies. -
Référence — toutes les clés
mp.jwt.*, tous les algorithmes.