---
title: "Supabase RLS policy: pavyzdžiai, išbandyti per API"
description: "Paruoštos Supabase RLS policy savoms eilutėms, viešiems profiliams, komandoms ir MFA. Kiekviena išbandyta kaip svečias, savininkas ir kitas vartotojas."
url: "https://madsalt.tech/lt/blog/supabase-rls-policy-examples/"
author: "Denys Zaichenko"
author_url: "https://madsalt.tech/lt/about/"
language: "lt"
published: 2026-10-27
---

# Supabase RLS policy: pavyzdžiai, išbandyti per API

TL;DR

Žemiau pateiktos RLS politikos, kurių reikia daugumai Supabase programų. Kiekvieną paleidau per REST API kaip svečias, kaip eilutės savininkas ir kaip kitas prisijungęs vartotojas. Trys rezultatai verti dėmesio prieš ką nors kopijuojant: svetimos eilutės update grąžina `200` su tuščiu sąrašu, o ne klaidą; nekaltai atrodanti insert politika narystės lentelėje leido vartotojui prisijungti prie kitos įmonės ir skaityti jos projektus; o užrašius `(select auth.uid())` vietoj `auth.uid()`, viena užklausa 200 000 eilučių lentelėje pagreitėjo nuo 156 ms iki 7,4 ms.

Esu Denys, full-stack ir AI inžinierius, ir dauguma programų, kurias kuriu ar taisau, veikia ant Supabase. Pavyzdžių, kaip parašyti Supabase RLS policy, yra visur; ką jos daro, kai pasiekia tikra užklausa, parodoma retai. Todėl jas paleidau.

**Kaip testavau.** Dauguma užklausų ėjo per vietinį Supabase stack (CLI 2.109.1, Postgres 17.6, PostgREST 14.14, Auth 2.192.0) su publishable key ir dviem tikrais prisijungusiais vartotojais, A ir B. Atvejai, kuriems HTTP nereikia, pavyzdžiui, sintaksės klaidos ir laikai, veikė ant paprasto vietinio Postgres 17.10 su Supabase rolėmis `anon` ir `authenticated` bei iš naujo sukurta funkcija `auth.uid()`. Viskas 2026 m. spalio 24 d., su išgalvotomis eilutėmis.

## Prieš bet kokią politiką: ar lentelė turi teisę (grant)?

Politika nusprendžia, kurias eilutes rolė mato. Teisė (grant) nusprendžia, ar rolė apskritai pasiekia lentelę, o dabartiniuose Supabase projektuose naujos lentelės Data API teisių automatiškai nebegauna (nuo 2026 m. spalio 30 d. visuose projektuose). Mano stack lentelė su įjungtu RLS ir be teisės atsakė taip:

| Užklausa                     | Statusas | Turinys                                                                        |
| ---------------------------- | -------- | ------------------------------------------------------------------------------ |
| Svečias `GET /invoices`      | `401`    | `permission denied for table invoices`, su užuomina `GRANT SELECT ... TO anon` |
| Vartotojas A `GET /invoices` | `403`    | tas pats, su užuomina `authenticated`                                          |

Taigi pirmas žingsnis kiekvienai lentelei, kurią programa skaito iš naršyklės:

```sql
alter table public.todos enable row level security;
grant select, insert, update, delete on public.todos to authenticated;
```

Suteikite tik tas komandas, kurių reikia naršyklei, ir tik rolei `authenticated`, nebent lentelę turi skaityti ir svečiai.

## Kaip parašyti Supabase RLS policy, kad vartotojai matytų tik savo eilutes?

Viena politika kiekvienai komandai, kiekviena apribota prisijungusiais vartotojais:

