La sécurité au niveau des lignes est un mécanisme de base de données où des politiques filtrent, à chaque requête, les lignes que chacun peut voir ou modifier. Le modèle mental est celui d’une clause WHERE invisible et obligatoire : la condition que vous devriez sinon penser à écrire dans chaque requête devient une propriété de la table elle-même — appliquée par le moteur, sur chaque chemin d’accès, y compris ceux que votre code applicatif ne voit jamais.
Points clés
| Question | Réponse |
|---|---|
| Ce que c’est | Un contrôle d’accès par ligne, appliqué par le moteur de base de données lui-même |
| Le modèle mental | Une clause WHERE impossible à oublier |
| Le cas d’usage phare | L’isolation multi-tenant et les données par utilisateur, dans la couche qu’une faille ne peut pas sauter |
| Les petites lignes | Rôles de contournement, refus par défaut, contexte de pooling — les pièges sont opérationnels |
| La défaillance qu’elle évite | Un filtre oublié dans le code applicatif = une fuite entre tenants |
RLS en dix lignes de SQL
CREATE TABLE invoices (
tenant_id uuid NOT NULL,
total numeric(10,2)
);
ALTER TABLE invoices ENABLE ROW LEVEL SECURITY; -- à partir d'ici : refus par défaut
CREATE POLICY tenant_isolation ON invoices
USING (tenant_id = current_setting('app.tenant')::uuid) -- ce que vous pouvez lire
WITH CHECK (tenant_id = current_setting('app.tenant')::uuid); -- ce que vous pouvez écrire
-- Chaque requête se comporte désormais comme si elle se terminait par WHERE tenant_id = <le vôtre>.
-- Une requête qui oublie son filtre ne renvoie rien — pas tout.
La même garantie existe dans les bases de données documentaires sous la forme d’un contrôle d’accès porté par la ligne elle-même — chaque objet porte son ACL, et la plateforme l’applique à chaque requête :
// JavaScript / Node.js — Back4app JS SDK
// Row-level security as data: this row is readable by its owner, period
const note = new Parse.Object('Note');
note.set('text', 'Q3 salary planning');
note.setACL(new Parse.ACL(Parse.User.current())); // owner-only, on the row itself
await note.save();
// Later, any query by any user — the row exists only for its owner
const notes = await new Parse.Query('Note').find(); // others never see it // Flutter / Dart — Back4app Flutter SDK
// Row-level security as data: this row is readable by its owner, period
final user = await ParseUser.currentUser() as ParseUser;
final acl = ParseACL(owner: user); // owner-only, on the row itself
final note = ParseObject('Note')
..set('text', 'Q3 salary planning')
..setACL(acl);
await note.save(); // other users' queries never return this row // iOS / Swift — Back4app Swift SDK
// Row-level security as data: this row is readable by its owner, period
var note = Note()
note.text = "Q3 salary planning"
note.ACL = try ParseACL.defaultACL() // owner-only, on the row itself
note.save { result in
if case .success = result {
print("saved — other users' queries never return this row")
}
} // Android / Kotlin — Back4app Android SDK
// Row-level security as data: this row is readable by its owner, period
val note = ParseObject("Note").apply {
put("text", "Q3 salary planning")
acl = ParseACL(ParseUser.getCurrentUser()) // owner-only, on the row itself
}
note.saveInBackground { e ->
if (e == null) Log.d("Notes", "saved — invisible to every other user")
} Ce que le moteur fait d’une requête
USING vs. WITH CHECK, permissives vs. restrictives
| Concept | Ce qu’il régit | Règle empirique |
|---|---|---|
USING | Ce qui existe pour vous : les lectures, et les lignes éligibles à update/delete | ”Puis-je le voir ?” |
WITH CHECK | Ce que vous pouvez écrire : les inserts, et les valeurs après update | ”Puis-je le créer ainsi ?” |
| Politiques permissives (par défaut) | Combinées par OR — une seule correspondance suffit | Accorder l’accès |
| Politiques restrictives | Ajoutées par AND — toutes doivent passer | Imposer des limites obligatoires |
Deux règles de composition à retenir de la référence PostgreSQL : omettre WITH CHECK réutilise USING pour les écritures, et au moins une politique permissive doit passer avant que les restrictives ne soient même consultées — uniquement des restrictives, et personne n’entre.
Où RLS est pris en charge
| Famille de moteurs | Mécanisme |
|---|---|
| PostgreSQL (9.5+) et dérivés | CREATE POLICY — l’implémentation de référence |
| SQL Server (2016+) | Politiques de sécurité sur des fonctions de prédicat inline (filter + block) |
| SQL distribué (CockroachDB, YugabyteDB) | Politiques compatibles PostgreSQL |
| Data warehouses cloud | Politiques d’accès par ligne, à la sauce de chaque fournisseur |
| Bases de données documentaires | Pas de politiques — des ACL par objet appliquées par la couche plateforme |
La dernière ligne est celle qui intéresse ce glossaire : sur les bases documentaires, la frontière au niveau des lignes est typiquement une donnée portée par la ligne (les ACL) plutôt qu’un prédicat dans le moteur — même garantie, mécanisme différent, détaillé dans l’article d’ingénierie de Back4app.
Les pièges, enfin réunis au même endroit
- Les surprises du refus par défaut. Activer RLS sans aucune politique bloque tout le monde sauf le propriétaire — la moitié des histoires “RLS a cassé la production”, c’est ça.
- La matrice de contournement. Les superutilisateurs, les rôles
BYPASSRLSet les propriétaires de table ignorent les politiques — définissezFORCE ROW LEVEL SECURITYsi le propriétaire est aussi l’utilisateur de l’application, et auditez qui détient quoi. - Les vues s’exécutent avec les droits de leur propriétaire par défaut — une vue sur une table sous RLS peut la contourner silencieusement, sauf si elle est créée avec les droits de l’appelant (
security_invokerdans PostgreSQL). - Les contraintes trahissent l’existence. Une violation d’unicité ou de clé étrangère peut révéler qu’une ligne invisible existe — le canal caché documenté ; traitez l’unicité sur des valeurs protégées en conséquence.
- Les erreurs peuvent trahir des valeurs. Des expressions conçues pour échouer sur des données précises (une division par zéro quand une valeur cachée correspond) exfiltrent via les messages d’erreur — la classe de canaux auxiliaires que décrit la documentation de SQL Server.
- Les dumps et les pools ont des modes. Les outils de sauvegarde peuvent nécessiter de désactiver la sécurité par ligne pour tout exporter ; les poolers en mode transaction exigent un contexte à portée de transaction (
SET LOCAL), sinon les tenants déteignent d’une requête à l’autre.
Performances : les politiques sont du code sur le chemin critique
Trois règles couvrent l’essentiel de l’expérience terrain : indexez chaque colonne référencée par une politique (le prédicat s’exécute avant les filtres propres à votre requête — sans index, c’est un parcours complet) ; gardez des prédicats sans jointure, en déportant les recherches dans des fonctions ou des vérifications de rôle ; et faites en sorte que les fonctions par requête soient évaluées une fois par requête, pas une fois par ligne, en les encapsulant dans une sous-requête scalaire. Bien mesurée, RLS coûte quelques points de pourcentage ; mal mesurée, c’est le ralentissement mystérieux sur chaque table que vous avez sécurisée.
Cas d’usage courants
- SaaS multi-tenant. Le cas canonique — l’isolation des tenants appliquée sous l’application, là où un filtre oublié ne peut pas devenir une fuite.
- Données par utilisateur. Messages, documents, dossiers médicaux : les utilisateurs voient leurs lignes, point.
- Frontières départementales et régionales. Les commerciaux voient leur région ; les auditeurs voient tout, en lecture seule — la superposition de politiques à l’œuvre.
- Régimes de conformité. Un contrôle d’accès démontrable au niveau de la couche de données, là où les auditeurs l’aiment.
- Accès analytique partagé. Les analystes interrogent directement des répliques de production et ne voient que ce que leur rôle autorise — sûr parce que c’est la base de données qui l’applique, pas le dashboard.
Devriez-vous appliquer le contrôle au niveau des lignes ? Matrice de décision
| Appliquez avec RLS/ACL quand… | Le filtrage applicatif peut suffire quand… |
|---|---|
| Plusieurs tenants ou utilisateurs partagent des tables | La base de données a exactement un appelant de confiance |
| Autre chose que l’app touche la base de données | Aucun outil de BI, aucun SQL d’administration, aucun second service |
| Un filtre oublié signifie une fuite, pas un bug | Le cloisonnement est une commodité, pas de la sécurité |
| La conformité exige une preuve au niveau des données | Les données ne sont pas sensibles |
| Vous voulez une frontière testée une fois, de façon centralisée | Vous aimez auditer chaque requête pour toujours |
La synthèse honnête des deux colonnes : gardez le cloisonnement dans l’application pour la lisibilité — et appliquez-le quand même dans la base de données. La défense en profondeur est tout l’enjeu.
Limites et trade-offs
- Les politiques sont invisibles par conception — ce qui fait du débogage de “où sont passées mes lignes ?” une vraie compétence ; journalisez d’abord le contexte de session.
- Une autorisation complexe dépasse les prédicats. Les règles qui nécessitent un état de workflow ou une logique entre entités relèvent des couches d’autorisation applicatives, avec RLS comme filet de sécurité.
- La surface opérationnelle est réelle. La matrice de contournement, les modes de dump et le contexte de pooling sont un savoir opérationnel continu, pas une configuration ponctuelle.
- L’évaluation par ligne est une taxe — faible quand elle est bien conçue, illimitée sinon.
- Elle sécurise des lignes, pas des colonnes. Masquer des champs relève de la sécurité au niveau des colonnes ; combinez les deux pour un effet au niveau de la cellule.
La sécurité au niveau des lignes sur Back4app
Back4app est une plateforme open-source de Backend as a Service (BaaS) qui combine une base de données gérée, des API REST et GraphQL générées automatiquement, l’authentification, le stockage de fichiers et des fonctions serverless avec Cloud Code. Son modèle au niveau des lignes est le mécanisme d’ACL-comme-donnée des onglets ci-dessus : chaque objet porte sa liste d’accès, les rôles expriment les tenants et les équipes, et la plateforme applique les deux à chaque requête — SDK, REST, GraphQL et dashboard compris, avec les permissions au niveau de la classe comme couche restrictive par-dessus. C’est la garantie de la clause WHERE obligatoire, livrée sur une base de données documentaire, avec la liste de pièges ci-dessus absorbée par la plateforme plutôt que laissée à votre charge.
Questions fréquentes
Qu'est-ce que la sécurité au niveau des lignes, en termes simples ?
Une clause WHERE invisible et obligatoire. Vous attachez des politiques à une table, et la base de données applique automatiquement leurs conditions à chaque requête — chaque utilisateur ne voit et ne modifie que les lignes que la politique autorise, quelle que soit la requête qu'il écrit ou l'outil qu'il utilise. Le filtre vit dans la base de données : aucun chemin de code applicatif ne peut l'oublier.
Comment fonctionne la sécurité au niveau des lignes ?
Vous activez RLS sur une table et créez des politiques contenant des prédicats booléens — qui comparent typiquement la colonne de propriétaire ou de tenant d'une ligne à l'utilisateur courant ou à une variable de session. À l'exécution, le moteur évalue le prédicat ligne par ligne avant les conditions de l'utilisateur, en filtrant les lectures et en bloquant les écritures non autorisées. Une fois RLS activé sans aucune politique, le comportement par défaut est de tout refuser.
Quelle est la différence entre USING et WITH CHECK ?
Le sens. USING filtre ce qui existe pour vous : les lignes visibles par SELECT et éligibles à UPDATE ou DELETE. WITH CHECK valide ce que vous écrivez : les lignes insérées et les nouvelles valeurs après une mise à jour. Omettez WITH CHECK et le prédicat USING s'applique aux deux — mais les tables où les utilisateurs lisent largement et écrivent de façon restreinte ont besoin des deux, définis différemment.
Qui peut contourner la sécurité au niveau des lignes ?
Dans PostgreSQL : les superutilisateurs, les rôles dotés de BYPASSRLS et — celui que tout le monde oublie — le propriétaire de la table, sauf si vous définissez FORCE ROW LEVEL SECURITY. Auditer cette matrice de contournement fait partie du déploiement de RLS ; une politique parfaite ne protège rien si l'application se connecte en tant que propriétaire de la table.
La sécurité au niveau des lignes dégrade-t-elle les performances ?
Elle le peut — le prédicat de la politique est évalué sur les lignes candidates à chaque requête. Les parades sont constantes d'une expérience de production à l'autre : indexez les colonnes référencées par vos politiques, gardez des prédicats sans jointure (passez plutôt par des fonctions ou des rôles de recherche) et encapsulez les fonctions par requête pour qu'elles soient évaluées une fois par requête plutôt qu'une fois par ligne. Une politique sur une colonne non indexée, c'est un parcours complet de table avec un badge de sécurité.
Quelle est la différence entre RLS et le filtrage applicatif ?
L'endroit où vit la frontière. Le filtrage applicatif restreint les requêtes dans le code — flexible, mais dupliqué sur chaque chemin de code et contourné par tout ce qui parle directement à la base de données. RLS applique la règle dans le moteur, en couvrant chaque chemin d'accès, outils d'administration et autres services compris. Le consensus est la défense en profondeur : restreignez dans l'application pour la clarté, appliquez dans la base de données pour la sécurité.
Comment RLS fonctionne-t-il avec un pool de connexions ?
Avec précaution. Les applications qui utilisent un pool se connectent sous un seul utilisateur de base de données, donc les politiques s'appuient sur une variable de session par requête plutôt que sur l'identité de la connexion — définie à l'emprunt, lue par la politique, réinitialisée au retour. Avec les poolers en mode transaction, le paramètre doit avoir une portée de transaction, sinon le contexte d'un tenant peut fuiter vers la requête suivante sur la même connexion. Cette interaction est le bug RLS le plus fréquent en production.
Comment tester des politiques de sécurité au niveau des lignes ?
Endossez chaque rôle et exécutez les quatre opérations — select, insert, update, delete — en vérifiant à la fois ce qui apparaît et ce qui est refusé. Ajoutez les deux cas oubliés : le contexte non défini (l'absence de variable de tenant doit signifier aucune ligne, pas toutes les lignes) et la matrice de contournement (propriétaire, superutilisateur, chemins de réplication). RLS gagne votre confiance par des tests adverses, pas parce que la politique se lit bien.