KoderKoder.ai
الأسعارالمؤسساتالتعليمللمستثمرين
تسجيل الدخولابدأ الآن

المنتج

الأسعارالمؤسساتللمستثمرين

الموارد

اتصل بناالدعمالتعليمالمدونة

قانوني

سياسة الخصوصيةشروط الاستخدامالأمانسياسة الاستخدام المقبولالإبلاغ عن إساءة

اجتماعي

LinkedInTwitter
Koder.ai
اللغة

© 2026 ‏Koder.ai. جميع الحقوق محفوظة.

الرئيسية›المدونة›أمان مستوى الصف في PostgreSQL لتطبيقات SaaS — سياسات فعالة
14 أكتوبر 2025·7 دقيقة

أمان مستوى الصف في PostgreSQL لتطبيقات SaaS — سياسات فعالة

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

أمان مستوى الصف في PostgreSQL لتطبيقات SaaS — سياسات فعالة

المشكلة الحقيقية التي تحاول RLS حلها في تطبيقات SaaS

في تطبيق SaaS، أخطر ثغرة أمنية هي تلك التي تظهر بعد أن تتوسع. تبدأ بقاعدة بسيطة مثل "يمكن للمستخدمين رؤية بيانات مستأجرهم فقط"، ثم تطلق نقطة نهاية جديدة بسرعة، تضيف استعلام تقارير، أو تدخل انضمامًا يتجاوز الفحص دون أن تلاحظ.

التحقق من الأذونات في طبقة التطبيق يفشل تحت الضغط لأن القواعد تصبح مبعثرة. يتحقق كنترولر واحد من tenant_id، والآخر يتحقق من العضوية، ومهمة خلفية تنسى، ومسار "تصدير للمشرف" يبقى "مؤقتًا" لشهور. حتى الفرق الحذرة تفوّت موضعًا واحدًا.

Row-level security في PostgreSQL (RLS) تحل مشكلة محددة: تجعل قاعدة البيانات تفرض الصفوف المرئية لطلب معين. نموذج التفكير بسيط: كل SELECT وUPDATE وDELETE يُصفى تلقائيًا بواسطة سياسات، بنفس طريقة تصفية كل طلب عبر وسيط المصادقة.

كلمة "صفوف" مهمة. RLS لا تحمي كل شيء:

  • لا تخفي أعمدة محددة بنفسها (استخدم views أو صلاحيات أعمدة).
  • لا تجعل الدوال غير الآمنة آمنة (يمكن للدالة أن تتسرب إذا نفّذت بصلاحيات مرتفعة).
  • لا تتحقق من قواعد العمل المعقدة (مثل "فقط المالكون يمكنهم تغيير إعدادات الفواتير").

مثال ملموس: تضيف نقطة نهاية تعرض المشاريع مع انضمام إلى الفواتير لواجهة لوحة. مع التفويض في التطبيق فقط، من السهل تصفية projects حسب المستأجر ونسيان تصفية invoices، أو الانضمام على مفتاح يقطع عبر المستأجرين. مع RLS، يمكن لكلتا الجداول فرض عزل المستأجرين، لذا يفشل الاستعلام بأمان بدلًا من تسريب البيانات.

المقايضة حقيقية. تكتب أقل منطق تفويض مكرر وتقلل أماكن التسريب المحتملة. لكن عليك أيضًا عمل جديد: يجب أن تصمم السياسات بعناية، تختبرها مبكرًا، وتقبل أن سياسة قد تمنع استعلامًا كنت تتوقع أن يعمل.

متى تبسط RLS التفويض (ومتى تضيف ألمًا)

RLS قد تبدو عملًا إضافيًا حتى يأتي التطبيق إلى ما بعد بضع نقاط نهاية. إذا كانت لديك حدود مستأجر صارمة والعديد من مسارات الاستعلام (قوائم، بحث، تصدير، أدوات إدارة)، فإن وضع القاعدة في قاعدة البيانات يعني ألا تحتاج لأن تتذكر إضافة نفس الفلتر في كل مكان.