```sql
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 turi dvi eilutes, B vieną. Kas grįžo:

| Užklausos autorius | Ką daro                             | Statusas | Turinys                                      |
| ------------------ | ----------------------------------- | -------- | -------------------------------------------- |
| Svečias            | skaityti viską                      | `401`    | permission denied (`anon` neturi teisės)     |
| A                  | skaityti viską                      | `200`    | 2 vartotojo A eilutės                        |
| A                  | įterpti eilutę, priklausančią B     | `403`    | `new row violates row-level security policy` |
| A                  | atnaujinti vartotojo B eilutę       | `200`    | `[]`                                         |
| A                  | ištrinti vartotojo B eilutę         | `200`    | `[]`                                         |
| A                  | pakeisti savo eilutės savininką į B | `403`    | `new row violates row-level security policy` |

Du `200 []` įrašai yra tie, kuriuos verta įsiminti. RLS neatmeta update ar delete eilučių, kurių nematote; jis jas išfiltruoja, o nulis pakeistų eilučių yra sėkmė. Jei jūsų programa „nėra klaidos“ laiko „išsaugota“, ji pasakys vartotojui, kad pakeitimas pavyko, nors nieko neįvyko. Paprašykite pakeistų eilučių (`.select()` supabase-js, `Prefer: return=representation` per HTTP) ir patikrinkite, ar viena grįžo.

## Kodėl update politikai reikia WITH CHECK?

`using` parenka, kurių esamų eilučių vartotojas gali liesti; `with check` nusprendžia, kaip tos eilutės gali atrodyti po to. Palyginau tris tos pačios update politikos versijas:

| Update politika                                    | A atiduoda savo eilutę vartotojui B             |
| -------------------------------------------------- | ----------------------------------------------- |
| `using (uid = user_id) with check (uid = user_id)` | `403`, atmesta                                  |
| `using (uid = user_id)`, be `with check`           | atmesta (Postgres naudoja `using` kaip patikrą) |
| `using (uid = user_id) with check (true)`          | `200`, eilutė dabar priklauso B                 |

Praleisti `with check` saugiau, nei atrodo, nes Postgres pasitelkia `using` išraišką. `with check (true)`, kurį kai kurie generatoriai rašo, kad „leistų update“, būtent ir leidžia vartotojui perkelti eilutę į svetimą paskyrą. Parašykite patikrą pilnai.

## Kaip padaryti profilius viešus, bet redaguojamus tik savininko?

```sql
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);
```

Paleidimas ant paprasto Postgres: svečias perskaitė abu profilius, svečio update pakeitė 0 eilučių, o A `update profiles set display_name = 'x'` be filtro pakeitė lygiai 1 eilutę, paties A. `using (true)` tinka select duomenims, kurie ir turi būti vieši. Bet kurioje kitoje lentelėje tai tas pats, kas jokio RLS, o politika, parašyta be `to` sąlygos, galioja ir svečiams: mano svečias perskaitė „privačią“ eilutę per `using (true)` politiką, kuri neturėjo `to`.

## Kaip dalytis eilutėmis komandos viduje?

Įprasta forma: lentelė `memberships` ir politika duomenyse, kuri joje ieško vartotojo komandų.

```sql
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())));
```

Ta politika tvarkinga. Nutekėjimas buvo šalia. Lentelei `memberships` daviau politiką, kurią siūlo daug pamokų, „vartotojai gali įterpti savo narystę“:

```sql
create policy "join any org" on public.memberships
  for insert to authenticated with check (user_id = (select auth.uid()));
