Corak RLS Supabase Praktikal untuk SaaS Pelbagai Penyewa
Panduan untuk enam corak keselamatan peringkat baris (RLS) Supabase bagi membina SaaS pelbagai penyewa yang selamat. Kami bincangkan pengasingan penyewa, peranan, dan cara kami menguji polisi di JRV Systems.
Di JRV Systems, kami membina aplikasi SaaS pelbagai penyewa (multi-tenant) untuk perniagaan di Malaysia, daripada sistem pengurusan klinik sehinggalah platform pengebilan automatik. Satu keperluan mutlak untuk sistem-sistem ini ialah pengasingan data: data seorang pelanggan tidak boleh dilihat oleh pelanggan lain.
Supabase, dengan pangkalan data PostgreSQL bersepadunya, menawarkan alat yang hebat untuk tujuan ini yang dipanggil Row Level Security (RLS). RLS membolehkan anda menulis peraturan pada peringkat pangkalan data untuk menapis baris mana yang boleh dilihat atau diubah suai oleh pengguna. Ia umpama meletakkan firewall pada setiap baris data anda. Apabila dilaksanakan dengan betul, ia menyediakan asas keselamatan yang kukuh. Berikut adalah enam corak RLS Supabase praktikal untuk aplikasi pelbagai penyewa yang telah kami gunakan dalam projek kami.
Corak Teras RLS Supabase untuk Pelbagai Penyewa
Asas kepada mana-mana polisi RLS dalam Supabase ialah pengesahan (authentication). Setiap permintaan terikat kepada JSON Web Token (JWT) yang mengenal pasti pengguna. Kita boleh mengakses maklumat pengguna ini di dalam polisi kita menggunakan fungsi seperti auth.uid() untuk mendapatkan ID unik mereka.
Untuk aplikasi pelbagai penyewa, kami melanjutkannya dengan menambah tuntutan (claim) khas pada JWT. Contohnya, tuntutan tenant_id memberitahu pangkalan data organisasi mana yang diwakili oleh pengguna. Maklumat tunggal ini menjadi tunjang kepada kebanyakan polisi keselamatan kami.
Corak 1: Pengasingan Penyewa Melalui Tuntutan JWT
Corak paling asas untuk mana-mana SaaS ialah mengasingkan data mengikut penyewa. Setiap baris dalam jadual kritikal (seperti invoices, patients, atau projects) mesti mempunyai lajur tenant_id. Polisi RLS kemudiannya hanya perlu menyemak sama ada tenant_id dalam baris tersebut sepadan dengan tenant_id dalam JWT pengguna.
Ekspresi polisi tersebut kelihatan seperti ini:
(current_setting('request.jwt.claims', true)::jsonb ->> 'tenant_id')::uuid = tenant_id
Satu baris ini, apabila diguna pakai pada setiap jadual yang relevan, memastikan pengguna dari Klinik A tidak akan dapat mengakses data milik Klinik B secara tidak sengaja. Inilah polisi pertama yang kami tulis untuk setiap projek pelbagai penyewa yang baru.
Corak 2: Pemilikan Baris untuk Data Khusus Pengguna
Di dalam sesebuah penyewa, ada data yang dimiliki oleh pengguna tertentu. Contohnya seperti kunci API peribadi, nota peribadi, atau draf dokumen. Untuk kes sebegini, kami menggunakan model pemilikan yang mudah.
Jadual tersebut akan mempunyai lajur user_id atau created_by yang menyimpan ID pengguna yang mencipta baris itu. Polisinya amat terus:
auth.uid() = user_id
Kami sering menggabungkan corak ini dengan pengasingan penyewa. Seorang pengguna hanya boleh melihat dokumennya sendiri di dalam penyewanya sendiri. Polisi gabungan akan menjadi:
((current_setting('request.jwt.claims', true)::jsonb ->> 'tenant_id')::uuid = tenant_id) AND (auth.uid() = user_id)
Corak 3: Akses Berasaskan Peranan Melalui Jadual Penghubung
Aplikasi dunia sebenar memerlukan kebenaran yang lebih terperinci. Seorang pengurus perlu melihat semua invois syarikatnya, tetapi staf biasa hanya patut melihat invois yang mereka cipta. Ini dipanggil kawalan akses berasaskan peranan (RBAC).
Kami melaksanakannya menggunakan jadual penghubung (junction table), selalunya dinamakan memberships atau roles. Jadual ini menghubungkan pengguna dengan penyewa dan memberikan mereka peranan (cth: 'admin', 'member', 'viewer').
- Lajur jadual
memberships:id,user_id,tenant_id,role(teks)
Polisi RLS pada jadual invoices kemudiannya akan menyemak jadual memberships ini untuk melihat sama ada pengguna semasa mempunyai peranan yang diperlukan untuk penyewa invois tersebut.
Untuk seorang admin yang boleh melihat semua invois dalam penyewanya:
EXISTS (SELECT 1 FROM memberships WHERE memberships.user_id = auth.uid() AND memberships.tenant_id = invoices.tenant_id AND memberships.role = 'admin')
Corak ini sangat penting untuk sistem klinik yang kami bina, di mana akses kepada data pesakit mesti dikawal dengan ketat berdasarkan sama ada pengguna itu seorang doktor, jururawat, atau pentadbir.
Corak 4: Polisi Lanjutan untuk Operasi dan Status
RLS bukan hanya untuk melihat data (SELECT). Ia boleh mengawal operasi INSERT, UPDATE, dan DELETE, selalunya berdasarkan status data itu sendiri.
- Padaman Lembut (
Soft Deletes): Apabila pengguna "memadam" rekod, kami selalunya hanya menandakannya dengan cap masa dalam lajurdeleted_at. Polisi RLS boleh menyembunyikan rekod ini daripada pengguna biasa dengan menambahdeleted_at IS NULLpada klausaUSING. Polisi yang berasingan mungkin membenarkan peranan 'admin' untuk melihat semua rekod, termasuk yang telah dipadam secara lembut, untuk tujuan pengauditan. - Jejak Audit Kekal: Untuk jadual
audit_logs, kami mahu memastikan rekod hanya boleh ditambah (append-only). Ia boleh dicipta tetapi tidak boleh diubah atau dipadam. Kita boleh mencapainya dengan polisi berasingan:FOR INSERT: Polisi yang membenarkan pengguna menambah log.FOR UPDATE: Polisi yang hanya menggunakanWITH CHECK (false)untuk menafikan semua kemas kini.FOR DELETE: Polisi yang menggunakanUSING (false)untuk menafikan semua pemadaman.
Cara Kami Menguji Polisi RLS dengan Yakin
Polisi keselamatan yang tidak diuji adalah satu liabiliti. Kesilapan kecil dalam logik SQL anda boleh mendedahkan semua data pelanggan anda. Di JRV Systems, kami menjadikan ujian RLS sebagai keutamaan sebelum sebarang deployment.
Alat utama kami ialah fail supabase/seed.sql. Kami menggunakannya untuk mencipta keadaan pangkalan data yang boleh dijangka untuk tujuan ujian:
- Cipta beberapa penyewa (cth: 'Syarikat A', 'Syarikat B').
- Cipta pengguna dengan peranan berbeza dalam setiap penyewa (cth:
admin_A,member_A,admin_B). - Isi jadual dengan data milik setiap penyewa.
Dengan data seed ini, suit ujian automatik kami boleh menggunakan client library Supabase untuk menyamar sebagai setiap pengguna. Kami log masuk sebagai member_A dan pastikan kami hanya boleh melihat data untuk Syarikat A, dan tidak boleh melakukan tindakan admin. Kemudian, kami log masuk sebagai admin_B dan menjalankan semakan yang serupa untuk Syarikat B. Proses ini mengesahkan bahawa polisi pengasingan penyewa dan berasaskan peranan kami berfungsi seperti yang dirancang, memberikan kami dan pelanggan kami keyakinan terhadap keselamatan sistem.