Aller au contenu

Sécuriser le cellier d'une taverne avec Basic Auth dans Quarkus

Protéger une API ne doit pas être complexe. Avec Quarkus, découvrez trois approches pour sécuriser vos endpoints en Basic Auth : utilisateurs embarqués, base JDBC ou modèle JPA. De la solution simple au système évolutif, posez les fondations d’une sécurité efficace.

Sécurisons les vivres de la taverne

La nuit tombe sur la taverne. Les tables se vident, les bougies vacillent, et il ne reste plus qu'un dernier problème a régler : protéger le cellier.
Les roublards, voleurs et nains trop voraces rôdent. S'ils mettent la main sur les provisions et la bière, l'auberge est perdue.

Dans cet article, on met en place Basic Auth dans Quarkus avec trois approches, dans cet ordre :

  • utilisateurs embarques
  • utilisateurs stockes en base via JDBC
  • utilisateurs stockes via JPA

Resume rapide

Approche Ou sont les utilisateurs Quand l'utiliser
Embedded Dans application.properties Demo, POC, cas ultra simples
JDBC En base, via requete SQL Besoin d'un stockage simple et direct
JPA En base, via entite JPA Modele evolutif et besoin de domain modeling

Encart pour les habitués de Spring Boot

Si tu viens de Spring Boot, certains reflexes vont te manquer ici, et c'est normal :

  • Pas de UserDetailsService a implementer : Quarkus branche un Identity Provider directement depuis les dépendances declarées.
  • Pas de classe de configuration securite : la plupart des règles passent par application.properties et les annotations (@RolesAllowed).

Dans nos trois exemples, c'est Quarkus qui fournit l'identité en fonction du module :

  • utilisateurs embarques avec quarkus-elytron-security-properties-file
  • utilisateurs en base via quarkus-elytron-security-jdbc
  • utilisateurs via JPA avec quarkus-security-jpa

Tu te concentres sur les endpoints et les roles, le reste est geré par le moteur de sécurité.

Basic Auth, en pratique

Basic Auth est un mécanisme tres simple :

  • Le client envoie un header Authorization: Basic base64(username:password).
  • Il n'y a aucun chiffrement par lui-même, d'ou l'usage obligatoire de HTTPS.
  • Le serveur decode, vérifie les credentials, et applique les roles.

Point clé a retenir : l'authentification est gérée par Quarkus. Ton code metier ne doit jamais manipuler les identifiants ou mots de passe. C'est un principe de base en sécurité : l'application expose des routes et des roles, l'infrastructure de sécurité gère l'identité.

Utilisateurs embarques (embedded user)

Premier rempart : une serrure simple, directe, avec des utilisateurs declares dans application.properties. C'est ideal pour un prototype ou un donjon d'entrainement.

Les dépendances

Pour sécuriser l’API avec une authentification simple, deux dépendances sont nécessaires :

<dependency>
    <groupId>io.quarkus</groupId>
    <artifactId>quarkus-elytron-security-properties-file</artifactId>
</dependency>
<dependency>
    <groupId>io.quarkus</groupId>
    <artifactId>quarkus-security</artifactId>
</dependency>

les dépendances qui seront utilisées

  • quarkus-security : Cette dépendance constitue le socle de la sécurité dans Quarkus.
    Elle active les mécanismes de base : gestion des identités, rôles, et intégration avec les annotations de sécurité (@RolesAllowed, etc.).
    Autrement dit, elle fournit l’infrastructure nécessaire pour protéger les endpoints.
  • quarkus-elytron-security-properties-file : Cette extension permet de définir des utilisateurs directement dans des fichiers de configuration (application.properties ou fichiers dédiés).
    Elle repose sur Elytron, le moteur de sécurité utilisé par Quarkus, et offre une solution simple pour déclarer des utilisateurs / associer des mots de passe / définir des rôles.

Les properties

Configuration dans application.properties :

# Basic Auth avec utilisateurs embarques
quarkus.http.auth.basic=true

quarkus.security.users.embedded.enabled=true
quarkus.security.users.embedded.plain-text=true