```

Ji tikrina, kam eilutė skirta, o ne kuriai komandai. A, 1 įmonės narys, išsiuntė vieną užklausą:

![Viena insert politika lentelėje memberships. 1 įmonės vartotojas A per REST API. Politika: user_id = auth.uid(): Vartotojas A → jungiasi prie 2 įmonės → 201 Created → skaito B algas; Be insert politikos: Vartotojas A → jungiasi prie 2 įmonės → atmesta → tik savi projektai.](https://madsalt.tech/charts/lt/supabase-rls-policy-examples-join.svg)

*1 pav. Mano testas vietiniame Supabase stack (CLI 2.109.1), 2026 m. spalio 24 d.*

| A užklausa                                | Statusas | Turinys                  |
| ----------------------------------------- | -------- | ------------------------ |
| skaityti projektus                        | `200`    | `A roadmap`              |
| įterpti narystę `{user_id: A, org_id: 2}` | `201`    | sukurta                  |
| vėl skaityti projektus                    | `200`    | `A roadmap`, `B payroll` |

Sprendimas: `memberships` nedaryti jokios insert politikos, kad naršyklė negalėtų į ją rašyti (ant Postgres paleidimo tas pats insert tada buvo atmestas su `42501`), o narius pridėti per kvietimų srautą, veikiantį serveryje arba funkcijoje, tikrinančioje kvietimą. Vartotojas, priklausantis dviem įmonėms, yra dar vieni spąstai; apie tai rašiau straipsnyje [Supabase RLS: kodėl dviejų org. narys mato svetimus duomenis](https://madsalt.tech/lt/notes/supabase-rls-user-in-two-organizations/).

## Ką daryti, jei politikai reikia skaityti savo lentelę?

„Nariai mato savo komandos draugus“ reiškia politiką lentelėje `memberships`, kuri skaito `memberships`. Postgres ją atmeta vykdant užklausą:

```text
ERROR 42P17: infinite recursion detected in policy for relation "members2"
```

Paieškos veiksmą perkelkite į funkciją, kuri veikia kaip jos savininkas, schemoje, kurios API neatveria, su fiksuotu `search_path`:

```sql
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()));
```

Po to A matė abu 1 įmonės narius, o B matė tik save. Funkciją laikykite ne `public`: viską, kas ten, galima iškviesti per API.

## Kaip reikalauti MFA jautrioms eilutėms?

Prie įprastos politikos pridėkite ribojančią (restrictive) politiką. Ribojančios politikos jungiamos per `and`, todėl turi praeiti abi:

```sql
create policy "mfa only" on public.payouts as restrictive
  for select to authenticated using ((select auth.jwt() ->> 'aal') = 'aal2');
