Zeilenbasierte Sicherheit ist ein Datenbankmechanismus, bei dem Policies bei jeder Abfrage filtern, welche Zeilen jemand sehen oder ändern darf. Das mentale Modell ist eine erzwungene, unsichtbare WHERE-Klausel: Die Bedingung, an die Sie sonst in jeder Abfrage denken müssten, wird zu einer Eigenschaft der Tabelle selbst – angewendet von der Engine, auf jedem Zugriffspfad, auch auf denen, die Ihr Anwendungscode nie zu sehen bekommt.
Das Wichtigste in Kürze
| Frage | Antwort |
|---|---|
| Was es ist | Zugriffskontrolle pro Zeile, durchgesetzt von der Datenbank-Engine selbst |
| Das mentale Modell | Eine WHERE-Klausel, die niemand vergessen kann |
| Der wichtigste Anwendungsfall | Multi-Tenant-Isolation und Daten pro Benutzer, in der Schicht, die ein Angriff nicht überspringen kann |
| Das Kleingedruckte | Umgehungsrollen, Default-Deny, Pooling-Kontext – die Fallstricke sind operativer Natur |
| Der Fehler, den sie verhindert | Ein vergessener Filter im Anwendungscode = Datenabfluss zwischen Tenants |
RLS in zehn Zeilen SQL
CREATE TABLE invoices (
tenant_id uuid NOT NULL,
total numeric(10,2)
);
ALTER TABLE invoices ENABLE ROW LEVEL SECURITY; -- ab hier: standardmäßig verboten
CREATE POLICY tenant_isolation ON invoices
USING (tenant_id = current_setting('app.tenant')::uuid) -- was Sie lesen dürfen
WITH CHECK (tenant_id = current_setting('app.tenant')::uuid); -- was Sie schreiben dürfen
-- Jede Abfrage verhält sich nun so, als endete sie mit WHERE tenant_id = <Ihrer>.
-- Eine Abfrage, die ihren Filter vergisst, liefert nichts – nicht alles.
Dieselbe Garantie existiert in Dokumentdatenbanken als Zugriffskontrolle auf der Zeile selbst – jedes Objekt trägt seine ACL, und die Plattform setzt sie bei jeder Anfrage durch:
// 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")
} Was die Engine aus einer Abfrage macht
USING vs. WITH CHECK, permissiv vs. restriktiv
| Konzept | Regelt | Faustregel |
|---|---|---|
USING | Was für Sie existiert: Lesezugriffe und Zeilen, die für Update/Delete infrage kommen | ”Darf ich es sehen?” |
WITH CHECK | Was Sie schreiben dürfen: Inserts und die Werte nach einem Update | ”Darf ich es so anlegen?” |
| Permissive Policies (Standard) | Mit OR verknüpft – ein Treffer genügt | Zugriff gewähren |
| Restriktive Policies | Mit AND darübergelegt – alle müssen passen | Zwingende Grenzen setzen |
Zwei Kompositionsregeln aus der PostgreSQL-Referenz lohnen sich zu merken: Wer WITH CHECK weglässt, verwendet USING auch für Schreibzugriffe, und mindestens eine permissive Policy muss passen, bevor die restriktiven überhaupt herangezogen werden – nur restriktive Policies heißt: niemand kommt herein.
Wo RLS unterstützt wird
| Engine-Familie | Mechanismus |
|---|---|
| PostgreSQL (9.5+) und Derivate | CREATE POLICY – die Referenzimplementierung |
| SQL Server (2016+) | Security-Policies über Inline-Prädikatfunktionen (filter + block) |
| Verteiltes SQL (CockroachDB, YugabyteDB) | PostgreSQL-kompatible Policies |
| Cloud-Data-Warehouses | Row Access Policies, je nach Anbieter unterschiedlich |
| Dokumentdatenbanken | Nicht policy-basiert – ACLs pro Objekt, durchgesetzt von der Plattformschicht |
Die letzte Zeile ist die, um die es in diesem Glossar geht: In Dokumentspeichern ist die Grenze auf Zeilenebene typischerweise ein Datum auf der Zeile (die ACL) statt ein Prädikat in der Engine – dieselbe Garantie, anderer Mechanismus, ausführlich beschrieben im Engineering-Beitrag von Back4app.
Die Fallstricke, endlich an einem Ort
- Überraschungen durch Default-Deny. RLS ohne Policy zu aktivieren sperrt alle außer dem Eigentümer aus – die Hälfte aller “RLS hat die Produktion lahmgelegt”-Geschichten ist genau das.
- Die Umgehungsmatrix. Superuser,
BYPASSRLS-Rollen und Tabelleneigentümer überspringen Policies – setzen SieFORCE ROW LEVEL SECURITY, wenn der Eigentümer zugleich der Benutzer der Anwendung ist, und auditieren Sie, wer was hält. - Views laufen standardmäßig mit den Rechten ihres Eigentümers – ein View auf eine RLS-Tabelle kann sie still umgehen, sofern er nicht mit den Rechten des Aufrufers erstellt wurde (
security_invokerin PostgreSQL). - Constraints verraten Existenz. Die Verletzung einer Unique- oder Fremdschlüssel-Constraint kann offenbaren, dass eine unsichtbare Zeile existiert – der dokumentierte verdeckte Kanal; behandeln Sie Eindeutigkeit auf geschützten Werten entsprechend.
- Fehler können Werte verraten. Gezielt gebaute Ausdrücke, die bei bestimmten Daten fehlschlagen (eine Division durch null, wenn ein verborgener Wert passt), schleusen Informationen über Fehlermeldungen aus – die Klasse von Seitenkanälen, die die SQL-Server-Dokumentation beschreibt.
- Dumps und Pools haben Modi. Backup-Werkzeuge brauchen unter Umständen die abgeschaltete Zeilensicherheit, um alles zu exportieren; Pooler im Transaktionsmodus brauchen einen transaktionsweiten Kontext (
SET LOCAL), sonst färben Tenants von Anfrage zu Anfrage ab.
Performance: Policies sind Code auf dem heißen Pfad
Drei Regeln decken den Großteil der Praxiserfahrung ab: Indizieren Sie jede Spalte, auf die eine Policy verweist (das Prädikat läuft vor den eigenen Filtern Ihrer Abfrage – ohne Index ist es ein voller Table Scan); halten Sie Prädikate frei von Joins, indem Sie das Nachschlagen in Funktionen oder Rollenprüfungen verlagern; und sorgen Sie dafür, dass Funktionen pro Anfrage einmal pro Abfrage ausgewertet werden, nicht einmal pro Zeile, indem Sie sie in eine skalare Unterabfrage kapseln. Sauber gemessen kostet RLS einstellige Prozentwerte; schlecht gemessen ist es die rätselhafte Verlangsamung auf jeder Tabelle, die Sie abgesichert haben.
Typische Anwendungsfälle
- Multi-Tenant-SaaS. Der kanonische Fall – Tenant-Isolation unterhalb der Anwendung durchgesetzt, dort, wo ein vergessener Filter kein Datenleck werden kann.
- Daten pro Benutzer. Nachrichten, Dokumente, Gesundheitsakten: Benutzer sehen ihre Zeilen, mehr nicht.
- Abteilungs- und Regionsgrenzen. Der Vertrieb sieht seine Region; Prüfer sehen alles, nur lesend – Policy-Schichtung in Aktion.
- Compliance-Anforderungen. Zugriffskontrolle, die sich auf der Datenschicht nachweisen lässt – genau dort, wo Prüfer sie sehen wollen.
- Geteilter analytischer Zugriff. Analysten fragen Produktionsreplikate direkt ab und sehen nur, was ihre Rolle erlaubt – sicher, weil die Datenbank es durchsetzt, nicht das Dashboard.
Sollten Sie auf Zeilenebene durchsetzen? Entscheidungsmatrix
| Mit RLS/ACLs durchsetzen, wenn … | Filterung in der Anwendung kann genügen, wenn … |
|---|---|
| Mehrere Tenants oder Benutzer sich Tabellen teilen | Die Datenbank genau einen vertrauenswürdigen Aufrufer hat |
| Auch anderes als die App die Datenbank berührt | Keine BI-Werkzeuge, kein Admin-SQL, kein zweiter Dienst |
| Ein vergessener Filter ein Leck bedeutet, keinen Fehler | Die Eingrenzung Bequemlichkeit ist, keine Sicherheit |
| Compliance einen Nachweis auf der Datenschicht verlangt | Die Daten nicht sensibel sind |
| Sie die Grenze einmal zentral getestet haben wollen | Sie es genießen, für immer jede Abfrage zu auditieren |
Die ehrliche Zusammenfassung beider Spalten: Behalten Sie die Eingrenzung in der Anwendung, weil sie den Code lesbar hält – und setzen Sie sie trotzdem in der Datenbank durch. Mehrschichtige Verteidigung ist genau der Punkt.
Grenzen und Trade-offs
- Policies sind von Natur aus unsichtbar – was das Debuggen von “wo sind meine Zeilen hin?” zu einer echten Fertigkeit macht; protokollieren Sie zuerst den Session-Kontext.
- Komplexe Autorisierung sprengt Prädikate. Regeln, die Workflow-Zustand oder entitätsübergreifende Logik brauchen, gehören in die Autorisierungsschicht der Anwendung, mit RLS als Auffangnetz.
- Die operative Oberfläche ist real. Umgehungsmatrix, Dump-Modi und Pooling-Kontext sind fortlaufendes Betriebswissen, keine einmalige Einrichtung.
- Die Auswertung pro Zeile ist eine Steuer – gering, wenn sauber konstruiert, unbegrenzt, wenn nicht.
- Sie sichert Zeilen, nicht Spalten. Felder zu verbergen ist Sicherheit auf Spaltenebene; kombinieren Sie beides für eine Wirkung auf Zellebene.
Zeilenbasierte Sicherheit mit Back4app
Back4app ist eine Open-Source-Plattform für Backend as a Service (BaaS), die eine verwaltete Datenbank, automatisch generierte REST- und GraphQL-APIs, Authentifizierung, Dateispeicher und Serverless-Funktionen mit Cloud Code kombiniert. Das Modell auf Zeilenebene ist der ACL-als-Daten-Mechanismus aus den Tabs oben: Jedes Objekt trägt seine Zugriffsliste, Rollen bilden Tenants und Teams ab, und die Plattform setzt beides bei jeder Anfrage durch – SDKs, REST, GraphQL und das Dashboard inklusive, mit Klassenberechtigungen als restriktiver Schicht darüber. Es ist die Garantie der erzwungenen WHERE-Klausel, geliefert auf einer Dokumentdatenbank, mit der obigen Fallstrickliste von der Plattform absorbiert statt Ihnen aufgebürdet.
Häufige Fragen
Was ist zeilenbasierte Sicherheit, einfach erklärt?
Eine erzwungene, unsichtbare WHERE-Klausel. Sie hängen Policies an eine Tabelle, und die Datenbank wendet deren Bedingungen automatisch auf jede Abfrage an – jeder Benutzer sieht und ändert nur die Zeilen, die die Policy erlaubt, ganz gleich welche Abfrage er schreibt oder welches Werkzeug er benutzt. Der Filter liegt in der Datenbank, also kann ihn kein Codepfad der Anwendung vergessen.
Wie funktioniert zeilenbasierte Sicherheit?
Sie aktivieren RLS auf einer Tabelle und legen Policies mit booleschen Prädikaten an – die typischerweise die Eigentümer- oder Tenant-Spalte einer Zeile mit dem aktuellen Benutzer oder einer Session-Variablen vergleichen. Zur Abfragezeit wertet die Engine das Prädikat pro Zeile aus, bevor die eigenen Bedingungen des Benutzers greifen, filtert Lesezugriffe und blockiert unerlaubte Schreibzugriffe. Ist RLS einmal ohne Policy aktiviert, gilt standardmäßig: alles verboten.
Was ist der Unterschied zwischen USING und WITH CHECK?
Die Richtung. USING filtert, was für Sie existiert: Zeilen, die für SELECT sichtbar und für UPDATE oder DELETE zugänglich sind. WITH CHECK prüft, was Sie schreiben: einzufügende Zeilen und die neuen Werte nach einem Update. Lassen Sie WITH CHECK weg, gilt das USING-Prädikat für beides – aber Tabellen, in denen Benutzer breit lesen und eng schreiben dürfen, brauchen beide, unterschiedlich definiert.
Wer kann zeilenbasierte Sicherheit umgehen?
In PostgreSQL: Superuser, Rollen mit BYPASSRLS und – der Fall, den alle vergessen – der Eigentümer der Tabelle, sofern Sie nicht FORCE ROW LEVEL SECURITY setzen. Diese Umgehungsmatrix zu auditieren gehört zum Ausrollen von RLS; eine perfekte Policy schützt nichts, wenn die Anwendung sich als Tabelleneigentümer verbindet.
Kostet zeilenbasierte Sicherheit Performance?
Sie kann es – das Prädikat der Policy wird bei jeder Abfrage gegen die Kandidatenzeilen ausgewertet. Die Gegenmaßnahmen sind über alle Produktionserfahrungen hinweg dieselben: Indizieren Sie die Spalten, auf die Ihre Policies verweisen, halten Sie Prädikate frei von Joins (nutzen Sie stattdessen Funktionen oder Rollenprüfungen) und kapseln Sie Funktionen pro Anfrage so, dass sie einmal pro Abfrage statt einmal pro Zeile ausgewertet werden. Eine Policy auf einer nicht indizierten Spalte ist ein Table Scan mit Sicherheitsabzeichen.
Was ist der Unterschied zwischen RLS und Filterung in der Anwendung?
Wo die Grenze liegt. Filterung in der Anwendung grenzt Abfragen im Code ein – flexibel, aber in jedem Codepfad dupliziert und von allem umgangen, was direkt mit der Datenbank spricht. RLS setzt in der Engine durch und deckt jeden Zugriffspfad ab, Admin-Werkzeuge und andere Dienste eingeschlossen. Der Konsens lautet mehrschichtige Verteidigung: in der Anwendung eingrenzen für die Klarheit, in der Datenbank durchsetzen für die Sicherheit.
Wie funktioniert RLS mit Connection Pooling?
Mit Vorsicht. Anwendungen mit Pool verbinden sich als ein einziger Datenbankbenutzer, deshalb stützen sich Policies auf eine Session-Variable pro Anfrage statt auf die Identität der Verbindung – gesetzt beim Entnehmen, gelesen von der Policy, zurückgesetzt bei der Rückgabe. Bei Poolern im Transaktionsmodus muss die Einstellung transaktionsweit gelten, sonst sickert der Kontext eines Tenants in die nächste Anfrage auf derselben Verbindung. Dieses Zusammenspiel ist der häufigste RLS-Fehler im Produktivbetrieb.
Wie testet man Policies für zeilenbasierte Sicherheit?
Schlüpfen Sie in jede Rolle und führen Sie alle vier Operationen aus – select, insert, update, delete – und prüfen Sie sowohl, was erscheint, als auch, was verweigert wird. Ergänzen Sie die beiden vergessenen Fälle: den ungesetzten Kontext (keine gesetzte Tenant-Variable muss keine Zeilen bedeuten, nicht alle) und die Umgehungsmatrix (Eigentümer, Superuser, Replikationspfade). RLS verdient sich Vertrauen durch feindselige Tests, nicht dadurch, dass die Policy sich gut liest.