أمان مستوى الصف في PostgreSQL لتطبيقات SaaS يساعد على فرض عزل المستأجرين داخل قاعدة البيانات. تعلّم متى تستخدمه، كيف تكتب السياسات، وما الذي يجب تجنّبه.

في تطبيق SaaS، أخطر ثغرة أمنية هي تلك التي تظهر بعد أن تتوسع. تبدأ بقاعدة بسيطة مثل "يمكن للمستخدمين رؤية بيانات مستأجرهم فقط"، ثم تطلق نقطة نهاية جديدة بسرعة، تضيف استعلام تقارير، أو تدخل انضمامًا يتجاوز الفحص دون أن تلاحظ.
التحقق من الأذونات في طبقة التطبيق يفشل تحت الضغط لأن القواعد تصبح مبعثرة. يتحقق كنترولر واحد من tenant_id، والآخر يتحقق من العضوية، ومهمة خلفية تنسى، ومسار "تصدير للمشرف" يبقى "مؤقتًا" لشهور. حتى الفرق الحذرة تفوّت موضعًا واحدًا.
Row-level security في PostgreSQL (RLS) تحل مشكلة محددة: تجعل قاعدة البيانات تفرض الصفوف المرئية لطلب معين. نموذج التفكير بسيط: كل SELECT وUPDATE وDELETE يُصفى تلقائيًا بواسطة سياسات، بنفس طريقة تصفية كل طلب عبر وسيط المصادقة.
كلمة "صفوف" مهمة. RLS لا تحمي كل شيء:
مثال ملموس: تضيف نقطة نهاية تعرض المشاريع مع انضمام إلى الفواتير لواجهة لوحة. مع التفويض في التطبيق فقط، من السهل تصفية projects حسب المستأجر ونسيان تصفية invoices، أو الانضمام على مفتاح يقطع عبر المستأجرين. مع RLS، يمكن لكلتا الجداول فرض عزل المستأجرين، لذا يفشل الاستعلام بأمان بدلًا من تسريب البيانات.
المقايضة حقيقية. تكتب أقل منطق تفويض مكرر وتقلل أماكن التسريب المحتملة. لكن عليك أيضًا عمل جديد: يجب أن تصمم السياسات بعناية، تختبرها مبكرًا، وتقبل أن سياسة قد تمنع استعلامًا كنت تتوقع أن يعمل.
RLS قد تبدو عملًا إضافيًا حتى يأتي التطبيق إلى ما بعد بضع نقاط نهاية. إذا كانت لديك حدود مستأجر صارمة والعديد من مسارات الاستعلام (قوائم، بحث، تصدير، أدوات إدارة)، فإن وضع القاعدة في قاعدة البيانات يعني ألا تحتاج لأن تتذكر إضافة نفس الفلتر في كل مكان.
RLS مناسب بقوة عندما تكون القاعدة مملة وعامة: "يمكن للمستخدم رؤية الصفوف الخاصة بمستأجره فقط" أو "يمكن للمستخدم رؤية المشاريع التي هو عضو فيها فقط". في هذه الحالات، تقلل السياسات الأخطاء لأن كل SELECT وUPDATE وDELETE تمر عبر نفس البوابة، حتى لو أُضيف استعلام لاحقًا.
يفيد أيضًا في التطبيقات ذات القراءة الكثيفة حيث يبقى منطق التصفية متسقًا. إذا كان لديك 15 طريقة مختلفة لتحميل الفواتير (بحسب الحالة، التاريخ، العميل، البحث)، تتيح لك RLS التوقف عن إعادة تنفيذ فلترة المستأجر في كل استعلام والتركيز على الميزة.
RLS يضيف ألمًا عندما لا تكون القواعد مبنية على الصفوف. القواعد على مستوى الحقول مثل "يمكنك رؤية الراتب لكن ليس المكافأة" أو "اخفِ هذا العمود إلا إذا كنت جزءًا من شؤون الموظفين" تتحول غالبًا إلى SQL محرجة واستثناءات يصعب الحفاظ عليها.
كما أنه ملائم بصعوبة للتقارير الثقيلة التي تحتاج حقًا إلى وصول واسع. الفرق غالبًا تنشئ أدوارًا تتجاوز القواعد لـ "مهمة واحدة فقط"، وهناك تتراكم الأخطاء.
قبل الالتزام، قرر ما إذا كنت تريد أن تكون قاعدة البيانات الحارس النهائي. إذا نعم، خطّط للانضباط: اختبر سلوك قاعدة البيانات (ليس استجابات الـ API فقط)، اعتبر الترحيلات تغييرات أمنية، تجنّب التجاوزات السريعة، قرر كيف توثّق مهام الخلفية، وحافظ على سياسات صغيرة وقابلة للإعادة.
إذا كنت تستخدم أدوات تولد الـ backend، يمكن أن تسرّع التسليم، لكنها لا تزيل الحاجة إلى أدوار واضحة، اختبارات، ونموذج مستأجر بسيط. (على سبيل المثال، Koder.ai تستخدم Go و PostgreSQL للـ backends المولّدة، وما تزال تريد تصميم RLS بعمد بدلًا من "رشه" لاحقًا.)
RLS تكون الأسهل عندما يوضح المخطط بالفعل من يملك ماذا. إذا بدأت بنموذج غامض وحاولت "إصلاحه بالسياسات"، فعادة ما تحصل على استعلامات بطيئة وأخطاء محيرة.
اختر مفتاح مستأجر واحد (مثل org_id) واستخدمه باستمرار. معظم الجداول المملوكة للمستأجر يجب أن تحتويه، حتى لو كانت تشير أيضًا إلى جدول آخر يحتويه. هذا يتجنّب الانضمامات داخل السياسات ويحافظ على فحوص USING بسيطة.
قاعدة عملية: إذا كان ينبغي أن يختفي الصف عندما يلغي العميل اشتراكه، فربما يحتاج org_id.
سياسات RLS عادة تجيب على سؤال واحد: "هل هذا المستخدم عضو في هذه المنظمة، وماذا يمكنه أن يفعل؟" من الصعب استنتاج ذلك من أعمدة عشوائية.
حافظ على الجداول الأساسية صغيرة ومباشرة:
users (صف واحد لكل شخص)orgs (صف واحد لكل مستأجر)org_memberships (user_id, org_id, role, status)project_memberships للوصول حسب المشروعمع ذلك، يمكن لسياساتك التحقق من العضوية عبر بحث مفهرس واحد.
ليس كل شيء بحاجة إلى org_id. جداول المرجع مثل الدول، فئات المنتجات، أو أنواع الخطط غالبًا ما تكون مشتركة عبر جميع المستأجرين. اجعلها قابلة للقراءة فقط لمعظم الأدوار، ولا تربطها بمنظمة محددة.
يجب أن تتجنب بيانات المالك المستأجر (مشاريع، فواتير، تذاكر) جلب تفاصيل مستأجر عبر جداول مشتركة. اجعل الجداول المشتركة قليلة ومستقرة.
لا تزال المفاتيح الأجنبية تعمل مع RLS، لكن الحذف قد يفاجئك إذا كان الدور المحذوف لا "يرى" الصفوف التابعة. خطّط للحذف التتابعي واختبر عمليات الحذف الحقيقية.
فهرس الأعمدة التي تصفي سياساتك، خاصة org_id ومفاتيح العضوية. لا ينبغي أن يتحول شرط بسيط مثل WHERE org_id = ... إلى مسح كامل للجدول عندما يصل الجدول إلى ملايين الصفوف.
RLS هو مفتاح على مستوى الجدول. بمجرد التمكين، تتوقف PostgreSQL عن الوثوق بكود التطبيق لتذكّر فلتر المستأجر. كل SELECT و UPDATE و DELETE يُصفى بواسطة السياسات، وكل INSERT و UPDATE تُتحقق بواسطة السياسات.
أكبر تبدّل ذهني: مع تفعيل RLS، الاستعلامات التي كانت تعيد بيانات قد تبدأ بإرجاع صفر صفوف دون أخطاء. هذا PostgreSQL ينفذ تحكم الوصول.
السياسات قواعد صغيرة مرتبطة بجدول. تستخدم نوعين من الفحوص:
USING هو فلتر القراءة. إذا لم يطابق الصف USING، فهو غير مرئي لـ SELECT ولا يمكن استهدافه بـ UPDATE أو DELETE.WITH CHECK هو بوابة الكتابة. يقرّر ما الصفوف الجديدة أو المعدّلة المسموح بها لـ INSERT أو UPDATE.نمط SaaS شائع: USING يضمن أنك ترى صفوف مستأجرك فقط، وWITH CHECK يضمن ألا تستطيع إدراج صف في مستأجر آخر عبر تخمين tenant_id.
عند إضافة سياسات لاحقًا، هذا يهم:
PERMISSIVE (الافتراضي): الصف مسموح به إذا سمحت به أي سياسة.RESTRICTIVE: الصف مسموح به فقط إذا سمحت به كل السياسات المقيدة (إلى جانب سلوك permissive).إذا كنت تخطط لطبقات مثل مطابقة المستأجر بالإضافة إلى فحوص الدور بالإضافة إلى عضوية المشروع، قد تجعل السياسات المقيدة النية أوضح، لكنها تجعل أيضًا من السهل أن تُحجب إذا نسيت شرطًا.
RLS يحتاج قيمة "من ينادي" موثوقة. خيارات شائعة:
app.user_id و app.tenant_id).SET ROLE ...) لكل طلب، والذي قد يعمل لكنه يضيف عبء تشغيلي.اختر نهجًا واحدًا وطبّقه في كل مكان. خلط مصادر الهوية عبر الخدمات مسار سريع للأخطاء المحيرة.
استخدم اصطلاحًا متوقعًا حتى تبقى تفريغات المخطط والسجلات قابلة للقراءة. على سبيل المثال: {table}__{action}__{rule}، مثل projects__select__tenant_match.
إذا كنت جديدًا على RLS، ابدأ بجدول واحد وبرهان صغير. الهدف ليس تغطية مثالية. الهدف أن ترفض قاعدة البيانات الوصول عبر المستأجر حتى لو حدث خطأ في التطبيق.
افترض جدول projects بسيطًا. أولًا، أضف tenant_id بطريقة لا تكسر عمليات الكتابة.
ALTER TABLE projects ADD COLUMN tenant_id uuid;
-- Backfill existing rows (example: everyone belongs to a default tenant)
UPDATE projects SET tenant_id = '11111111-1111-1111-1111-111111111111'::uuid
WHERE tenant_id IS NULL;
ALTER TABLE projects ALTER COLUMN tenant_id SET NOT NULL;
بعد ذلك، فرّق بين الملكية والوصول. نمط شائع: دور واحد يملك الجداول (app_owner)، ودور آخر يستخدمه الـ API (app_user). يجب ألا يكون دور الـ API مالك الجدول، وإلا قد يتجاوز السياسات.
ALTER TABLE projects OWNER TO app_owner;
REVOKE ALL ON projects FROM PUBLIC;
GRANT SELECT, INSERT, UPDATE, DELETE ON projects TO app_user;
الآن قرر كيف يخبر الطلب Postgres أي مستأجر يخدم. نهج بسيط هو إعداد نطاق جلسة للطلب. يضبط التطبيق هذا مباشرة بعد فتح المعاملة.
-- inside the same transaction as the request
SELECT set_config('app.current_tenant', '22222222-2222-2222-2222-222222222222', true);
فعّل RLS وابدأ بوصول القراءة.
ALTER TABLE projects ENABLE ROW LEVEL SECURITY;
CREATE POLICY projects_tenant_select
ON projects
FOR SELECT
TO app_user
USING (tenant_id = current_setting('app.current_tenant')::uuid);
ثبت أنه يعمل عبر تجربة مستأجرين مختلفين وملاحظة تغير عدد الصفوف.
سياسات القراءة لا تحمي الكتابات. أضف WITH CHECK حتى لا تستطيع الإدخالات والتحديثات تهريب صفوف إلى مستأجر خاطئ.
CREATE POLICY projects_tenant_write
ON projects
FOR INSERT, UPDATE
TO app_user
WITH CHECK (tenant_id = current_setting('app.current_tenant')::uuid);
طريقة سريعة للتحقق من السلوك (بما في ذلك الفشل) هي الاحتفاظ بنص SQL صغير يمكنك إعادة تشغيله بعد كل ترحيل:
BEGIN; SET LOCAL ROLE app_user;SELECT set_config('app.current_tenant', '\u003ctenant A\u003e', true); SELECT count(*) FROM projects;INSERT INTO projects(id, tenant_id, name) VALUES (gen_random_uuid(), '\u003ctenant B\u003e', 'bad'); (يجب أن يفشل)UPDATE projects SET tenant_id = '\u003ctenant B\u003e' WHERE ...; (يجب أن يفشل)ROLLBACK;إذا استطعت تشغيل هذا النص والحصول على نفس النتائج في كل مرة، فبذلك تملك خط أساس موثوق قبل نشر RLS إلى جداول أخرى.
معظم الفرق تعتمد RLS بعد أن تملّ من تكرار نفس فحوص التفويض في كل استعلام. الخبر الجيد أن أشكال السياسات التي تحتاجها عادة تكون متسقة.
بعض الجداول مملوكة طبيعيًا من قبل مستخدم واحد (ملاحظات، رموز API). أخرى تخص مستأجرًا حيث يعتمد الوصول على العضوية. عامل هذين النمطين بشكل مختلف.
لبيانات المالك فقط، غالبًا ما تتحقق السياسات من created_by = app_user_id(). لبيانات المستأجر، عادة تتحقق السياسات مما إذا كان للمستخدم صف عضوية في المنظمة.
طريقة عملية للحفاظ على قابلية قراءة السياسات هي تجميع الهوية في دوال SQL صغيرة وإعادة استخدامها:
-- Example helpers
create function app_user_id() returns uuid
language sql stable as $$
select current_setting('app.user_id', true)::uuid
$$;
create function app_is_admin() returns boolean
language sql stable as $$
select current_setting('app.is_admin', true) = 'true'
$$;
القراءات غالبًا أوسع من الكتابات. على سبيل المثال، أي عضو في المنظمة يمكنه SELECT المشاريع، لكن فقط المحررون يمكنهم UPDATE، وفقط المالكون يمكنهم DELETE.
اجعل ذلك صريحًا: سياسة واحدة لـ SELECT (العضوية)، سياسة واحدة لـ INSERT/UPDATE مع WITH CHECK (الدور)، وسياسة واحدة لـ DELETE (غالبًا أكثر صرامة من التحديث).
تجنّب "إيقاف RLS للمشرفين". بدلًا من ذلك، أضف مخرجًا داخل السياسات، مثل app_is_admin()، حتى لا تمنح وصولًا كاملاً عن طريق الخطأ لدور خدمة مشترك.
إذا كنت تستخدم deleted_at أو status، أدرجها في سياسة SELECT (deleted_at is null). وإلا قد يتم "إحياء" الصفوف عبر قلب أعلام افترض التطبيق أنها نهائية.
WITH CHECK ودودًايجب أن يفي INSERT ... ON CONFLICT DO UPDATE بشرط WITH CHECK للصف بعد الكتابة. إذا كانت سياستك تتطلب created_by = app_user_id()، فتأكد أن upsert يضع created_by عند الإدراج ولا يكتبه فوقه عند التحديث.
إذا كنت تولد كود الـ backend، فهذه الأنماط تستحق أن تتحول إلى قوالب داخلية حتى تبدأ الجداول الجديدة بإفتراضات آمنة بدلًا من صفحة فارغة.
RLS رائعة حتى تفعل تفصيلة صغيرة فتبدو PostgreSQL "يخفي أو يظهر" بيانات بشكل عشوائي. الأخطاء أدناه تهدر معظم الوقت.
الفخ الأول هو نسيان WITH CHECK على الإدراج والتحديث. USING يتحكم بما ترى، لا بما يُسمح بإنشائه. بدون WITH CHECK، يمكن لخطأ في التطبيق كتابة صف لمستأجر خاطئ، وربما لا تلاحظه لأن نفس المستخدم لا يمكنه قراءته.
فخ شائع آخر هو "الانضمام الماسِر". تقوم بتصفية projects بشكل صحيح، ثم تنضم إلى invoices أو notes أو files التي ليست محمية بنفس الطريقة. الحل صارم لكنه واضح: كل جدول يمكنه إفشاء بيانات مستأجر يحتاج سياسته الخاصة، ولا يجب أن تعتمد views على أن جدولًا واحدًا فقط آمن.
أنماط الفشل الشائعة تظهر مبكرًا:
WITH CHECK.السياسات التي تشير إلى نفس الجدول (مباشرة أو عبر view) يمكن أن تخلق مفاجآت تكرارية. قد تتحقق السياسة من العضوية باستعلام view يقرأ الجدول المحمي مرة أخرى، مما يؤدي إلى أخطاء، استعلامات بطيئة، أو سياسة لا تطابق أبدًا.
إعداد الأدوار مصدر آخر للالتباس. ملاك الجداول والأدوار المرتفعة يمكنهم تجاوز RLS، لذا تمر اختباراتك بينما يفشل المستخدمون الحقيقيون (أو العكس). اختبر دائمًا بالدور منخفض الامتياز الذي يستخدمه تطبيقك.
كن حذرًا مع الدوال ذات SECURITY DEFINER. تعمل بصلاحيات مالك الدالة، لذا مساعد مثل current_tenant_id() قد يكون آمنًا، لكن دالة "تسهيلية" تقرأ بيانات قد تقرأ عبر المستأجرين ما لم تصممها لاحترام RLS.
ضبط search_path آمن داخل دوال security definer مهم أيضًا. إن لم تفعل، قد تلتقط الدالة كائنًا مختلفًا بنفس الاسم، وقد تشير منطق السياسة بهدوء إلى الشيء الخطأ اعتمادًا على حالة الجلسة.
أخطاء RLS عادة تكون بسبب سياق مفقود، لا "SQL سيئ". قد تكون السياسة صحيحة نظريًا وتفشل لأن دور الجلسة مختلف عما تظن، أو لأن الطلب لم يضبط قيم المستأجر والمستخدم التي تعتمد عليها السياسة.
طريقة موثوقة لإعادة إنتاج تقرير من الإنتاج هي محاكاة نفس إعداد الجلسة محليًا وتشغيل نفس الاستعلام بالضبط. ذلك عادة يعني:
SET ROLE app_user; (أو دور الـ API الحقيقي)SELECT set_config('app.tenant_id', 't_123', true); و SELECT set_config('app.user_id', 'u_456', true);SELECT current_user, current_setting('app.tenant_id', true), current_setting('app.user_id', true);عندما تكون غير متأكد أي سياسة تُطبق، افحص الكتالوج بدلًا من التخمين. يظهر pg_policies كل سياسة، الأمر، وتعبيرات USING و WITH CHECK. اقترن ذلك مع pg_class لتتأكد أن RLS مفعّل على الجدول وأنه لا يتم تجاوزه.
مشاكل الأداء قد تبدو مثل مشاكل تفويض. سياسة تنضم لجدول العضوية أو تستدعي دالة قد تكون صحيحة لكنها بطيئة عندما يكبر الجدول. استخدم EXPLAIN (ANALYZE, BUFFERS) على الاستعلام المعاد إنتاجه وابحث عن عمليات المسح التسلسلي، الحلقات المتداخلة غير المتوقعة، أو الفلاتر المطبقة متأخرًا. فهارس مفقودة على (tenant_id, user_id) وجداول العضوية أسباب شائعة.
يساعد أيضًا تسجيل ثلاث قيم لكل طلب على مستوى التطبيق: معرّف المستأجر، معرّف المستخدم، ودور قاعدة البيانات المستخدم للطلب. عندما لا تتطابق هذه القيم مع ما تظن أنك ضبطته، سيتصرف RLS "بشكل خاطئ" لأن المدخلات خاطئة.
لاختباراتك، احتفظ ببضع مستأجرين مُعدة واجعل الفشل صريحًا. مجموعة صغيرة عادة تتضمن: "المستأجر A لا يمكنه قراءة المستأجر B"، "المستخدم بدون عضوية لا يرى المشروع"، "المالك يمكنه التحديث، المشاهد لا يمكنه"، "الإدراج محظور إلا إذا طابق tenant_id السياق"، و"تجاوز المشرف يطبق فقط حيث ينبغي".
عامل RLS مثل حزام الأمان، ليس مفتاح ميزة قابلة للتشغيل. الأخطاء الصغيرة تتحول إلى "الجميع يمكنهم رؤية بيانات الجميع" أو "كل شيء يرجع صفر صفوف".
تأكد أن تصميم جدولك وقواعد سياساتك تطابق نموذج المستأجرين.
tenant_id). إذا لم يفعل، اكتب سببًا لذلك (مثل جداول مرجعية عالمية).FORCE ROW LEVEL SECURITY على تلك الجداول.USING. يجب أن تتضمن الكتابات WITH CHECK حتى لا تسمح الإدراجات والتحديثات بنقل الصف إلى مستأجر آخر.tenant_id أو تنضم عبر جداول العضوية، أضف الفهارس المطابقة.سيناريو صحة بسيط: مستخدم من المستأجر A يمكنه قراءة فواتيره، يمكنه إدراج فاتورة فقط للمستأجر A، ولا يمكنه تحديث فاتورة لتغيير tenant_id.
RLS قوية بقدر الأدوار التي يستخدمها تطبيقك.
bypassrls.تخيل تطبيق B2B حيث الشركات (orgs) تملك مشاريع، والمشاريع لها مهام. يمكن للمستخدمين الانتماء لعدة منظمات، وقد يكون المستخدم عضواً في بعض المشاريع وليس في الأخرى. هذا مناسب لـ RLS لأن قاعدة البيانات يمكنها إجبار عزل المستأجرين حتى لو نسي نقطة نهاية في الـ API فلترًا.
نموذج بسيط هو: orgs, users, org_memberships (org_id, user_id, role), projects (id, org_id), project_memberships (project_id, user_id), tasks (id, project_id, org_id, ...). وجود org_id على tasks مقصود. يبقي السياسات بسيطة ويقلل المفاجآت أثناء الانضمامات.
تسريب كلاسيكي يحدث عندما تحتوي المهام فقط على project_id، وتتحقق سياستك من الوصول عبر انضمام إلى projects. خطأ واحد (سياسة متساهلة على projects، انضمام يُسقط شرطًا، أو view يغيّر السياق) يمكنه كشف مهام من منظمة أخرى.
مسار هجرة أكثر أمانًا يتجنب تعطيل حركة الإنتاج:
org_id إلى tasks, أضف جداول العضوية).tasks.org_id من projects.org_id، ثم أضف NOT NULL.دخول الدعم يُتعامل معه عادة بدور ضيق لكسر القفل، وليس بتعطيل RLS. احتفظ به منفصلًا عن حسابات الدعم العادية واجعل استخدامه صريحًا عند الحاجة.
وثّق القواعد حتى لا تنحرف السياسات: أي متغيرات جلسة يجب ضبطها (user_id, org_id)، أي جداول يجب أن تحمل org_id, ما معنى "عضو"، وبعض أمثلة SQL التي يجب أن تعيد 0 صف عندما تُشغّل كمنظمة خاطئة.
تسهّل RLS عندما تعاملها كتغيير منتج. طبّقها دفعات صغيرة، أثبت السلوك بالاختبارات، واحتفظ بسجل واضح لسبب وجود كل سياسة.
خطة طرح تعمل عادة:
projects) وقم بتأمينه.بعد استقرار الجدول الأول، اجعل تغييرات السياسات متعمدة. أضف خطوة مراجعة السياسات للترحيلات، وضمن ملاحظة قصيرة عن النية (من ينبغي أن يصل إلى ماذا ولماذا) ومعها تحديث اختبار مطابق. هذا يمنع "إضافة OR آخر" التي تتحول تدريجيًا إلى ثغرة.
إذا كنت تتحرك بسرعة، أدوات مثل Koder.ai (koder.ai) يمكن أن تساعدك في توليد نقطة بداية Go + PostgreSQL عبر المحادثة، ثم يمكنك وضع سياسات RLS والاختبارات بنفس انضباط قاعدة الكود المولد يدويًا.
أخيرًا، احتفظ بحواجز أمان أثناء الطرح. التقط لقطات قبل ترحيلات السياسات، مرّن التراجع حتى يصبح مملًا، واحتفظ بممر صغير لكسر القفل للدعم لا يعطل RLS عبر النظام بأكمله.
RLS يجعل PostgreSQL يفرض أي الصفوف المرئية أو القابلة للكتابة لطلب ما، بحيث لا يعتمد عزل المستأجرين على أن يتذكّر كل مسار في الواجهة وضع فلتر WHERE tenant_id = .... الفائدة الرئيسية هي تقليل أخطاء "فحص واحد مفقود" عندما يكبر التطبيق وتتكاثر الاستعلامات.
تفيد عندما تكون قواعد الوصول متناسقة ومبنية على الصفوف، مثل عزل المستأجرين أو الوصول المبني على العضويات، ولديك العديد من مسارات الاستعلام (بحث، تصدير، شاشات إدارة، مهام خلفية). عادة لا تفيد عندما تكون القواعد على مستوى الحقل مع استثناءات كثيرة أو عندما تحتاج تقارير واسعة النطاق لقراءة عبر مستأجرين متعددة.
استخدم RLS لرؤية الصفوف والقيود الأساسية على الكتابة، ثم استخدم أدوات أخرى للحاجات الأخرى. خصوصية الأعمدة عادة تتطلب views وصلاحيات أعمدة، والقواعد التجارية المعقدة (مثل ملكية الفوترة أو تدفقات الموافقة) تظل من مهام منطق التطبيق أو قيود قاعدة بيانات مصممة بعناية.
أنشئ دورًا منخفض الامتياز للـ API (ليس مالك الجدول)، فعّل RLS، ثم أضف سياسة SELECT وسياسة INSERT/UPDATE مع WITH CHECK. اضبط متغير جلسة مخصص للطلب (مثل app.current_tenant) وتحقق أن تغييره يغيّر الصفوف التي يمكنك رؤيتها وكتابتها.
الافتراض الشائع هو متغير جلسة لكل طلب، يُضبط في بداية المعاملة، مثل app.tenant_id و app.user_id. المفتاح هو الاتساق: كل مسار برمجي (طلبات الويب، الوظائف، السكربتات) يجب أن يضبط نفس القيم التي تتوقعها السياسات، وإلا ستحصل على سلوك "صفوف صفر" محيّر.
USING يتحكم في أي الصفوف الموجودة مرئية ويمكن استهدافها لـ SELECT و UPDATE و DELETE. WITH CHECK يتحكم في أي صفوف جديدة أو معدلة مسموح بها خلال INSERT و UPDATE، لذا يمنع "الكتابة إلى مستأجر آخر" حتى لو مرر التطبيق خاطئًا.
لأن إضافة USING فقط تسمح للرؤية لكن لا تمنع إنشاء صفوف في مستأجر آخر. نقطة الشيوع: إذا لم تضف WITH CHECK، يمكن لنقطة نهاية خاطئة إدخال صف إلى مستأجر خاطئ ولن تلاحظه لأن نفس المستخدم قد لا يستطيع قراءة ذلك الصف لاحقًا. دائمًا أضف قاعدة WITH CHECK مطابقة لقراءة المستأجر للكتابات.
تجنّب الانضمامات داخل السياسات بوضع مفتاح المستأجر (مثل org_id) مباشرة على الجداول المملوكة للمستأجر حتى لو كانت تشير إلى جدول آخر يمتلكه المستأجر. أضف جداول عضوية صريحة (org_memberships وربما project_memberships) حتى تتطلب السياسات نظرة مرجعية مفهرسة واحدة بدلًا من استنتاج معقّد.
أعد إنشاء نفس سياق الجلسة الذي يستخدمه التطبيق عبر ضبط نفس الدور وإعدادات الجلسة ثم نفّذ نفس الاستعلام بالضبط. تأكد أن RLS مفعّل وافحص pg_policies لترى تعابير USING و WITH CHECK المطبقة، لأن أخطاء RLS عادة تنتج عن سياق هوية مفقود أكثر من "SQL خاطئ".
نعم، لكن اعتبر الكود المُولّد نقطة بداية لا كحل أمني نهائي. حتى لو استخدمت Koder.ai لتوليد Backend بـ Go + PostgreSQL، لا بد من تحديد نموذج المستأجرين، ضبط هوية الجلسة بشكل متسق، وإضافة سياسات واختبارات بعناية حتى لا تُشحن جداول جديدة بدون الحماية الصحيحة.
tenant_id