Image de couverture : Multi-tenant avec PostgreSQL RLS et Hibernate : où poser le tenant sans fuite de données
Bases de données

Multi-tenant avec PostgreSQL RLS et Hibernate : où poser le tenant sans fuite de données

18 octobre 2026
5 min de lecture
47 vues
Sébastien Muler

Quatre endroits pour dire à PostgreSQL qui est le client, deux seulement sont fiables

Avec le row-level security (RLS) de PostgreSQL, c'est la base elle-même qui filtre les lignes visibles selon le client courant. Encore faut-il lui indiquer qui est ce client. Le moyen classique consiste à poser un paramètre sur la session de connexion avec set_config('app.tenant', ...). Dans une application Spring Boot, la question devient alors : à quel moment et à quel endroit exécuter cette instruction ? Une mesure publiée par Chris van Eijk (Now-Next) compare quatre emplacements sous Spring Boot 4 et Hibernate 7, et les résultats méritent un audit de vos propres projets.

Le piège : le pool garde la session en vie

Derrière toutes les approches se trouve HikariCP, qui conserve les sessions PostgreSQL ouvertes entre les requêtes HTTP. Un paramètre posé sur une connexion y reste tant que personne ne le remplace. Si une requête suivante récupère cette même connexion sans définir son propre tenant, elle hérite silencieusement de celui du client précédent. Le RLS fait alors exactement ce qu'on lui demande : il montre les lignes du mauvais client.

Les quatre emplacements testés sont les suivants :

  • un wrapper autour du DataSource, qui pose le paramètre à chaque sortie de connexion du pool
  • un HandlerInterceptor Spring MVC, qui le pose avant l'exécution du contrôleur
  • la première instruction d'une méthode de service annotée @Transactional
  • le MultiTenantConnectionProvider propre à Hibernate

Le banc d'essai utilisait Spring Boot 4.1.1, Hibernate ORM 7.4.5, HikariCP avec son pool par défaut de 10 connexions, PostgreSQL 17 et Java 21. La table de test contenait des factures de deux tenants, avec le RLS activé. La configuration Spring Boot par défaut a été conservée, y compris spring.jpa.open-in-view qui reste actif.

Ce que les mesures ont montré

Le verdict est net et dépend de l'endroit choisi :

  • Deux des quatre approches ont livré à une requête sans tenant les factures du client précédent. Une requête qui oublie de se déclarer n'est donc pas refusée : elle récupère les données d'un autre.
  • L'intercepteur HTTP a fait pire. Sous 30 requêtes concurrentes, il a aussi servi à des requêtes qui avaient bien posé leur tenant les lignes d'un autre client. Le décalage entre le moment où le paramètre est posé et celui où la connexion est réellement utilisée suffit à mélanger les identités quand plusieurs requêtes se disputent le pool.
  • La méthode @Transactional et le provider Hibernate ont, eux, séparé correctement tous les tenants.

Une nuance importante concerne le provider : son isolation n'est garantie que pour les requêtes passant par une connexion que Hibernate a lui-même fournie. Tout accès qui contourne Hibernate (JDBC direct, par exemple) sort de ce périmètre.

Pourquoi la transaction protège mieux

La logique est simple. Poser le tenant dans la transaction, avec une portée locale à celle-ci, lie le paramètre à la durée de l'unité de travail et non à la durée de vie de la connexion. Quand la transaction se termine, la valeur disparaît. Le provider Hibernate applique le même principe au moment où il remet la connexion à l'ORM. À l'inverse, un paramètre posé de façon globale sur la session survit au passage dans le pool, et c'est précisément ce qui crée les fuites.

Que vérifier dans votre code

Si vous exploitez une application multi-tenant avec RLS, voici une démarche d'audit raisonnable :

  • Repérer où le tenant est posé : wrapper de DataSource, intercepteur, service ou provider.
  • Vérifier que la valeur est limitée à la transaction et jamais laissée sur la session du pool.
  • Écrire un test de concurrence qui lance des requêtes de plusieurs tenants en parallèle, y compris des requêtes sans tenant, et qui échoue si une ligne étrangère apparaît.
  • Contrôler qu'aucun accès ne contourne Hibernate sans repasser par le même mécanisme.

Un test de charge de ce type est peu coûteux à écrire, et c'est le seul moyen de découvrir ces fuites, qui n'apparaissent presque jamais en développement avec une seule requête à la fois.

Conclusion

Le RLS ne protège que si le tenant est défini au bon endroit et au bon moment. Les mesures montrent que la méthode transactionnelle et le provider Hibernate tiennent l'isolation, quand le wrapper de DataSource et surtout l'intercepteur laissent passer des lignes d'autres clients. Les détails complets, les chiffres et le protocole de test sont dans l'article d'origine : Chris van Eijk: Hibernate multi-tenancy and PostgreSQL RLS in Spring Boot.

Ce que ça change pour vous

Si votre logiciel sert plusieurs clients sur une même base, un mauvais réglage technique peut montrer à l'un les factures d'un autre, surtout quand beaucoup d'utilisateurs se connectent en même temps. Ce genre de fuite ne se voit pas lors d'un simple essai et peut engager votre responsabilité vis-à-vis de vos clients. Demandez à votre prestataire s'il a testé l'isolation des données avec plusieurs clients simultanés. Une vérification ciblée de ce point coûte peu au regard du risque.

Partager cet article

Et votre site, il vaut quoi ?

Vitesse, accessibilité, référencement technique. Le rapport est écrit pour un dirigeant, pas pour un développeur. Gratuit, sans inscription.

Tester mon site