RLS مناسب بقوة عندما تكون القاعدة مملة وعامة: "يمكن للمستخدم رؤية الصفوف الخاصة بمستأجره فقط" أو "يمكن للمستخدم رؤية المشاريع التي هو عضو فيها فقط". في هذه الحالات، تقلل السياسات الأخطاء لأن كل SELECT وUPDATE وDELETE تمر عبر نفس البوابة، حتى لو أُضيف استعلام لاحقًا.

يفيد أيضًا في التطبيقات ذات القراءة الكثيفة حيث يبقى منطق التصفية متسقًا. إذا كان لديك 15 طريقة مختلفة لتحميل الفواتير (بحسب الحالة، التاريخ، العميل، البحث)، تتيح لك RLS التوقف عن إعادة تنفيذ فلترة المستأجر في كل استعلام والتركيز على الميزة.

RLS يضيف ألمًا عندما لا تكون القواعد مبنية على الصفوف. القواعد على مستوى الحقول مثل "يمكنك رؤية الراتب لكن ليس المكافأة" أو "اخفِ هذا العمود إلا إذا كنت جزءًا من شؤون الموظفين" تتحول غالبًا إلى SQL محرجة واستثناءات يصعب الحفاظ عليها.

كما أنه ملائم بصعوبة للتقارير الثقيلة التي تحتاج حقًا إلى وصول واسع. الفرق غالبًا تنشئ أدوارًا تتجاوز القواعد لـ "مهمة واحدة فقط"، وهناك تتراكم الأخطاء.

قبل الالتزام، قرر ما إذا كنت تريد أن تكون قاعدة البيانات الحارس النهائي. إذا نعم، خطّط للانضباط: اختبر سلوك قاعدة البيانات (ليس استجابات الـ API فقط)، اعتبر الترحيلات تغييرات أمنية، تجنّب التجاوزات السريعة، قرر كيف توثّق مهام الخلفية، وحافظ على سياسات صغيرة وقابلة للإعادة.

إذا كنت تستخدم أدوات تولد الـ backend، يمكن أن تسرّع التسليم، لكنها لا تزيل الحاجة إلى أدوار واضحة، اختبارات، ونموذج مستأجر بسيط. (على سبيل المثال، Koder.ai تستخدم Go و PostgreSQL للـ backends المولّدة، وما تزال تريد تصميم RLS بعمد بدلًا من "رشه" لاحقًا.)

أساسيات نموذج البيانات التي تجعل سياسات 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، بدون الأجزاء المخيفة

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 (الافتراضي): الصف مسموح به إذا سمحت به أي سياسة.
  • RESTRICTIVE: الصف مسموح به فقط إذا سمحت به كل السياسات المقيدة (إلى جانب سلوك permissive).

إذا كنت تخطط لطبقات مثل مطابقة المستأجر بالإضافة إلى فحوص الدور بالإضافة إلى عضوية المشروع، قد تجعل السياسات المقيدة النية أوضح، لكنها تجعل أيضًا من السهل أن تُحجب إذا نسيت شرطًا.

كيف تعرف Postgres من هو المستخدم

RLS يحتاج قيمة "من ينادي" موثوقة. خيارات شائعة:

  • متغير جلسة يُضبط لكل طلب (مثلاً app.user_id و app.tenant_id).
  • مطالبات JWT تُح映 إلى إعدادات الجلسة بواسطة طبقة الـ API.
  • تبديل الدور (SET ROLE ...) لكل طلب، والذي قد يعمل لكنه يضيف عبء تشغيلي.

اختر نهجًا واحدًا وطبّقه في كل مكان. خلط مصادر الهوية عبر الخدمات مسار سريع للأخطاء المحيرة.

تسمية السياسات لتتمكن من تصحيحها لاحقًا

استخدم اصطلاحًا متوقعًا حتى تبقى تفريغات المخطط والسجلات قابلة للقراءة. على سبيل المثال: {table}__{action}__{rule}، مثل projects__select__tenant_match.

