Lühidalt
Allpool on RLS-poliitikad, mida enamik Supabase’i rakendusi vajab, ja iga ühte on käivitatud REST API kaudu külalisena, rea omanikuna ja teise sisselogitud kasutajana. Kolme tulemust tasub teada enne, kui midagi kopeerite: võõra rea update annab tagasi 200 ja tühja nimekirja, mitte vea; kahjutu välimusega insert-poliitika liikmetabelil lasi kasutajal liituda teise ettevõttega ja lugeda selle projekte; ja kui kirjutasin auth.uid() asemel (select auth.uid()), kiirenes üks päring 200 000 real 156 ms-st 7,4 ms-ni.
Olen Denys, full-stack- ja tehisintellekti insener, ning enamik rakendusi, mida ehitan või parandan, töötab Supabase’is. Poliitikate koodilõike on igal pool; mida nad teevad, kui neid tabab päris päring, näidatakse harva. Nii et käivitasin need. Need on Supabase RLS policy näited, millest ise alustan, koos igaühe tulemusega.
Kuidas testisin. Enamik päringuid käis läbi kohaliku Supabase’i stacki (CLI 2.109.1, Postgres 17.6, PostgREST 14.14, Auth 2.192.0) avaliku võtme ja kahe päris sisselogitud kasutajaga, A ja B. Juhud, mis HTTP-d ei vaja, näiteks süntaksivead ja ajamõõtmised, jooksid tavalisel kohalikul Postgres 17.10-l, kus Supabase’i rollid anon ja authenticated ning auth.uid() olid uuesti loodud. Kõik 24. oktoobril 2026, väljamõeldud ridadega.
Enne ühtegi poliitikat: kas tabelil on grant?
Poliitika otsustab, milliseid ridu roll näeb. Grant otsustab, kas roll jõuab tabelini üldse, ja praegustes Supabase’i projektides uued tabelid Data API grante enam automaatselt ei saa (30. oktoobrist 2026 igas projektis). Minu stackis vastas tabel, millel oli RLS sees, aga grant puudus, nii:
| Päring | Olek | Sisu |
|---|---|---|
Külaline GET /invoices |
401 |
permission denied for table invoices, vihjega GRANT SELECT ... TO anon |
Kasutaja A GET /invoices |
403 |
sama, vihjega rollile authenticated |
Seega iga tabeli puhul, mida rakendus brauserist loeb, on esimene samm:
alter table public.todos enable row level security;
grant select, insert, update, delete on public.todos to authenticated;
Andke ainult need käsud, mida brauser vajab, ja ainult rollile authenticated, kui külalised ei pea tabelit lugema.
Kuidas lasta kasutajatel näha ainult oma ridu?
Üks poliitika käsu kohta, igaüks piiratud sisselogitud kasutajatega:
create policy "read own" on public.todos
for select to authenticated using ((select auth.uid()) = user_id);
create policy "add own" on public.todos
for insert to authenticated with check ((select auth.uid()) = user_id);
create policy "edit own" on public.todos
for update to authenticated
using ((select auth.uid()) = user_id) with check ((select auth.uid()) = user_id);
create policy "delete own" on public.todos
for delete to authenticated using ((select auth.uid()) = user_id);
A-l on kaks rida, B-l üks. Mis tagasi tuli:
| Päringu tegija | Mida | Olek | Sisu |
|---|---|---|---|
| Külaline | loe kõik | 401 |
permission denied (rollil anon pole granti) |
| A | loe kõik | 200 |
A kaks rida |
| A | insert B-le kuuluv rida | 403 |
new row violates row-level security policy |
| A | update B rida | 200 |
[] |
| A | delete B rida | 200 |
[] |
| A | muuda oma rea omanikuks B | 403 |
new row violates row-level security policy |
Need kaks 200 [] rida tasub meelde jätta. RLS ei lükka tagasi update’i ega delete’i ridadele, mida te ei näe; ta filtreerib need välja ja null muudetud rida on edu. Kui teie rakendus peab “viga puudub” märgiks, et salvestamine õnnestus, ütleb see kasutajale, et muudatus töötas, kuigi midagi ei juhtunud. Küsige muudetud read tagasi (.select() supabase-js-is, Prefer: return=representation HTTP-ga) ja kontrollige, et üks tuli.
Miks vajab update-poliitika WITH CHECK-i?
using valib, milliseid olemasolevaid ridu kasutaja puudutada tohib; with check otsustab, milline need read pärast välja näevad. Võrdlesin sama update-poliitika kolme versiooni:
| Update-poliitika | A annab oma rea B-le |
|---|---|
using (uid = user_id) with check (uid = user_id) |
403, keelatud |
using (uid = user_id), ilma with check-ita |
keelatud (Postgres kasutab using-i ka checkina) |
using (uid = user_id) with check (true) |
200, rida kuulub nüüd B-le |
with check-i väljajätmine on turvalisem, kui paistab, sest Postgres kasutab tagavarana using-avaldist. Kasutaja saab rea teise konto alla viia with check (true) abil, mille mõned generaatorid kirjutavad “update’ide lubamiseks”. Kirjutage check täielikult välja.
Kuidas teha profiilid avalikuks, aga muudetavaks ainult omaniku poolt?
create policy "public read" on public.profiles
for select to anon, authenticated using (true);
create policy "own edit" on public.profiles
for update to authenticated
using ((select auth.uid()) = id) with check ((select auth.uid()) = id);
Tavalise Postgresi käivitusel: külaline luges mõlemad profiilid, külalise update muutis 0 rida ja A update profiles set display_name = 'x' ilma filtrita muutis täpselt 1 rea, A enda oma. using (true) sobib selectile andmetel, mis peavadki avalikud olema. Igal teisel tabelil on see sama mis RLS-i puudumine, ja ilma to-klauslita kirjutatud poliitika kehtib ka külalistele: minu külaline luges “privaatse” rea poliitikaga using (true), millel polnud to-d.
Kuidas jagada ridu tiimi sees?
Tavaline kuju: tabel memberships ja andmete poliitika, mis otsib sealt kasutaja tiimid.
create policy "members read" on public.projects
for select to authenticated
using (org_id in (select org_id from public.memberships
where user_id = (select auth.uid())));
See poliitika on korras. Leke oli selle kõrval. Andsin tabelile memberships poliitika, mida paljud õpetused soovitavad, “kasutajad võivad lisada oma liikmesuse”:
create policy "join any org" on public.memberships
for insert to authenticated with check (user_id = (select auth.uid()));
See kontrollib, kellele rida kuulub, mitte millisesse tiimi. A, ettevõtte 1 liige, saatis ühe päringu:
| A päring | Olek | Sisu |
|---|---|---|
| loe projektid | 200 |
A roadmap |
insert liikmesus {user_id: A, org_id: 2} |
201 |
loodud |
| loe projektid uuesti | 200 |
A roadmap, B payroll |
Lahendus on mitte anda tabelile memberships üldse insert-poliitikat, et brauser ei saaks sinna kirjutada (tavalisel Postgresil lükati sama insert siis tagasi veaga 42501), ja lisada liikmed kutsevoo kaudu, mis jookseb serveris või funktsioonis, mis kutset kontrollib. Kasutaja, kes kuulub kahte ettevõttesse, on järgmine lõks; kirjutasin sellest artiklis Supabase RLS: kuidas kahe org-i kasutaja andmeid lekitab.
Mis siis, kui poliitika peab lugema oma tabelit?
“Liikmed näevad oma meeskonnakaaslasi” tähendab poliitikat tabelil memberships, mis loeb tabelit memberships. Postgres keeldub päringu ajal:
ERROR 42P17: infinite recursion detected in policy for relation "members2"
Tõstke otsing funktsiooni, mis jookseb omaniku õigustes, skeemas, mida API ei avalda, fikseeritud search_path-iga:
create schema private;
create function private.user_org_ids() returns setof int
language sql stable security definer set search_path = ''
as $$ select org_id from public.members2 where user_id = (select auth.uid()) $$;
revoke all on function private.user_org_ids() from public;
grant usage on schema private to authenticated;
grant execute on function private.user_org_ids() to authenticated;
create policy "see teammates" on public.members2
for select to authenticated using (org_id in (select private.user_org_ids()));
Pärast seda nägi A ettevõtte 1 mõlemat liiget ja B nägi ainult iseennast. Hoidke funktsioon skeemast public väljas: kõik, mis seal on, on API kaudu kutsutav.
Kuidas nõuda tundlike ridade jaoks MFA-d?
Lisage tavalise poliitika peale piirav poliitika. Piiravad poliitikad ühendatakse and-iga, seega mõlemad peavad läbi minema:
create policy "mfa only" on public.payouts as restrictive
for select to authenticated using ((select auth.jwt() ->> 'aal') = 'aal2');
Sama kasutajaga luges aal1 seanss 0 väljamakset ja aal2 seanss 1.
Millistel tabelitel peaks RLS olema sees ja poliitikat mitte?
Tabelitel, mida puudutab ainult teie server. RLS sees ilma poliitikata keelab brauseri rollidele kõik: tavalisel Postgresil lugesid külaline ja A 0 rida ning A insert lükati tagasi veaga 42501. RLS-i väljalülitatuks jätmine on vastupidine. Tabel, millel on RLS väljas ja vaikimisi grandid, loetuna avaliku võtmega ilma sisselogimiseta:
| Külalise päring | Olek | Sisu |
|---|---|---|
GET /leads |
200 |
mõlemad read |
DELETE /leads?id=gt.0 |
200 |
mõlemad read, kustutatud |
Just sellest räägib Advisori hoiatus “RLS disabled in public”.
Kas üks poliitika võib katta mitut käsku?
Mitte nimekirjaga. for update, delete on süntaksiviga (42601: syntax error at or near ","), kuigi see esineb endiselt avaldatud koodilõikude kogudes. for all on kehtiv ja rakendab ühte using- ja ühte with check-avaldist kõigile käskudele, mida harva soovitakse. Ka insert-poliitika koos using-iga ebaõnnestub: only WITH CHECK expression allowed for INSERT.
Kas poliitikad aeglustavad päringuid?
Võivad küll. Poliitika avaldis jookseb iga rea kohta, mida Postgres kaalub. Mõõtsin select * from big kasutajana A 200 000 real, mille omanikud on 1 000 kasutajat, neist A-le kuulub 200 (7 käivituse mediaan, Postgres 17.10):
| Poliitika ja indeks | Mediaan |
|---|---|
auth.uid() = user_id, indeksita |
156,3 ms |
(select auth.uid()) = user_id, indeksita |
7,4 ms |
auth.uid() = user_id, indeks veerul user_id |
0,21 ms |
(select auth.uid()) = user_id, indeksiga |
0,05 ms |
Kutse mähkimine select-i sisse lubab Postgresil see arvutada üks kord päringu kohta, mitte üks kord rea kohta; ülejäänu teeb indeks. Supabase’i RLS-i jõudluse juhend toob oma 100 000 realises tabelis sama suurusjärgu muutuse (179 ms-st 9 ms-ni). Paljud blogipostitused tsiteerivad neid numbreid enda omadena; ülaltoodud on minu käivitusest.
Kust peaksid rollid tulema?
Väljalt app_metadata või teie enda tabelist, mitte kunagi väljalt user_metadata. Sisselogitud kasutaja saab oma user_metadata-t muuta käsuga supabase.auth.updateUser(), nii et poliitika, mis kontrollib auth.jwt() -> 'user_metadata' ->> 'role', lubab tal rolli endale anda. Registreerimistrigger, mis kopeerib rolli registreerimise metaandmetest, teeb sama; see juhtum on artiklis Supabase raw_user_meta_data: registreerimine ja Owner-roll.
Kuidas kontrollida oma poliitikaid?
- Tasuta Supabase’i turvakontroll loeb teie projekti ainult lugemisrežiimis ja märgib alati tõesed poliitikad, külaliste kirjutuspoliitikad, kasutaja metaandmetele toetuvad poliitikad ja tabelid, millel RLS on väljas, igaühele parandus-SQL-iga.
- Ükski skanner ei tea, millised read kellele kuuluvad. Selleks käivitage päringuid kahe kasutajana, nagu see artikkel teeb; täielik meetod rollidega rakenduse jaoks on artiklis kuidas Supabase’i RLS-i testida.
- Mida Supabase teie eest kaitseb ja mida jätab teile, on artiklis kas Supabase on turvaline?.
- Samad omaniku ja tiimi testid, käivitatud ka Appwrite’is, on artiklis Appwrite vs Supabase turvalisus: samad rünnakud mõlemale.
Piirangud. Üks kohalik stack ja üks sülearvuti, väljamõeldud andmed, 24. oktoober 2026. Ajad sõltuvad riistvarast ja tabeli kujust; suhted loevad rohkem kui millisekundid. Storage’i poliitikaid ja Realtime’i siin ei käsitleta.
Pole kindel, kas teie poliitikad peavad vastu?
Vaatan hea meelega koos teiega skeemi. Müügijututa.
Võtke ühendust