> ## Content Index
> Fetch the complete content index at: https://www.sfeir.dev/llms.txt
> Use this file to discover other available public pages before exploring further.

# Protéger le livre de comptes et le coffre-fort avec JWT dans Quarkus
- URL: https://www.sfeir.dev/back/proteger-le-livre-de-comptes-et-le-coffre-fort-avec-jwt-dans-quarkus/
- Published: 2026-04-13T08:55:27.000Z
- Updated: 2026-06-25T09:22:53.000Z
- Description: Dans une taverne où chaque accès se mérite, la clé ne suffit plus. Avec JWT, l’identité s’efface au profit d’un jeton signé, vérifié à chaque passage. Une autre manière de penser la sécurité : plus fine, plus exigeante, et résolument moderne dans Quarkus.
- Author: Erwan Le Tutour
- Tags: Back, Java, quarkus, Sécurité, JWT

Les torches crépitent dans la salle du trésor. Le cellier est déjà sous clé, mais le **livre de comptes** et le **coffre-fort** exigent mieux qu'une [simple serrure](https://www.sfeir.dev/back/securiser-le-cellier-dune-taverne-avec-basic-auth-dans-quarkus/).  
Ici, une clé ne suffit plus. Ce qu’il faut, c’est une preuve.

Cette preuve prend la forme d’un **jeton scellé** : un [**JWT**](https://www.sfeir.dev/back/securisez-vos-api-avec-spring-security-jwt/).  
Un artefact que l’on ne forge qu’une fois, que l’on présente à chaque passage, et dont l’authenticité peut être vérifiée sans jamais révéler de secret.

Dans cet article, nous mettons en place cette mécanique dans [Quarkus ](https://www.sfeir.dev/quarkus-definition/)pour protéger deux accès sensibles :

- le livre de comptes (rôle `accountant`)
- le coffre-fort (rôle `treasurer`)

Pour comprendre pleinement ce mécanisme, nous nous appuierons sur deux approches issues du code :

- une première où les jetons sont fournis afin d’en observer la vérification
- une seconde, où ils sont forgés à la demande via un login et une base JPA

Deux manières d’aborder un même principe, entre observation et mise en pratique, afin de mieux saisir comment le trésor reste fermé… même lorsque la porte semble ouverte.

## Rappel rapide sur JWT

JWT (JSON Web Token) est un mécanisme d'authentification basé sur un **jeton signé** :

- Le client envoie un header `Authorization: Bearer <token>`.
- Le serveur ne fait qu’une chose : vérifier le jeton (signature, expiration, issuer).  
Il ne connaît jamais le mot de passe et ne génère aucun token dans ce module.
- Le token contient des **claims** (ex: `issuer`, `exp`, `groups`).
- Les rôles sont lus dans le token et appliqués via `@RolesAllowed`.

Point clé à retenir : **le serveur ne reçoit plus de mot de passe après le login**.  
Le mot de passe sert à obtenir un JWT, ensuite seul le token circule.

## JWT avec jetons de démonstration

Premier niveau de défense : ici, on ne forge pas encore les jetons.  
On les reçoit déjà scellés, prêts à l’usage.

C’est une approche simple, presque didactique : elle permet de tester rapidement les rôles et les accès sans se soucier du processus de création.  
Les **jetons sont fournis**, signés à l’avance avec une clé privée de démonstration.

Le garde, lui, ne fait qu’une chose : vérifier le sceau.

### Les dépendances

Pour mettre en place cette mécanique dans Quarkus, le module s’appuie sur quelques briques essentielles :

```xml
<dependencies>
    <dependency>
        <groupId>io.quarkus</groupId>
        <artifactId>quarkus-rest-jackson</artifactId>
    </dependency>
    <dependency>
        <groupId>io.quarkus</groupId>
        <artifactId>quarkus-smallrye-jwt</artifactId>
    </dependency>
    <dependency>
        <groupId>io.quarkus</groupId>
        <artifactId>quarkus-security</artifactId>
    </dependency>
</dependencies>
```

- `quarkus-smallrye-jwt` permet de **vérifier les jetons**
- `quarkus-security` relie les rôles aux accès via `@RolesAllowed`

### La configuration JWT

Ici, tout repose sur la capacité à reconnaître un jeton valide :

```properties
# Verification JWT (publique)
mp.jwt.verify.publickey.location=publicKey.pem
mp.jwt.verify.issuer=tavern-issuer
```

Concrètement :

- `mp.jwt.verify.publickey.location` désigne la **clé publique** utilisée pour vérifier la signature
- `mp.jwt.verify.issuer` impose l’**origine attendue** du jeton

La clé publique est accessible au garde.  
La clé privée, elle, reste entre les mains de celui qui a forgé le sceau.

### L’API protégée

Le livre de comptes et le coffre-fort sont gardés par une seule porte, mais chacun répond à une autorité différente :

Chaque accès est contrôlé par un rôle :

- `accountant` pour le livre de comptes
- `treasurer` pour le coffre-fort

Deux éléments assurent la garde :

- `@SecurityScheme` expose le mécanisme **Bearer JWT** dans la documentation
- `@RolesAllowed` décide qui peut passer… et qui reste dehors

### Le trésor derrière la porte

Derrière ces accès protégés, on retrouve des données de démonstration - juste assez pour comprendre ce qui est en jeu :

```java
@ApplicationScoped
public class TreasuryService {

    public LedgerResponse ledger() {
        return new LedgerResponse("grand-ledger", List.of(
                "Ale festival - 12 silver",
                "Mead barrels - 7 silver",
                "Royal tax - 3 silver"
        ));
    }

    public VaultResponse vault() {
        return new VaultResponse("iron-vault", "sealed", List.of(
                "Emerald chalice",
                "Guild charter",
                "Emergency gold stash"
        ));
    }
}
```

Le contenu importe peu ici.  
Ce qui compte, c’est que **l’accès dépend désormais du jeton présenté**.

### Jetons de démonstration

Pour mettre à l’épreuve le système, deux jetons sont fournis.  
Valides jusqu’au `2030-01-01`, chacun porte un rôle précis :

- `LEDGER_TOKEN` \- permet d’accéder au livre de comptes (`accountant`)

```text
eyJhbGciOiJSUzI1NiIsInR5cCI6IkpXVCIsImtpZCI6InRhdmVybi1rZXkifQ.eyJpc3MiOiJ0YXZlcm4taXNzdWVyIiwiaWF0IjoxNzc1MDg4MDAwLCJleHAiOjE4OTM0NTYwMDAsInN1YiI6ImVsb3JhIiwicHJlZmVycmVkX3VzZXJuYW1lIjoiZWxvcmEiLCJncm91cHMiOlsiYWNjb3VudGFudCJdfQ.JwKFZD-c0fKaoGPpElS-BZ2IhKLOH6A8Tx3dBt1QhdECOClf2WtAtpi7epQRxQaPnxRL0RZ7Aq-tAd_bWFZ-uuq5l1IBKdcCOMSEOkoKRmHS_tGlKYq2ci6-RXjCOJpkXLr6JJ0tqsyR_G8qiF40TJeIsAnARKSHT8o4CS0ACblPahnyQ8ms35SGBwOkkgKgV0JmcQqdnohKXYb2UnT-EpJ7S1Fq5RKv_aX_q7WKDq0M9OvLesC21Irb3tvUdOivgLz0IwG2Re43xnyT5vCoJm0sXLC8czuyfteY71c1SldTEtkwWvvcVo1nPabaztewnWEbCYDUPQYDDIYR5zGm4w
```

- `VAULT_TOKEN` \- ouvre le coffre (`treasurer`)

```text
eyJhbGciOiJSUzI1NiIsInR5cCI6IkpXVCIsImtpZCI6InRhdmVybi1rZXkifQ.eyJpc3MiOiJ0YXZlcm4taXNzdWVyIiwiaWF0IjoxNzc1MDg4MDAwLCJleHAiOjE4OTM0NTYwMDAsInN1YiI6ImJvcmluIiwicHJlZmVycmVkX3VzZXJuYW1lIjoiYm9yaW4iLCJncm91cHMiOlsidHJlYXN1cmVyIl19.GUZJ3bsjD21LXlPBzrs0kxfJzkp822UfgEjN-MnTcrZnf4GpSW1HJrWNn153aeeZobRss2EGc_m3kCEmn0xA2uEwb7NsGytPv-tz2EFf4sDEJWo3SPu88L8yzTdLhyzMouZXkdMnoQ766COcXkhufuRa06WqCGfG6RyNs5W9IoIitS9usTmRcWli9uM9gxiRdS3Cpp3z_RrxtX3ZY5px6zjwGXDNCo_gbXH6GzEitoOmE5-5jF8nfpQvepaO4yxXwjW3M149eFwUygGtsM4BH7cWuDmnG1G7qE-eD0qCl-g824CgMzTik6xab6uS47RsZDWYIitwBl1mNCyNHGZbsg
```

Ces jetons sont déjà scellés.  
Ils permettent d’observer le mécanisme en place, sans intervenir dans la forge.

## JWT + JPA avec login

Deuxième niveau de défense : cette fois, les jetons ne sont plus fournis.  
Ils sont **forgés à la demande**.

Pour accéder au livre de comptes ou au coffre-fort, il ne suffit plus de présenter un sceau valide :  
il faut d’abord prouver son identité.

Les utilisateurs sont enregistrés en base, leurs secrets soigneusement conservés, et chaque passage par le portail d’authentification permet de générer un **JWT signé**, valable pour une durée limitée.

Ici, la taverne ne se contente plus de vérifier les sceaux.  
Elle les crée.

### Les dépendances

Pour mettre en place cette forge dans Quarkus, le module s’appuie sur plusieurs briques :

```xml
<dependencies>
    <dependency>
        <groupId>io.quarkus</groupId>
        <artifactId>quarkus-rest-jackson</artifactId>
    </dependency>
    <dependency>
        <groupId>io.quarkus</groupId>
        <artifactId>quarkus-smallrye-jwt</artifactId>
    </dependency>
    <dependency>
        <groupId>io.quarkus</groupId>
        <artifactId>quarkus-smallrye-jwt-build</artifactId>
    </dependency>
    <dependency>
        <groupId>io.quarkus</groupId>
        <artifactId>quarkus-security</artifactId>
    </dependency>
    <dependency>
        <groupId>io.quarkus</groupId>
        <artifactId>quarkus-elytron-security-common</artifactId>
    </dependency>
    <dependency>
        <groupId>io.quarkus</groupId>
        <artifactId>quarkus-hibernate-orm-panache</artifactId>
    </dependency>
    <dependency>
        <groupId>io.quarkus</groupId>
        <artifactId>quarkus-jdbc-h2</artifactId>
    </dependency>
</dependencies>
```

- `quarkus-smallrye-jwt` : vérification des jetons
- `quarkus-smallrye-jwt-build` : **génération et signature**
- JPA + H2 : stockage des utilisateurs
- `elytron` : gestion des mécanismes de sécurité (dont le hash des mots de passe)

### La configuration

Ici, deux mondes coexistent :

- celui de la **mémoire** (les utilisateurs)
- celui du **sceau** (les jetons)

```properties
# Datasource H2 en memoire
quarkus.datasource.db-kind=h2
quarkus.datasource.jdbc.url=jdbc:h2:mem:tavern-jwt-jpa;DB_CLOSE_DELAY=-1
quarkus.datasource.username=sa
quarkus.datasource.password=sa

quarkus.hibernate-orm.database.generation=drop-and-create

# JWT verification + signature
mp.jwt.verify.publickey.location=publicKey.pem
mp.jwt.verify.issuer=tavern-issuer
mp.jwt.signer.key.location=privateKey.pem
smallrye.jwt.sign.key.location=privateKey.pem
```

- la **clé publique** permet de vérifier les jetons
- la **clé privée** permet de les signer

Le garde vérifie.  
La forge, elle, signe.

### Les utilisateurs en base

Avant toute chose, il faut des identités.

Au démarrage, quelques comptes sont inscrits dans les registres de la taverne :

```java
@ApplicationScoped
public class StartupService {

    @Transactional
    void init(@Observes StartupEvent event) {
        if (TavernUser.count() > 0) {
            return;
        }

        TavernUser accountant = new TavernUser();
        accountant.username = "elora";
        accountant.password = BcryptUtil.bcryptHash("ledger123");
        accountant.role = "accountant";
        accountant.persist();

        TavernUser treasurer = new TavernUser();
        treasurer.username = "borin";
        treasurer.password = BcryptUtil.bcryptHash("vault123");
        treasurer.role = "treasurer";
        treasurer.persist();
    }
}
```

Chaque mot de passe est **haché avec bcrypt** avant d’être conservé.

Ainsi, même dans les archives, aucun secret n’apparaît en clair.

### Le portail d’authentification

Pour obtenir un jeton, il faut passer par une porte dédiée :

```java
@Path("/api/tavern/auth")
@Consumes(MediaType.APPLICATION_JSON)
@Produces(MediaType.APPLICATION_JSON)
@Tag(name = "Tavern Auth", description = "Authentification et generation de JWT.")
public class AuthResource {

    private final AuthService authService;

    @Inject
    public AuthResource(AuthService authService) {
        this.authService = authService;
    }

    @POST
    @Path("/login")
    @Operation(summary = "Login", description = "Genere un JWT pour acceder aux ressources protegees.")
    @APIResponse(
            responseCode = "200",
            description = "JWT genere",
            content = @Content(schema = @Schema(implementation = LoginResponse.class))
    )
    @APIResponse(responseCode = "401", description = "Identifiants invalides")
    public Response login(LoginRequest request) {
        if (request == null || request.username() == null || request.password() == null) {
            return Response.status(Response.Status.BAD_REQUEST).build();
        }

        String token = authService.login(request.username(), request.password());
        if (token == null) {
            return Response.status(Response.Status.UNAUTHORIZED).build();
        }

        LoginResponse response = new LoginResponse(token, "Bearer", authService.expiresAtSeconds());
        return Response.ok(response).build();
    }
}
```

Ce point d’entrée agit comme un véritable guichet :

- il vérifie les identifiants
- il refuse les imposteurs
- il délivre un jeton aux membres reconnus

Un échec renvoie un refus immédiat (`401`).  
Un succès remet un sceau valide entre les mains du visiteur.

### La forge du jeton

C’est ici que tout se joue.

```java
@ApplicationScoped
public class AuthService {

    private static final String ISSUER = "tavern-issuer";
    private static final long TOKEN_TTL_SECONDS = 3600;

    public String login(String username, String password) {
        TavernUser user = TavernUser.find("username", username).firstResult();
        if (user == null || user.password == null || !BcryptUtil.matches(password, user.password)) {
            return null;
        }

        Instant now = Instant.now();
        return Jwt.issuer(ISSUER)
                .upn(user.username)
                .preferredUserName(user.username)
                .groups(Set.of(user.role))
                .issuedAt(now)
                .expiresAt(now.plusSeconds(TOKEN_TTL_SECONDS))
                .sign();
    }

    public long expiresAtSeconds() {
        return Instant.now().plusSeconds(TOKEN_TTL_SECONDS).getEpochSecond();
    }
}
```

Après vérification des identifiants :

- un jeton est créé
- signé avec la clé privée
- enrichi des rôles (`groups`)
- limité dans le temps (1 heure)

Quelques points clés :

- `groups` contient les rôles exploités par `@RolesAllowed`
- `issuer` doit correspondre à celui attendu côté vérification
- `expiresAt` impose une durée de vie stricte

Le jeton devient alors un **passe d’accès temporaire**, accepté par le reste du système.

### Tests rapides

Une fois le jeton obtenu, le mécanisme redevient le même :

```bash
TOKEN=$(curl -s -X POST http://localhost:8080/api/tavern/auth/login \
  -H "Content-Type: application/json" \
  -d '{"username":"elora","password":"ledger123"}' | jq -r .token)

curl -H "Authorization: Bearer $TOKEN" http://localhost:8080/api/tavern/ledger
```

Le visiteur ne présente plus ses identifiants.  
Il présente le sceau obtenu.

## Conclusion

Le livre de comptes et le coffre-fort ne se protègent pas comme une simple réserve.  
Une clé suffit pour une porte. Mais lorsqu’il s’agit de richesses, il faut des preuves.

Avec JWT, la taverne change de logique.  
Ce n’est plus l’identité que l’on présente à chaque passage, mais un **sceau** : forgé une fois, vérifié à chaque entrée.

Le garde ne questionne plus.  
Il observe le jeton, en vérifie l’origine, contrôle qu’il est encore valide… puis décide.

Mais ce modèle impose sa propre discipline.

Un jeton accepté reste valable jusqu’à son expiration.  
Il ne se retire pas aisément, ne se conteste pas.  
La sécurité ne repose donc pas uniquement sur la signature, mais sur un équilibre : durée de vie maîtrisée, renouvellement, et vigilance constante.

Ainsi, le livre demeure entre les mains légitimes,  
et le coffre ne s’ouvre qu’à ceux qui portent le bon sceau.

La taverne, elle, peut prospérer en toute confiance.  
Car ici, ce ne sont plus les mots qui ouvrent les portes…  
mais la preuve qu’ils ont déjà été donnés.

[Sécurisez vos API avec Spring Security : JWTProtéger une API est essentiel pour garantir la sécurité des données et prévenir les accès non autorisés. Dans cet article, découvrez comment sécuriser vos endpoints avec Spring Security et l’authentification par JSON Web Token.![](https://storage.ghost.io/c/0b/31/0b31478e-a66a-46f1-8c34-d44388e21192/content/images/icon/plume-point-425.png)sfeir.dev - Le média incontournable pour les passionnés de tech et d'intelligence artificielleErwan Le Tutour![](https://storage.ghost.io/c/0b/31/0b31478e-a66a-46f1-8c34-d44388e21192/content/images/thumbnail/20250827_0901_Pixelated-JWT-Magic_simple_compose_01k3n565byfy6adt0079zxj0dd-1.png)](https://www.sfeir.dev/back/securisez-vos-api-avec-spring-security-jwt/)

[À la découverte de QuarkusQuarkus est un framework Java moderne et performant qui simplifie le développement d’applications cloud natives : découvrons-le au travers de la création d’une application exposant des micro-services REST.![](https://storage.ghost.io/c/0b/31/0b31478e-a66a-46f1-8c34-d44388e21192/content/images/icon/plume-point-426.png)sfeir.dev - Le média incontournable pour les passionnés de tech et d'intelligence artificielleFlorian Desfossés![](https://storage.ghost.io/c/0b/31/0b31478e-a66a-46f1-8c34-d44388e21192/content/images/thumbnail/_bd022ee4-3172-42c7-a8ad-af987a0ec9f7-1.jpeg)](https://www.sfeir.dev/back/a-la-decouverte-de-quarkus/)

---

Tout le code relatif à cet article peut être consulter ici :

[GitHub - ErwanLT/quarkus-demo: Demo project for quarkus possibilityDemo project for quarkus possibility. Contribute to ErwanLT/quarkus-demo development by creating an account on GitHub.![](https://static.ghost.org/v5.0.0/images/link-icon.svg)GitHubErwanLT![](https://repository-images.githubusercontent.com/943905754/5635256a-85fe-4dc6-8c3a-7e4df754b269)](https://github.com/ErwanLT/quarkus-demo?ref=sfeir.dev)