خطوة بخطوة: أضف RLS لجدول واحد وأثبت أنه يعمل

سَرِّع تأسيسات متعدد المستأجرين
حوّل نموذج المستأجرين إلى جداول حقيقية، أدوار ونقاط نهاية دون البدء من الصفر.
جرّب Koder.ai

إذا كنت جديدًا على 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)

سياسات القراءة لا تحمي الكتابات. أضف 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 إلى جداول أخرى.

أنماط سياسات ستعيد استخدامها في معظم تطبيقات SaaS

معظم الفرق تعتمد 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

تجنّب "إيقاف RLS للمشرفين". بدلًا من ذلك، أضف مخرجًا داخل السياسات، مثل app_is_admin()، حتى لا تمنح وصولًا كاملاً عن طريق الخطأ لدور خدمة مشترك.

الحذف الناعم وعلامات الحالة

إذا كنت تستخدم deleted_at أو status، أدرجها في سياسة SELECT (deleted_at is null). وإلا قد يتم "إحياء" الصفوف عبر قلب أعلام افترض التطبيق أنها نهائية.

UPSERT: اجعل WITH CHECK ودودًا

يجب أن يفي INSERT ... ON CONFLICT DO UPDATE بشرط WITH CHECK للصف بعد الكتابة. إذا كانت سياستك تتطلب created_by = app_user_id()، فتأكد أن upsert يضع created_by عند الإدراج ولا يكتبه فوقه عند التحديث.

إذا كنت تولد كود الـ backend، فهذه الأنماط تستحق أن تتحول إلى قوالب داخلية حتى تبدأ الجداول الجديدة بإفتراضات آمنة بدلًا من صفحة فارغة.

أخطاء شائعة في RLS تجعل التصحيح مؤلمًا

RLS رائعة حتى تفعل تفصيلة صغيرة فتبدو PostgreSQL "يخفي أو يظهر" بيانات بشكل عشوائي. الأخطاء أدناه تهدر معظم الوقت.

أخطاء تسبب تسربًا صامتًا للمستأجر

الفخ الأول هو نسيان WITH CHECK على الإدراج والتحديث. USING يتحكم بما ترى، لا بما يُسمح بإنشائه. بدون WITH CHECK، يمكن لخطأ في التطبيق كتابة صف لمستأجر خاطئ، وربما لا تلاحظه لأن نفس المستخدم لا يمكنه قراءته.

فخ شائع آخر هو "الانضمام الماسِر". تقوم بتصفية projects بشكل صحيح، ثم تنضم إلى invoices أو notes أو files التي ليست محمية بنفس الطريقة. الحل صارم لكنه واضح: كل جدول يمكنه إفشاء بيانات مستأجر يحتاج سياسته الخاصة، ولا يجب أن تعتمد views على أن جدولًا واحدًا فقط آمن.

أنماط الفشل الشائعة تظهر مبكرًا:

  • توجد سياسة قراءة لكن سياسة الكتابة تفتقد WITH CHECK.
  • شرط السياسة يستخدم انضمامًا إلى جدول آخر غير محمي.
  • يتم فرض الوصول في view، لكن الجدول الأساسي ما زال مفتوحًا.
  • تعتمد على أن "التطبيق دائمًا يضبط tenant_id"، ومهمة خلفية واحدة تنسى.
  • تختبر بدور superuser، لذا لا ترى السلوك الحقيقي.

أخطاء تجعل السلوك محيرًا

السياسات التي تشير إلى نفس الجدول (مباشرة أو عبر view) يمكن أن تخلق مفاجآت تكرارية. قد تتحقق السياسة من العضوية باستعلام view يقرأ الجدول المحمي مرة أخرى، مما يؤدي إلى أخطاء، استعلامات بطيئة، أو سياسة لا تطابق أبدًا.