quarkus.security.users.embedded.users.keeper=keeper123
quarkus.security.users.embedded.roles.keeper=keeper

quarkus.security.users.embedded.users.supplier=supplier123
quarkus.security.users.embedded.roles.supplier=supplier

les properties

Mais concrètement, que font elles ?

  • quarkus.http.auth.basic=true
    • Active l’authentification Basic Auth au niveau HTTP
    • Sans cette propriété, aucune authentification n’est appliquée
    • Si le header Authorization est absent ou invalide → réponse 401 Unauthorized
  • quarkus.security.users.embedded.enabled=true
    • Active un Identity Provider embarqué basé sur la configuration
    • Les utilisateurs sont définis directement dans application.properties
    • Aucun besoin de base de données
    • Idéal pour du test ou du prototypage
  • quarkus.security.users.embedded.plain-text=true
    • Autorise les mots de passe en clair (⚠️ À utiliser uniquement en développement)
  • quarkus.security.users.embedded.users.keeper=keeper123 quarkus.security.users.embedded.users.supplier=supplier123
    • Déclare les utilisateurs et leurs mots de passe
    • Format :
      • users.<username>=<password>
  • quarkus.security.users.embedded.roles.keeper=keeper quarkus.security.users.embedded.roles.supplier=supplier
    • Associe des rôles aux utilisateurs
    • Format :
      • roles.<username>=<role1>,<role2>
    • Ces rôles sont utilisés dans le code avec @RolesAllowed

L'API

Les endpoints du cellier et de la reserve sont proteges via des roles :

@Path("/api/tavern")
@Produces(MediaType.APPLICATION_JSON)
@Tag(name = "Tavern Stock", description = "Acces a la reserve de nourriture et de biere.")
@SecurityScheme(securitySchemeName = "basicAuth", type = SecuritySchemeType.HTTP, scheme = "basic")
@SecurityRequirement(name = "basicAuth")
public class PantryResource {

    private final PantryService pantryService;

    @Inject
    public PantryResource(PantryService pantryService) {
        this.pantryService = pantryService;
    }

    @GET
    @Path("/pantry")
    @RolesAllowed("keeper")
    @Operation(summary = "Reserve de nourriture", description = "Liste le contenu de la reserve.")
    @APIResponse(
            responseCode = "200",
            description = "Contenu de la reserve",
            content = @Content(schema = @Schema(implementation = StockResponse.class))
    )
    public StockResponse pantry() {
        return pantryService.pantry();
    }

    @GET
    @Path("/cellar")
    @RolesAllowed({"keeper", "supplier"})
    @Operation(summary = "Cave a biere", description = "Liste le contenu de la cave.")
    @APIResponse(
            responseCode = "200",
            description = "Contenu de la cave",
            content = @Content(schema = @Schema(implementation = StockResponse.class))
    )
    public StockResponse cellar() {
        return pantryService.cellar();
    }
}

notre API

Tests rapides :

En bref : rapide a mettre en place, ideal pour des environnements de test ou de demo.

Utilisateurs stockes en base via JDBC

Quand la taverne grandit, la simple serrure du cellier ne suffit plus.
Il faut un contrôle plus rigoureux : vérifier précisément qui entre, et sur quelle base.

Les accès ne sont plus définis à la main, mais reposent désormais sur une source fiable et centralisée : une base de données.

Le cellier reste le même… mais la manière de contrôler son accès devient plus solide.
Ici, Quarkus s’appuie sur Elytron JDBC pour aller vérifier les identités directement en base.

Les dépendances

Pour activer l’authentification via une base de données, on utilise l’extension JDBC de sécurité :

<dependency>
    <groupId>io.quarkus</groupId>
    <artifactId>quarkus-elytron-security-jdbc</artifactId>
</dependency>

les dépendances à utilisées

Elle vient compléter le socle quarkus-security déjà présent.

  • quarkus-security : fournit l’infrastructure de sécurité (identité, rôles, annotations)
  • quarkus-elytron-security-jdbc : permet d’interroger une base de données pour authentifier les utilisateurs

