Corak RLS Supabase untuk Aplikasi SaaS Multi-Penyewa
Panduan enam corak RLS Supabase untuk multi-penyewa yang kami guna di JRV Systems bagi membina SaaS yang selamat, dari tuntutan JWT hingga ujian polisi automatik.
Apabila membina aplikasi Perisian-sebagai-Perkhidmatan (SaaS) untuk multi-penyewa, tiada apa yang lebih kritikal daripada pengasingan data. Pengguna dari Penyewa A tidak sepatutnya, dalam apa jua keadaan, boleh melihat data dari Penyewa B. Di JRV Systems, kami bergantung pada Row Level Security (RLS) PostgreSQL, satu ciri teras Supabase, untuk menguatkuasakan sempadan ini di peringkat pangkalan data. Ia adalah alat yang sangat berkuasa, yang jika digunakan dengan betul, menjadikan kebocoran data hampir mustahil.
Melalui beberapa projek, daripada sistem pengurusan klinik hingga platform e-dagang untuk perniagaan di Malaysia, kami telah memperhalusi pendekatan kami. Berikut adalah enam corak RLS Supabase untuk multi-penyewa yang penting dan telah kami laksanakan dalam sistem produksi.
1. Asas Utama: ID Penyewa melalui Tuntutan JWT
Ini adalah corak yang paling asas dan efisien untuk sistem multi-penyewa. Idea utamanya adalah untuk memasukkan tenant_id pengguna terus ke dalam JSON Web Token (JWT) mereka semasa log masuk. Token ini dihantar bersama setiap permintaan ke pangkalan data, menjadikan konteks penyewa sentiasa tersedia.
Bagaimana ia berfungsi:
- Apabila pengguna mendaftar masuk, fungsi khas atau trigger pangkalan data akan menambah
tenant_idmereka ke dalamapp_metadataJWT mereka. - Polisi RLS anda kemudiannya akan mengekstrak ID ini untuk menapis pertanyaan.
Supabase menyediakan fungsi pembantu untuk mengakses tuntutan JWT, menjadikan polisi lebih kemas dan mudah dibaca:
CREATE POLICY "Polisi Pengasingan Penyewa" ON public.invois FOR ALL USING (tenant_id = (auth.jwt() ->> 'app_metadata')::jsonb ->> 'tenant_id');
Polisi tunggal ini, yang diguna pakai pada setiap jadual yang mengandungi data penyewa, memastikan wujudnya dinding pemisah yang kukuh antara penyewa. Ia adalah lapisan keselamatan pertama yang kami laksanakan dalam mana-mana sistem multi-penyewa.
2. Pemilikan Baris untuk Kawalan Terperinci
Kadangkala, pengasingan di peringkat penyewa tidak mencukupi. Dalam satu penyewa yang sama, anda mungkin perlu mengehadkan akses kepada rekod tertentu berdasarkan siapa yang menciptanya. Contohnya, seorang doktor di sebuah klinik hanya patut melihat nota konsultasi miliknya sendiri, bukan nota doktor lain di klinik yang sama.
Corak ini mudah: tambah lajur user_id pada jadual yang menyimpan auth.uid() pencipta rekod tersebut.
Polisi ini menggabungkan semakan penyewa dengan semakan pengguna:
CREATE POLICY "Polisi Akses Pemilik" ON public.nota FOR ALL USING (tenant_id = dapatkan_id_penyewa_dari_tuntutan() AND user_id = auth.uid());
Pendekatan berlapis ini menyediakan keselamatan di peringkat penyewa dan juga khusus untuk pengguna, satu keperluan biasa dalam aplikasi kolaboratif seperti papan pemuka yang kami bina.
3. Akses Berasaskan Peranan dengan Jadual Ahli
Aplikasi yang kompleks memerlukan tahap kebenaran yang berbeza. Seorang 'admin' boleh melihat semua invois untuk satu penyewa, manakala seorang 'pelihat' hanya boleh membacanya. Semakan user_id yang mudah tidak lagi memadai.
Penyelesaian kami adalah jadual ahli yang khusus untuk memetakan pengguna kepada penyewa dengan peranan tertentu:
user_id(UUID, kunci asing keauth.users)tenant_id(UUID, kunci asing kepenyewa)peranan(TEXT, cth., 'admin', 'ahli')
Polisi RLS tidak boleh membuat pertanyaan terus ke jadual ini dengan cekap dan selamat tanpa bantuan fungsi. Kami mencipta fungsi SECURITY DEFINER yang menyemak sama ada pengguna semasa mempunyai peranan yang diperlukan untuk penyewa tertentu. Fungsi ini berjalan dengan keistimewaan pemilik fungsi, membolehkannya memintas RLS untuk menyemak jadual ahli.
CREATE POLICY "Akses Bacaan Admin" ON public.invois FOR SELECT USING (ialah_ahli_penyewa(tenant_id, 'admin'));
Corak ini memastikan polisi kami sentiasa kemas dan memusatkan logik kebenaran yang kompleks ke dalam beberapa fungsi yang boleh diguna semula.
4. Mengawal Akses kepada Rekod 'Soft-Delete'
Kami lebih gemar melakukan 'soft-delete' (menanda rekod dengan cap masa deleted_at) berbanding pemadaman kekal. Ini memelihara data untuk tujuan audit atau pemulihan. Walau bagaimanapun, secara lalai, pengguna tidak sepatutnya melihat rekod yang diarkibkan ini dalam aliran kerja harian mereka.
RLS adalah alat yang sempurna untuk menguruskan ini. Polisi standard untuk sesebuah jadual boleh dikemas kini untuk menyembunyikan item yang telah di-'soft-delete':
USING (tenant_id = dapatkan_id_penyewa_dari_tuntutan() AND deleted_at IS NULL)
Untuk pentadbir atau untuk ciri 'tong sampah', polisi yang berasingan dan lebih permisif boleh diwujudkan. Polisi ini akan membenarkan pengguna dengan peranan 'admin' untuk melihat rekod di mana deleted_at BUKAN null, memberi mereka keupayaan untuk melihat atau memulihkan data.
5. Melindungi Jadual Jejak Audit
Bagi kebanyakan sistem yang kami bina, terutamanya SaaS klinik kami, mengekalkan jejak audit yang selamat adalah satu keperluan kawal selia. Jejak ini merekodkan siapa melakukan apa dan bila. Data ini amat sensitif.
Jadual log_audit mesti dilindungi oleh RLS. Walaupun operasi INSERT biasanya dikendalikan oleh trigger pangkalan data yang berjalan dengan keistimewaan tinggi, akses SELECT mesti dikunci dengan ketat.
Polisi yang mudah memastikan pengguna hanya boleh melihat peristiwa audit yang berkaitan dengan penyewa mereka sendiri:
CREATE POLICY "Pengasingan Penyewa Log Audit" ON public.log_audit FOR SELECT USING (tenant_id = dapatkan_id_penyewa_dari_tuntutan());
Peraturan tambahan boleh ditambah untuk mengehadkan akses hanya kepada admin penyewa, menghalang pengguna biasa daripada melihat log aktiviti penuh rakan sekerja mereka.
6. Cara Kami Menguji Polisi RLS
Polisi RLS adalah kod aplikasi. Ia boleh mempunyai pepijat, dan pepijat tersebut boleh membawa kepada kebocoran data yang besar. Ia mesti diuji.
Kami menggunakan Supabase CLI dan kerangka ujian pg_prove untuk mengautomasikan ujian RLS kami. Proses kami adalah seperti berikut:
- Data Permulaan (Seed): Kami mencipta fail
supabase/seed.sqlyang mengisi pangkalan data ujian dengan beberapa penyewa, pengguna dengan peranan berbeza, dan data sampel untuk setiap satunya. - Skrip Ujian: Kami menulis fail ujian SQL. Setiap ujian menyamar sebagai pengguna tertentu dan menjalankan pertanyaan.
- Penyamaran: Kuncinya adalah untuk menetapkan konteks sesi bagi meniru pengguna sebenar. Kami menggunakan
SET LOCAL "request.jwt.claims" = '...';untuk mensimulasikan JWT dengantenant_iddanuser_idyang spesifik. - Penegasan (Assertions): Skrip ujian kemudian membuat pertanyaan ke pangkalan data dan menegaskan bahawa ia hanya menerima data yang sepatutnya dilihat oleh pengguna tersebut. Jika pertanyaan untuk data Penyewa B mengembalikan hasil semasa menyamar sebagai pengguna dari Penyewa A, ujian itu akan gagal.
Ujian automatik ini memberi kami keyakinan bahawa corak RLS multi-penyewa Supabase kami dilaksanakan dengan betul sebelum kami menghantar sebarang kod.