إعداد الأدوار مصدر آخر للالتباس. ملاك الجداول والأدوار المرتفعة يمكنهم تجاوز RLS، لذا تمر اختباراتك بينما يفشل المستخدمون الحقيقيون (أو العكس). اختبر دائمًا بالدور منخفض الامتياز الذي يستخدمه تطبيقك.

كن حذرًا مع الدوال ذات SECURITY DEFINER. تعمل بصلاحيات مالك الدالة، لذا مساعد مثل current_tenant_id() قد يكون آمنًا، لكن دالة "تسهيلية" تقرأ بيانات قد تقرأ عبر المستأجرين ما لم تصممها لاحترام RLS.

ضبط search_path آمن داخل دوال security definer مهم أيضًا. إن لم تفعل، قد تلتقط الدالة كائنًا مختلفًا بنفس الاسم، وقد تشير منطق السياسة بهدوء إلى الشيء الخطأ اعتمادًا على حالة الجلسة.

تصحيح RLS: طرق عملية لرؤية ما يحدث

صمّم السياسات قبل البرمجة
خطط لأدوارك، إعدادات الجلسة، وقواعد السياسات قبل توليد الكود.
استخدام التخطيط

أخطاء 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);
  • نفّذ نفس SQL الذي نفّذه تطبيقك (بما في ذلك الوسائط)
  • تأكد مما تراه Postgres: 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 في الإنتاج

عامل RLS مثل حزام الأمان، ليس مفتاح ميزة قابلة للتشغيل. الأخطاء الصغيرة تتحول إلى "الجميع يمكنهم رؤية بيانات الجميع" أو "كل شيء يرجع صفر صفوف".

شكل نموذج البيانات والسياسات

تأكد أن تصميم جدولك وقواعد سياساتك تطابق نموذج المستأجرين.

  • كل جدول مملوك للمستأجر يجب أن يحتوي على مفتاح مستأجر واضح (عادة tenant_id). إذا لم يفعل، اكتب سببًا لذلك (مثل جداول مرجعية عالمية).
  • فعّل RLS على كل جدول مملوك للمستأجر، ليس فقط الجداول "الرئيسية". إذا كانت بعض المسارات لا يجب تجاوزها أبدًا، فكر في FORCE ROW LEVEL SECURITY على تلك الجداول.
  • قسّم قواعد القراءة والكتابة. تستخدم القراءات USING. يجب أن تتضمن الكتابات WITH CHECK حتى لا تسمح الإدراجات والتحديثات بنقل الصف إلى مستأجر آخر.
  • حافظ على شروط السياسات صديقة للفهارس. إذا كانت السياسات تصفِّى بـ tenant_id أو تنضم عبر جداول العضوية، أضف الفهارس المطابقة.

سيناريو صحة بسيط: مستخدم من المستأجر A يمكنه قراءة فواتيره، يمكنه إدراج فاتورة فقط للمستأجر A، ولا يمكنه تحديث فاتورة لتغيير tenant_id.

الأدوار، الأداء، والاختبارات

RLS قوية بقدر الأدوار التي يستخدمها تطبيقك.

  • تأكد ألا يتصل التطبيق كـ superuser أو مالك الجدول أو أي دور يملك 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 معطلاً أثناء اختبارك في المرحلة التجريبية.
  • فعّل RLS، ثم فرضه، وعندها فقط أزل فلاتر التطبيق القديمة.

دخول الدعم يُتعامل معه عادة بدور ضيق لكسر القفل، وليس بتعطيل RLS. احتفظ به منفصلًا عن حسابات الدعم العادية واجعل استخدامه صريحًا عند الحاجة.

وثّق القواعد حتى لا تنحرف السياسات: أي متغيرات جلسة يجب ضبطها (user_id, org_id)، أي جداول يجب أن تحمل org_id, ما معنى "عضو"، وبعض أمثلة SQL التي يجب أن تعيد 0 صف عندما تُشغّل كمنظمة خاطئة.

