Pola RLS Supabase Multi-Penyewa untuk Aplikasi SaaS Malaysia
Terokai enam pola RLS Supabase multi-penyewa sedia-produksi yang kami guna di JRV Systems untuk melindungi data SaaS. Pelajari pengasingan penyewa, pemilikan dan ujian.
Mengapa RLS Penting untuk SaaS Multi-Penyewa
Apabila membina aplikasi Perisian-sebagai-Perkhidmatan (SaaS) multi-penyewa, pengasingan data bukanlah satu pilihan—ia adalah asas kepercayaan. Pelanggan anda, sama ada klinik yang menggunakan sistem pengurusan atau perniagaan yang menggunakan perisian pengebilan kami, mesti yakin sepenuhnya bahawa data mereka tidak boleh diakses oleh penyewa lain. Walaupun anda boleh membina semakan ini dalam kod aplikasi, pendekatan ini rapuh. Satu pepijat kecil boleh mendedahkan maklumat sensitif.
Di sinilah Keselamatan Peringkat Baris (RLS) PostgreSQL, ciri teras Supabase, menjadi sangat penting. RLS membawa keselamatan turun ke peringkat pangkalan data. Ia memastikan tidak kira bagaimana pengguna membuat pertanyaan data—melalui API anda, sambungan terus, atau logik aplikasi yang silap—mereka hanya boleh melihat baris yang dibenarkan secara eksplisit. Artikel ini memperincikan enam pola RLS Supabase multi-penyewa praktikal yang telah kami laksanakan dalam projek untuk klien kami di Malaysia.
Pola 1: Pengasingan Penyewa melalui Tuntutan JWT
Pola ini adalah yang paling asas untuk mana-mana aplikasi multi-penyewa. Idea utamanya adalah untuk memasukkan tenant_id terus ke dalam Token Web JSON (JWT) pengguna apabila mereka log masuk. Tuntutan ini menjadi fakta yang tidak boleh disangkal tentang sesi pengguna tersebut.
Setiap jadual yang mengandungi data khusus penyewa mesti mempunyai lajur tenant_id. Polisi RLS kemudiannya hanya menyemak sama ada tenant_id dalam baris tersebut sepadan dengan tenant_id daripada JWT pengguna.
Untuk jadual seperti invois, polisinya akan kelihatan seperti ini:
CREATE POLICY "Benarkan akses berdasarkan penyewa" ON invois FOR ALL USING (tenant_id = (auth.jwt() ->> 'app_metadata')::uuid);
Kami menyimpan tenant_id dalam bahagian app_metadata JWT, yang merupakan tempat selamat untuk data yang tidak dikawal oleh pengguna. Polisi tunggal ini, yang diguna pakai di semua jadual yang berkaitan, membentuk perimeter yang kukuh, memastikan Klinik A tidak akan dapat melihat invois milik Klinik B.
Pola 2: Pemilikan Baris oleh ID Pengguna
Walaupun pengasingan penyewa adalah penting, anda sering memerlukan kawalan yang lebih terperinci di dalam satu penyewa. Sebagai contoh, seorang doktor hanya patut melihat nota pesakitnya sendiri, walaupun mereka berada di klinik (penyewa) yang sama dengan doktor lain.
Ini dicapai dengan menambah lajur user_id pada jadual yang berkaitan. Polisi kemudiannya menyemak sama ada ID pengguna yang sedang disahkan sepadan dengan yang ada di dalam baris.
Untuk jadual nota_pesakit, anda akan menggabungkan ini dengan semakan penyewa:
CREATE POLICY "Pengguna boleh akses nota sendiri dalam penyewa mereka" ON nota_pesakit FOR ALL USING (tenant_id = (auth.jwt() ->> 'app_metadata')::uuid AND user_id = auth.uid());
Pendekatan berlapis ini sangat berkesan. Semakan pertama mengesahkan pengguna berada di klinik yang betul, dan semakan kedua mengesahkan mereka adalah pemilik rekod tersebut.
Pola 3: Kebenaran Kompleks dengan Graf Peranan
Aplikasi dunia sebenar memerlukan lebih daripada sekadar pemilik dan penyewa. Anda memerlukan peranan seperti 'admin', 'doktor', atau 'staf_bil'. Seorang pentadbir klinik, misalnya, mungkin perlu melihat semua invois untuk klinik mereka, bukan hanya yang mereka cipta. Di sinilah graf peranan digunakan.
Kami biasanya melaksanakannya dengan beberapa jadual:
profiles: Menyimpan maklumat pengguna, dipautkan keauth.users.roles: Jadual ringkas dengan nama peranan (cth., 'admin', 'ahli').user_roles: Jadual pautan yang menyambungkanuser_idkerole_iddalamtenant_idtertentu.
Oleh kerana polisi RLS tidak dapat melakukan join yang kompleks secara langsung dengan cekap, kami menggunakan fungsi SECURITY DEFINER PostgreSQL. Fungsi ini boleh berjalan dengan keistimewaan yang lebih tinggi untuk menyemak peranan pengguna dan mengembalikan nilai benar/salah.
CREATE OR REPLACE FUNCTION semak_peranan_pengguna(peranan_disemak text) RETURNS boolean AS $$
BEGIN
RETURN EXISTS (
SELECT 1 FROM user_roles ur
JOIN roles r ON ur.role_id = r.id
WHERE ur.user_id = auth.uid()
AND ur.tenant_id = (auth.jwt() ->> 'app_metadata')::uuid
AND r.name = peranan_disemak
);
END;
$$ LANGUAGE plpgsql SECURITY DEFINER;
Kini, polisi admin untuk jadual invois menjadi lebih kemas dan mudah dibaca:
CREATE POLICY "Admin boleh lihat semua invois penyewa" ON invois FOR SELECT USING (semak_peranan_pengguna('admin'));
Pola 4: Menyekat Akses kepada Rekod 'Soft-Delete'
Kami jarang memadam data secara kekal. Sebaliknya, kami menggunakan corak "padam lembut" (soft-delete) dengan menambah lajur cap masa deleted_at. Ini mengekalkan data untuk tujuan audit atau pemulihan. Walau bagaimanapun, pengguna biasa tidak sepatutnya melihat rekod yang telah dipadam lembut ini.
RLS adalah sempurna untuk menguatkuasakan peraturan ini di peringkat pangkalan data. Polisi pengguna standard termasuk semakan untuk memastikan deleted_at adalah NULL.
CREATE POLICY "Pengguna boleh lihat rekod yang tidak dipadam" ON invois FOR SELECT USING (deleted_at IS NULL AND tenant_id = ...);
Seorang pentadbir, bagaimanapun, mungkin perlu melihat atau memulihkan rekod ini. Anda boleh mencipta polisi yang berasingan dan lebih permisif hanya untuk mereka:
CREATE POLICY "Admin boleh lihat semua rekod, termasuk yang dipadam" ON invois FOR SELECT USING (semak_peranan_pengguna('admin'));
PostgreSQL menggunakan polisi dengan syarat OR, jadi jika pengguna adalah admin, polisi kedua akan memberikannya akses walaupun yang pertama tidak.
Pola 5: Melindungi Jejak Audit
Untuk pematuhan, terutamanya dalam sistem seperti pengurusan klinik atau pengebilan, jejak audit yang tidak boleh diubah adalah perlu. Ini adalah jadual yang mencatat setiap tindakan penting. Polisi RLS untuk jadual log_audit adalah unik:
- INSERT: Mana-mana pengguna yang disahkan sepatutnya boleh menambah log. Polisinya ringkas:
CREATE POLICY "Benarkan semua insert" ON log_audit FOR INSERT WITH CHECK (true); - SELECT: Hanya pengguna berkeistimewaan tinggi, seperti pentadbir sistem, yang sepatutnya boleh melihat log penuh.
CREATE POLICY "Admin boleh baca log" ON log_audit FOR SELECT USING (semak_peranan_pengguna('pentadbir_sistem')); - UPDATE / DELETE: Sama sekali tiada sesiapa pun yang boleh mengubah atau memadam sejarah.
CREATE POLICY "Larang kemas kini dan padam" ON log_audit FOR UPDATE, DELETE USING (false);
Set polisi ini mencipta lejar tulis-sahaja untuk kebanyakan pengguna, memastikan integriti jejak audit.
Cara Kami Menguji Polisi RLS Secara Tempatan
Polisi RLS sangat berkuasa tetapi boleh jadi sukar untuk dinyahpepijat. Satu kesilapan taip boleh mendedahkan semua data anda atau mengunci semua orang. Di JRV Systems, kami tidak pernah menghantar kod RLS tanpa ujian tempatan yang teliti.
Supabase CLI menjadikan ini mudah. Kami menggunakan fail supabase/seed.sql untuk mengisi pangkalan data tempatan kami dengan senario ujian yang realistik:
- Cipta beberapa penyewa (cth., Klinik A, Klinik B).
- Cipta pengguna dalam setiap penyewa.
- Berikan peranan yang berbeza: seorang admin di Klinik A, seorang doktor biasa di Klinik A, dan seorang pengguna di Klinik B.
Kemudian, dalam skrip ujian kami, kami menggunakan set_config PostgreSQL untuk menyamar sebagai pengguna ini dan mengesahkan polisi kami. Sebagai contoh, untuk menjalankan pertanyaan sebagai admin Klinik A:
SET LOCAL "request.jwt.claims" = '{"role":"authenticated","sub":"user_id_admin_klinik_a","app_metadata":{"tenant_id":"tenant_id_klinik_a"}}';
Kami kemudian menjalankan pertanyaan untuk mengesahkan bahawa pengguna ini boleh melihat semua data Klinik A tetapi tiada data dari Klinik B. Proses ujian yang boleh diulang dan diskrip ini memberi kami keyakinan bahawa pola RLS Supabase multi-penyewa kami adalah selamat sebelum ia sampai ke produksi.