Les properties

Configuration dans application.properties :

quarkus.http.auth.basic=true

quarkus.datasource.db-kind=h2
quarkus.datasource.username=sa
quarkus.datasource.password=sa
quarkus.datasource.jdbc.url=jdbc:h2:mem:tavern-security;DB_CLOSE_DELAY=-1;INIT=RUNSCRIPT FROM 'classpath:db/init.sql'

quarkus.security.jdbc.enabled=true
quarkus.security.jdbc.principal-query.sql=SELECT u.password, u.role FROM tavern_user u WHERE u.username=?
quarkus.security.jdbc.principal-query.clear-password-mapper.enabled=true
quarkus.security.jdbc.principal-query.clear-password-mapper.password-index=1
quarkus.security.jdbc.principal-query.attribute-mappings.0.index=2
quarkus.security.jdbc.principal-query.attribute-mappings.0.to=groups

# Swagger UI accessible sans authentification
quarkus.http.auth.permission.swagger-ui.paths=/q/swagger-ui/*,/q/openapi
quarkus.http.auth.permission.swagger-ui.policy=permit

# API taverne protegees
quarkus.http.auth.permission.tavern.paths=/api/tavern/*
quarkus.http.auth.permission.tavern.policy=authenticated

les properties

Mais concrètement, que font-elles ?

  • quarkus.http.auth.basic=true
    • Active l’authentification Basic Auth
    • Comme précédemment, sans cette propriété → aucune sécurité appliquée
  • quarkus.datasource.db-kind=h2
    • Définit le type de base de données (ici H2 en mémoire)
  • quarkus.datasource.jdbc.url=...
    • Configure la connexion à la base
    • Initialise automatiquement le schéma et les données via un script SQL
  • quarkus.datasource.username / password
    • Identifiants d’accès à la base
  • quarkus.security.jdbc.enabled=true
    • Active l’Identity Provider basé sur JDBC
  • quarkus.security.jdbc.principal-query.sql=...
    • Requête exécutée pour authentifier un utilisateur
    • Elle doit retourner :
      • le mot de passe
      • les rôles
  • clear-password-mapper
    • Indique que le mot de passe est stocké en clair (⚠️ uniquement pour démo)
    • password-index=1 → position du mot de passe dans le résultat SQL
  • attribute-mappings
    • Permet de mapper les colonnes vers les rôles
    • index=2 → colonne contenant le rôle
    • to=groups → correspond aux rôles utilisés par @RolesAllowed

Les données

Les utilisateurs ne sont plus définis dans les properties, mais directement en base :

CREATE TABLE tavern_user (
  id INT PRIMARY KEY,
  username VARCHAR(255),
  password VARCHAR(255),
  role VARCHAR(255)
);

INSERT INTO tavern_user (id, username, password, role) VALUES (1, 'keeper', 'keeper123', 'keeper');
INSERT INTO tavern_user (id, username, password, role) VALUES (2, 'supplier', 'supplier123', 'supplier');

le script d'initialisation de la base de données

L’API

Rien ne change côté endpoints.

Les règles d’accès restent définies par les rôles avec @RolesAllowed.
Seule la source d’identité évolue : elle est désormais externalisée en base.

En bref

Une approche simple et robuste :
les utilisateurs sont centralisés, accessibles, et facilement maintenables.

Parfaite lorsque la taverne commence à accueillir du monde…
mais sans encore nécessiter toute la richesse d’un modèle objet complet.

Utilisateurs stockés via JPA

Dernier niveau de défense : un contrôle d’accès entièrement intégré au fonctionnement de la taverne.

Le cellier n’est plus simplement protégé par une vérification externe.
Les règles d’accès font désormais partie du système lui-même, avec des identités structurées, sécurisées et prêtes à évoluer.

On ne se contente plus de vérifier qui entre :
on modélise et on sécurise durablement les accès au cellier.

Les dépendances

Pour activer cette approche, on utilise l’extension dédiée :

<dependency>
    <groupId>io.quarkus</groupId>
    <artifactId>quarkus-security-jpa</artifactId>
</dependency>

la dépendance pour la sécurité avec JPA

Elle s’appuie sur JPA pour gérer les utilisateurs comme des entités.

  • quarkus-security : socle de la sécurité (identité, rôles, annotations)
  • quarkus-security-jpa : permet de déléguer l’authentification à des entités JPA

L’entité utilisateur

Les utilisateurs sont désormais définis comme une entité persistée :

@Entity
@UserDefinition
public class TavernUser extends PanacheEntity {

    @Username
    public String username;

    @Password
    public String password;

    @Roles
    public Set<String> roles;
}

notre model pour les utilisateur

Mais concrètement, que font ces annotations ?

  • @UserDefinition
    • Indique que cette entité représente un utilisateur de sécurité
  • @Username
    • Champ utilisé comme identifiant de connexion
  • @Password
    • Champ contenant le mot de passe (hashé)
  • @Roles
    • Définit les rôles associés à l’utilisateur

Ici, Quarkus fait le lien automatiquement entre le modèle JPA et le moteur de sécurité.

Initialisation des utilisateurs

Les comptes sont créés au démarrage de l’application :

TavernUser keeper = new TavernUser();
keeper.username = "keeper";
keeper.password = BcryptUtil.bcryptHash("keeper123");
keeper.roles = Set.of("keeper");
keeper.persist();

TavernUser supplier = new TavernUser();
supplier.username = "supplier";
supplier.password = BcryptUtil.bcryptHash("supplier123");
supplier.roles = Set.of("supplier");
supplier.persist();

Contrairement aux approches précédentes :

  • les mots de passe sont hashés avec bcrypt
  • les utilisateurs sont manipulés comme de vrais objets métier

Les properties

Configuration dans application.properties :

quarkus.http.auth.basic=true

quarkus.datasource.db-kind=h2
quarkus.datasource.username=sa
quarkus.datasource.password=sa
quarkus.datasource.jdbc.url=jdbc:h2:mem:tavern-security-jpa;DB_CLOSE_DELAY=-1

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

les properties

  • quarkus.datasource.*
    • Configure la base de données (ici H2 en mémoire)
  • quarkus.hibernate-orm.database.generation=drop-and-create
    • Génère automatiquement le schéma à partir des entités
    • Réinitialise la base à chaque démarrage (pratique en dev)

L’API

Aucun changement côté endpoints.

Les règles d’accès restent définies via @RolesAllowed.
Seule la manière de gérer les identités évolue : elles sont maintenant intégrées au modèle applicatif.

Limites de Basic Auth

Basic Auth remplit parfaitement son rôle pour démarrer, mais il montre rapidement ses limites :

  • pas de véritable mécanisme de déconnexion (les credentials sont souvent conservés par le navigateur)
  • identifiants envoyés à chaque requête
  • peu adapté aux applications modernes (SPA, mobile)
  • à proscrire sans HTTPS

Dès que la taverne s’ouvre au public ou prend de l’ampleur, il devient nécessaire d’adopter des solutions plus solides, comme les systèmes à base de tokens (OAuth2, OIDC).

Conclusion

Du simple verrou posé sur une porte jusqu’au registre soigneusement tenu, voire au grimoire protégé, le cellier de la taverne est désormais sous bonne garde.

Avec Quarkus, la sécurité se met en place par couches, sans complexité inutile : tu définis les rôles, les accès, et le moteur fait le reste.
Qu’il s’agisse d’utilisateurs embarqués, d’une base JDBC ou d’un modèle JPA, l’essentiel reste le même : séparer clairement l’identité du code métier.

Et si un roublard tente malgré tout de forcer l’entrée, il trouvera porte close.

Prochaine étape : renforcer encore la défense, et abandonner les serrures visibles pour des mécanismes plus discrets et plus robustes.


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

GitHub - ErwanLT/quarkus-demo: Demo project for quarkus possibility
Demo project for quarkus possibility. Contribute to ErwanLT/quarkus-demo development by creating an account on GitHub.

Dernier