الخطوات التالية: تطبيق RLS بأمان وجعله قابلاً للصيانة

تسهّل RLS عندما تعاملها كتغيير منتج. طبّقها دفعات صغيرة، أثبت السلوك بالاختبارات، واحتفظ بسجل واضح لسبب وجود كل سياسة.

خطة طرح تعمل عادة:

  • ابدأ بجدول واحد بملكية مستأجر واضحة (مثلاً projects) وقم بتأمينه.
  • أضف اختبارات تغطي القراءات والكتابات المسموح بها والممنوعة لعدد قليل من الأدوار (مالك، عضو، خارجي).
  • وسّع على دفعات (مجال ميزة واحد في كل مرة) يمكنك تصحيحه في جلسة واحدة.
  • رصد أخطاء الصلاحيات أثناء الطرح وانشر في نافذة مخاطر منخفضة.

بعد استقرار الجدول الأول، اجعل تغييرات السياسات متعمدة. أضف خطوة مراجعة السياسات للترحيلات، وضمن ملاحظة قصيرة عن النية (من ينبغي أن يصل إلى ماذا ولماذا) ومعها تحديث اختبار مطابق. هذا يمنع "إضافة OR آخر" التي تتحول تدريجيًا إلى ثغرة.

إذا كنت تتحرك بسرعة، أدوات مثل Koder.ai (koder.ai) يمكن أن تساعدك في توليد نقطة بداية Go + PostgreSQL عبر المحادثة، ثم يمكنك وضع سياسات RLS والاختبارات بنفس انضباط قاعدة الكود المولد يدويًا.

أخيرًا، احتفظ بحواجز أمان أثناء الطرح. التقط لقطات قبل ترحيلات السياسات، مرّن التراجع حتى يصبح مملًا، واحتفظ بممر صغير لكسر القفل للدعم لا يعطل RLS عبر النظام بأكمله.

الأسئلة الشائعة

What security problem does RLS actually solve in a SaaS app?

RLS يجعل PostgreSQL يفرض أي الصفوف المرئية أو القابلة للكتابة لطلب ما، بحيث لا يعتمد عزل المستأجرين على أن يتذكّر كل مسار في الواجهة وضع فلتر WHERE tenant_id = .... الفائدة الرئيسية هي تقليل أخطاء "فحص واحد مفقود" عندما يكبر التطبيق وتتكاثر الاستعلامات.

When is RLS worth the extra complexity?

تفيد عندما تكون قواعد الوصول متناسقة ومبنية على الصفوف، مثل عزل المستأجرين أو الوصول المبني على العضويات، ولديك العديد من مسارات الاستعلام (بحث، تصدير، شاشات إدارة، مهام خلفية). عادة لا تفيد عندما تكون القواعد على مستوى الحقل مع استثناءات كثيرة أو عندما تحتاج تقارير واسعة النطاق لقراءة عبر مستأجرين متعددة.

What does RLS NOT protect me from?

استخدم RLS لرؤية الصفوف والقيود الأساسية على الكتابة، ثم استخدم أدوات أخرى للحاجات الأخرى. خصوصية الأعمدة عادة تتطلب views وصلاحيات أعمدة، والقواعد التجارية المعقدة (مثل ملكية الفوترة أو تدفقات الموافقة) تظل من مهام منطق التطبيق أو قيود قاعدة بيانات مصممة بعناية.

What’s the safest way to start using RLS if I’m new to it?

أنشئ دورًا منخفض الامتياز للـ API (ليس مالك الجدول)، فعّل RLS، ثم أضف سياسة SELECT وسياسة INSERT/UPDATE مع WITH CHECK. اضبط متغير جلسة مخصص للطلب (مثل app.current_tenant) وتحقق أن تغييره يغيّر الصفوف التي يمكنك رؤيتها وكتابتها.

How should my app tell Postgres which tenant and user is making the request?

