Corak Praktikal RLS Supabase Multi-Tenant untuk Aplikasi SaaS
Terokai enam corak praktikal RLS Supabase multi-tenant yang kami di JRV Systems guna untuk melindungi aplikasi SaaS. Pelajari 'tenant-by-claim', 'owner-by-row', dan banyak lagi.
Membina aplikasi Perisian-sebagai-Perkhidmatan (SaaS) berkonsepkan 'multi-tenant' bermakna pengasingan data adalah satu kemestian. Data satu penyewa (tenant) tidak boleh sekali-kali dilihat oleh penyewa lain. Di JRV Systems, kami kerap menggunakan Supabase untuk projek kami, daripada sistem pengurusan klinik hinggalah ke platform e-dagang. Pangkalan data PostgreSQL yang bersepadu dengannya didatangkan dengan ciri hebat yang dipanggil Row-Level Security (RLS), yang merupakan alat utama kami untuk menguatkuasakan pengasingan ini terus di lapisan pangkalan data.
Polisi RLS pada asasnya adalah peraturan yang anda letakkan pada jadual pangkalan data, yang menentukan baris data mana yang boleh dilihat atau diubah oleh pengguna. Ini jauh lebih kukuh berbanding cuba menapis data dalam kod aplikasi anda, di mana satu pepijat kecil boleh menyebabkan kebocoran data yang besar. Berikut adalah enam corak RLS Supabase multi-tenant praktikal yang telah kami laksanakan untuk klien kami di Malaysia.
Asas Utama: 'Claims' JWT dan Fungsi Bantuan
Sebelum boleh menulis polisi, kita perlukan cara untuk mengenal pasti pengguna semasa dan 'tenant' mereka. Apabila pengguna log masuk melalui Supabase Auth, mereka menerima JSON Web Token (JWT). Kami memperkaya token ini dengan metadata khas, atau 'claims', semasa pendaftaran atau melalui 'trigger'. 'Claims' yang paling penting untuk 'multi-tenancy' ialah tenant_id dan role.
Di dalam pangkalan data PostgreSQL, kita boleh mengakses 'claims' ini. Kami biasanya mencipta fungsi bantuan yang ringkas untuk menjadikan polisi kami lebih kemas:
auth.uid(): Fungsi terbina dalam Supabase yang memulangkan ID pengguna yang telah disahkan.request.tenant_id(): Fungsi khas yang kami cipta untuk mengekstraktenant_iddaripada 'claims' JWT dengan selamat.request.user_role(): Satu lagi fungsi khas untuk mendapatkan peranan pengguna.
Dengan komponen asas ini, kita boleh membina polisi keselamatan yang mantap.
Corak 1: Pengasingan 'Tenant-by-Claim'
Ini adalah corak RLS yang paling asas untuk mana-mana aplikasi 'multi-tenant'. Hampir setiap jadual yang mengandungi data spesifik 'tenant' (seperti invoices, customers, products) akan mempunyai lajur tenant_id.
Polisi RLS-nya sangat mudah: pengguna hanya boleh mengakses baris data di mana tenant_id dalam baris tersebut sepadan dengan tenant_id dalam 'claim' JWT mereka.
Untuk jadual invoices, polisi SELECT akan kelihatan seperti ini:
(tenant_id = request.tenant_id())
Satu baris ini, apabila diguna pakai pada setiap jadual yang berkaitan, dapat menghalang akses data antara 'tenant'. Ia ringkas, berkesan, dan merupakan perkara pertama yang kami sediakan dalam mana-mana projek 'multi-tenant'.
Corak 2: 'Owner-by-Row' untuk Data Spesifik Pengguna
Tidak semua data dikongsi oleh seluruh 'tenant'. Kadangkala, satu rekod dimiliki oleh pengguna tertentu. Contohnya seperti profil peribadi pengguna, draf laporan yang sedang diusahakan, atau kunci API yang mereka jana. Untuk kes sebegini, kami menggunakan corak 'owner-by-row'.
Jadual seperti user_profiles atau drafts akan mempunyai lajur user_id. Polisi akan menyemak sama ada lajur ini sepadan dengan ID individu yang membuat permintaan.
(user_id = auth.uid())
Kami sering menggabungkan corak ini dengan semakan 'tenant' untuk keselamatan berlapis. Ini memastikan pengguna hanya boleh melihat draf mereka sendiri dan draf tersebut tergolong dalam 'tenant' yang betul, sekaligus mengelakkan sebarang kes terpencil yang berisiko.
Corak 3: Graf Peranan ('Role-Graph') untuk Kebenaran Kompleks
Aplikasi dunia sebenar sering memerlukan kawalan yang lebih terperinci. Sebagai contoh, pengguna 'admin' mungkin perlu melihat semua invois untuk 'tenant' mereka, manakala 'sales_rep' hanya boleh melihat invois yang mereka cipta. Di sinilah semakan 'tenant' dan 'owner' yang ringkas tidak mencukupi.
Untuk menanganinya, kami melaksanakan sistem graf peranan. Ini biasanya melibatkan beberapa jadual:
roles: Mentakrifkan peranan seperti 'admin', 'finance', 'viewer'.permissions: Mentakrifkan tindakan spesifik seperti 'invoices.read.all', 'invoices.read.own', 'customers.create'.role_permissions: Jadual penghubung yang memadankan peranan dengan kebenaran yang dibenarkan.
Polisi RLS kami kemudiannya akan memanggil fungsi PostgreSQL (cth: has_permission('invoices.read.all')) yang menyemak sama ada peranan semasa pengguna mempunyai kebenaran yang diperlukan. Ini adalah salah satu corak RLS Supabase multi-tenant yang lebih maju, tetapi ia penting untuk membina perisian perusahaan yang fleksibel. Kami telah menggunakan corak ini untuk membina aliran kerja kelulusan yang kompleks untuk sistem pengebilan.
Corak 4: Menghadkan Akses kepada Rekod 'Soft-Delete'
Kami jarang memadam data secara kekal. Sebaliknya, kami menggunakan corak 'soft-delete' dengan menambah lajur cap masa deleted_at. Apabila pengguna 'memadam' sesuatu item, kami hanya menetapkan cap masa ini.
RLS sangat sesuai untuk menguruskan kebolehlihatan data ini. Kami mencipta dua polisi SELECT untuk jadual tersebut:
- Untuk pengguna biasa: Polisi hanya membenarkan akses jika
deleted_atadalahNULL.USING (tenant_id = request.tenant_id() AND deleted_at IS NULL) - Untuk pengguna admin: Polisi berasingan memberi mereka akses kepada semua rekod, termasuk yang telah di-'soft-delete', untuk tujuan audit atau pemulihan.
USING (tenant_id = request.tenant_id() AND request.user_role() = 'admin')
Supabase akan membenarkan akses jika mana-mana polisi untuk arahan tertentu (cth: SELECT) dinilai sebagai benar.
Corak 5: Melindungi Jejak Audit ('Audit Trails')
Bagi kebanyakan klien kami, terutamanya dalam industri yang dikawal selia seperti penjagaan kesihatan, menyimpan jejak audit yang terperinci adalah wajib. Jadual audit_log mungkin merekodkan setiap tindakan penting yang diambil oleh pengguna. Data ini sangat sensitif.
RLS memastikan log ini tidak boleh diakses sewenang-wenangnya. Polisi biasa untuk jadual audit_log akan mengehadkan pengguna untuk hanya melihat peristiwa yang berlaku dalam 'tenant' mereka sendiri. Kami mungkin akan mengehadkannya lagi supaya pengguna bukan admin hanya boleh melihat tindakan mereka sendiri.
Keselamatan di peringkat pangkalan data ini amat penting untuk menjaga privasi data dan mencapai piawaian pematuhan.
Menguji Polisi Anda dengan supabase/seed.sql
Menulis polisi hanyalah separuh jalan; anda juga mesti mengujinya dengan teliti. Kesilapan dalam RLS boleh membawa padah. Kami menggunakan Supabase CLI dan persekitaran pembangunan setempat untuk tujuan ini.
Fail supabase/seed.sql bertujuan untuk mengisi data pembangunan setempat, tetapi kami juga menggunakannya untuk menulis ujian RLS automatik. Ini proses kami:
- Sediakan Data Ujian: Dalam
seed.sql, kami mencipta beberapa 'tenant' dan pengguna dengan peranan yang berbeza (cth: Admin Tenant A, Ahli Tenant B). - Menyamar sebagai Pengguna: Kami menggunakan arahan
SETPostgreSQL untuk menjalankan 'query' seolah-olah kami adalah pengguna tertentu. Contohnya:SET ROLE authenticated; SET request.jwt.claims = '{"sub":"user-id-here", "tenant_id":"tenant-a-id", "role":"admin"}'; - Sahkan Hasil: Kami menggunakan rangka kerja ujian seperti
pgTAPuntuk menulis 'assertion'. Kami akan menguji bahawa pengguna Tenant A boleh melihat invois Tenant A dan, yang paling penting, mereka tidak boleh melihat invois Tenant B.
Ujian automatik ini memberi kami keyakinan bahawa corak RLS Supabase multi-tenant kami berfungsi dengan betul sebelum kami melaksanakan sebarang perubahan.