```

Su tuo pačiu vartotoju `aal1` sesija perskaitė 0 išmokų, o `aal2` sesija perskaitė 1.

## Kurios lentelės turi turėti įjungtą RLS ir nė vienos politikos?

Lentelės, kurias liečia tik jūsų serveris. Įjungtas RLS be politikos viską uždraudžia naršyklės rolėms: ant Postgres paleidimo svečias ir A perskaitė 0 eilučių, o A insert buvo atmestas su `42501`. Palikti RLS išjungtą yra priešingybė. Lentelė su išjungtu RLS ir numatytosiomis teisėmis, skaitoma su publishable key ir be prisijungimo:

| Svečio užklausa         | Statusas | Turinys                |
| ----------------------- | -------- | ---------------------- |
| `GET /leads`            | `200`    | abi eilutės            |
| `DELETE /leads?id=gt.0` | `200`    | abi eilutės, ištrintos |

Būtent apie tai ir yra Advisor įspėjimas „RLS disabled in public“.

## Ar viena politika gali apimti kelias komandas?

Ne sąrašu. `for update, delete` yra sintaksės klaida (`42601: syntax error at or near ","`), nors vis dar pasitaiko paskelbtose pavyzdžių rinkiniuose. `for all` galioja ir taiko vieną `using` bei vieną `with check` kiekvienai komandai, ko retai norite. Insert politika su `using` taip pat nepavyksta: `only WITH CHECK expression allowed for INSERT`.

## Ar politikos lėtina užklausas?

Gali. Politikos išraiška vykdoma kiekvienai eilutei, kurią svarsto Postgres. Išmatavau `select * from big` kaip vartotojas A, 200 000 eilučių, priklausančių 1 000 vartotojų, A turėjo 200 (7 paleidimų mediana, Postgres 17.10):

![Viena tik savininko politika, keturi užrašymai. Milisekundės 200 savų eilučių iš 200 000 perskaityti. 7 paleidimų mediana. auth.uid() = user_id, be indekso: 156,30; (select auth.uid()), be indekso: 7,40; auth.uid() = user_id, su indeksu: 0,21; (select auth.uid()), su indeksu: 0,05.](https://madsalt.tech/charts/lt/supabase-rls-policy-examples.svg)

*2 pav. Mano testas vietiniame Postgres 17.10 su atkurtomis Supabase rolėmis, 2026 m. spalio 24 d.*

| Politika ir indeksas                            | Laiko mediana |
| ----------------------------------------------- | ------------- |
| `auth.uid() = user_id`, be indekso              | 156,3 ms      |
| `(select auth.uid()) = user_id`, be indekso     | 7,4 ms        |
| `auth.uid() = user_id`, indeksas ties `user_id` | 0,21 ms       |
| `(select auth.uid()) = user_id`, indeksas       | 0,05 ms       |

Apvyniojus iškvietimą `select`, Postgres apskaičiuoja reikšmę vieną kartą per užklausą, o ne vieną kartą per eilutę; likusį darbą padaro indeksas. Supabase [RLS našumo vadovas](https://supabase.com/docs/guides/troubleshooting/rls-performance-and-best-practices-Z5Jjwv) nurodo tokio paties dydžio pokytį savo 100 000 eilučių lentelėje (nuo 179 ms iki 9 ms). Daug tinklaraščių tuos skaičius cituoja kaip savo; aukščiau pateikti yra iš mano paleidimo.

## Iš kur turi būti imamos rolės?

Iš `app_metadata` arba savo lentelės, niekada iš `user_metadata`. Prisijungęs vartotojas gali pakeisti savo `user_metadata` per `supabase.auth.updateUser()`, todėl politika, tikrinanti `auth.jwt() -> 'user_metadata' ->> 'role'`, leidžia jam pačiam suteikti sau rolę. Tą patį daro registracijos trigeris, kuris kopijuoja rolę iš registracijos metaduomenų; tas atvejis aprašytas straipsnyje [Supabase raw_user_meta_data: kaip registracija tapo Owner](https://madsalt.tech/lt/notes/supabase-raw-user-meta-data-role/).

## Kaip patikrinti savo politikas?

- Nemokamas [Supabase saugumo patikrinimas](https://madsalt.tech/lt/tools/supabase-security-check/) skaito jūsų projektą tik skaitymo režimu ir pažymi visada teisingas politikas, rašymo politikas svečiams, politikas, kurios pasitiki vartotojo metaduomenimis, ir lenteles su išjungtu RLS, kiekvienai su taisymo SQL.
- Joks skeneris nežino, kurios eilutės kam priklauso. Tam siųskite užklausas kaip du vartotojai, kaip šiame straipsnyje; visas metodas programai su rolėmis aprašytas straipsnyje [Kaip testuoti Supabase RLS: 30 iš 87 leidimų nieko nedarė](https://madsalt.tech/lt/blog/how-to-test-supabase-rls/).
- Ką Supabase užtikrina už jus, o ką palieka jums, aprašyta straipsnyje [Ar Supabase saugus? Ką jis apima, o ką turite jūs](https://madsalt.tech/lt/blog/is-supabase-secure/).
- Tie patys savininko ir komandos testai, paleisti ir ant Appwrite, aprašyti straipsnyje [Appwrite vs Supabase sauga: tos pačios atakos abiem](https://madsalt.tech/lt/blog/appwrite-vs-supabase/).

**Apribojimai.** Vienas vietinis stack ir vienas nešiojamasis kompiuteris, išgalvoti duomenys, 2026 m. spalio 24 d. Laikai priklauso nuo aparatūros ir lentelės formos; svarbiau santykiai nei milisekundės. Storage politikos ir Realtime čia neapimti.

Nesate tikri, ar jūsų politikos laikosi?

Mielai kartu pažiūrėsiu į schemą. Be pardavimų kalbų.

[Susisiekti](https://madsalt.tech/lt/#contact)