الافتراض الشائع هو متغير جلسة لكل طلب، يُضبط في بداية المعاملة، مثل app.tenant_id و app.user_id. المفتاح هو الاتساق: كل مسار برمجي (طلبات الويب، الوظائف، السكربتات) يجب أن يضبط نفس القيم التي تتوقعها السياسات، وإلا ستحصل على سلوك "صفوف صفر" محيّر.

What’s the difference between USING and WITH CHECK in an RLS policy?

USING يتحكم في أي الصفوف الموجودة مرئية ويمكن استهدافها لـ SELECT و UPDATE و DELETE. WITH CHECK يتحكم في أي صفوف جديدة أو معدلة مسموح بها خلال INSERT و UPDATE، لذا يمنع "الكتابة إلى مستأجر آخر" حتى لو مرر التطبيق خاطئًا.

Why do people keep saying “don’t forget WITH CHECK”?

لأن إضافة USING فقط تسمح للرؤية لكن لا تمنع إنشاء صفوف في مستأجر آخر. نقطة الشيوع: إذا لم تضف WITH CHECK، يمكن لنقطة نهاية خاطئة إدخال صف إلى مستأجر خاطئ ولن تلاحظه لأن نفس المستخدم قد لا يستطيع قراءة ذلك الصف لاحقًا. دائمًا أضف قاعدة WITH CHECK مطابقة لقراءة المستأجر للكتابات.

How should I structure my schema so RLS policies stay simple and fast?

تجنّب الانضمامات داخل السياسات بوضع مفتاح المستأجر (مثل org_id) مباشرة على الجداول المملوكة للمستأجر حتى لو كانت تشير إلى جدول آخر يمتلكه المستأجر. أضف جداول عضوية صريحة (org_memberships وربما project_memberships) حتى تتطلب السياسات نظرة مرجعية مفهرسة واحدة بدلًا من استنتاج معقّد.

How do I debug “RLS is hiding my data” without guessing?

أعد إنشاء نفس سياق الجلسة الذي يستخدمه التطبيق عبر ضبط نفس الدور وإعدادات الجلسة ثم نفّذ نفس الاستعلام بالضبط. تأكد أن RLS مفعّل وافحص pg_policies لترى تعابير USING و WITH CHECK المطبقة، لأن أخطاء RLS عادة تنتج عن سياق هوية مفقود أكثر من "SQL خاطئ".

If I generate my backend (for example with Koder.ai), do I still need to design RLS carefully?

نعم، لكن اعتبر الكود المُولّد نقطة بداية لا كحل أمني نهائي. حتى لو استخدمت Koder.ai لتوليد Backend بـ Go + PostgreSQL، لا بد من تحديد نموذج المستأجرين، ضبط هوية الجلسة بشكل متسق، وإضافة سياسات واختبارات بعناية حتى لا تُشحن جداول جديدة بدون الحماية الصحيحة.

المحتويات
المشكلة الحقيقية التي تحاول RLS حلها في تطبيقات SaaSمتى تبسط RLS التفويض (ومتى تضيف ألمًا)أساسيات نموذج البيانات التي تجعل سياسات RLS قابلة للإدارةكيف تعمل سياسات RLS، بدون الأجزاء المخيفةخطوة بخطوة: أضف RLS لجدول واحد وأثبت أنه يعملأنماط سياسات ستعيد استخدامها في معظم تطبيقات SaaSأخطاء شائعة في RLS تجعل التصحيح مؤلمًاتصحيح RLS: طرق عملية لرؤية ما يحدثقائمة فحص سريعة قبل الإطلاق لاستخدام RLS في الإنتاجمثال: تطبيق مشاريع متعدد المستأجرين مع وصول مبني على العضويةالخطوات التالية: تطبيق RLS بأمان وجعله قابلاً للصيانةالأسئلة الشائعة
مشاركة
Koder.ai
أنشئ تطبيقك الخاص مع Koder اليوم!

أفضل طريقة لفهم قوة Koder هي تجربتها بنفسك.

ابدأ مجاناًاحجز عرضاً توضيحياً
tenant_id