الأمان على مستوى الدوال
الأمان على مستوى الدوال
إنّ SecurityFilterChain الذي هيّأته في الدروس السابقة يحرس مسارات URL — وهو بمثابة السياج المحيطي. لكن ماذا يحدث حين يستدعي طرفا HTTP مختلفان نفس دالة الخدمة، أو حين تستدعي مهمة مجدولة داخلية منطق عمل يجب أن يُطبّق قواعد الوصول رغم ذلك؟ لا يستطيع تفويض الوصول على مستوى URL الإجابة على هذا السؤال. الأمان على مستوى الدوال ينقل حدود التطبيق إلى طبقة الخدمة، وهو المكان الذي ينتمي إليه لأي تطبيق يتجاوز البساطة.
يوفّر Spring Security تعليقَين توضيحيَّين رئيسيَّين لهذا الغرض: @PreAuthorize و@Secured. يغطي هذا الدرس كليهما بعمق — ما يفعله كل منهما، وكيف يختلفان، ومتى تُفضّل أحدهما على الآخر، والتداعيات الأمنية لكل خيار.
تفعيل الأمان على مستوى الدوال
أمان الدوال خاصية اختيارية. أضف @EnableMethodSecurity إلى أي فئة إعداد (فئة التطبيق الرئيسية أو فئة إعداد أمان مخصصة). في Spring Security 6 يحلّ هذا التعليق محل @EnableGlobalMethodSecurity القديم.
new)، يُتجاهَل التعليق بصمت. الطبيعة الاختيارية تُبقي السلوك واضحًا وقابلًا للاختبار.
@PreAuthorize — تحكم دقيق قائم على التعابير
يُقيّم @PreAuthorize تعبير Spring Expression Language (SpEL) قبل تشغيل جسم الدالة. إذا أعاد التعبير false، يرمي Spring استثناء AccessDeniedException ولا تُدخل الدالة أبدًا. هذا هو التعليق الذي ينبغي اللجوء إليه في كل مشروع جديد تقريبًا.
بنية #article في المثال الثالث هي طريقة SpEL للإشارة إلى معامل دالة بالاسم. تمنحك authentication.principal كائن المستخدم المُصادَق عليه حاليًا (تنفيذك المخصص لـ UserDetails). هذا يعني أن منطق التفويض يمكنه فحص بيانات نطاق حقيقية — لا مجرد سلاسل أدوار — وهذا ما يجعل @PreAuthorize بالغ القوة.
تعابير SpEL الشائعة
hasRole('ADMIN')— يتحقق من وجودROLE_ADMINفي صلاحيات المستخدم الممنوحة (يُضيف Spring البادئةROLE_تلقائيًا).hasAnyRole('EDITOR','VIEWER')— صحيح إذا كان المستخدم يملك أيًا من الأدوار المدرجة.hasAuthority('ARTICLE_DELETE')— يتحقق من سلسلة الصلاحية بالضبط؛ مفيد لأنظمة الأذونات الدقيقة التي تتجاوز أسماء الأدوار.isAuthenticated()— صحيح لأي مستخدم مسجّل دخوله (غير مجهول).isAnonymous()— صحيح فقط للطلبات غير المُصادَق عليها.authentication.name == 'system'— مقارنة اسم المستخدم مباشرةً.#param— الإشارة إلى معامل دالة باسمهparam.@myBean.check(#param)— التفويض إلى دالة حبّة Spring لمنطق معقد.
@Component SecurityService — وأشر إليها في SpEL: @PreAuthorize("@securityService.isProjectMember(#projectId, authentication)"). هذا يُبقي تعليقاتك مقروءة ومنطقك قابلًا لاختبارات الوحدة.
@PostAuthorize — التفويض بناءً على القيمة المُعادة
أحيانًا لا تعرف ما إذا كان ينبغي منح الوصول حتى بعد تحميل الدالة للبيانات من قاعدة البيانات. يُشغّل @PostAuthorize تعبير SpEL الخاص به بعد إعادة الدالة للنتيجة، مع إتاحة القيمة المُعادة عبر returnObject.
@PostAuthorize أبدًا على دالة لها آثار جانبية (كتابة، بريد إلكتروني، مدفوعات) — استخدم @PreAuthorize مع فحص أذونات مُحمَّل مسبقًا بدلًا من ذلك.
@Secured — فحص الأدوار البسيط
@Secured تعليق قديم يقبل قائمة من سلاسل الصلاحيات. تُنفَّذ الدالة فقط إذا كان المستخدم المُصادَق عليه يملك واحدة منها على الأقل. خلافًا لـ @PreAuthorize، لا يدعم SpEL — لا مراجع للمعاملات، ولا تفويض للحبوب، ولا تعابير مركّبة.
لاحظ أن البادئة الكاملة ROLE_ مطلوبة مع @Secured، في حين يُضيفها hasRole() في SpEL تلقائيًا.
لتفعيل @Secured يجب تمكينها صراحةً:
@PreAuthorize مقابل @Secured — متى تستخدم أيًّا منهما
- استخدم
@PreAuthorizeفي كل الكود الجديد. فهو أكثر تعبيرًا، يدعم SpEL، يعمل مع الأدوار والصلاحيات الدقيقة، ويمكنه فحص معاملات الدوال. - استخدم
@Securedفقط حين تحتاج إلى دعم قاعدة كود قديمة تستخدمه بالفعل، أو حين تريد إشارة بصرية بأن الدالة تُجري فحص دور بسيطًا لا أكثر.
تطبيق التعليقات على مستوى الفئة
يمكنك وضع @PreAuthorize على فئة لتعيين سياسة افتراضية لجميع دوالها، ثم تجاوزها لكل دالة حسب الحاجة:
اختبار أمان الدوال
يوفّر Spring Security Test التعليقَين @WithMockUser و@WithUserDetails لمحاكاة سياقات مُصادَقة في اختبارات الوحدة. يتيح لك هذا التحقق من قواعد التفويض دون خادم قيد التشغيل.
@PreAuthorize المفقود هو ثغرة أمنية صامتة. اكتب دائمًا اختبارات تُؤكد رفض المستخدمين غير المُخوَّلين، لا فقط السماح للمُخوَّلين.
مزالق عملية
- الاستدعاء الذاتي يتجاوز الوكيل. إذا استدعت دالة في نفس الفئة دالةً أخرى مُعلَّقة مباشرةً (لا عبر وكيل Spring)، لا يُقيَّم التعليق. استخرج الدالة إلى حبّة منفصلة إذا احتجت تطبيقها على الاستدعاءات الداخلية.
- الواجهة مقابل التنفيذ. ضع التعليقات على دوال فئة التنفيذ، لا على دوال الواجهة، إلا إذا كنت تستخدم وكلاء قائمة على الواجهة (نادر في Spring Boot). يدعم Spring Security 6 كليهما، لكن التعليق على التنفيذ أأمن وأوضح.
- لا تستبدل أمان URL. أمان الدوال يكمّل تفويض URL — لا يحلّ محلّه. احتفظ بكلتا الطبقتين. قواعد URL ترفض الطلبات السيئة عند المحيط؛ وقواعد الدوال تُطبّق ثوابت العمل في أعماق المكدس.
الخلاصة
يتيح الأمان على مستوى الدوال إرفاق قواعد تفويض مباشرةً بدوال الخدمة باستخدام @PreAuthorize (SpEL، تعبيري، مُفضَّل) أو @Secured (سلاسل أدوار بسيطة، متوافق مع الكود القديم). فعّل الميزة بـ @EnableMethodSecurity على فئة إعداد. استخدم مراجع معاملات SpEL وتفويض الحبوب للحفاظ على قابلية اختبار القواعد المعقدة. اقرن دائمًا أمان الدوال بتفويض URL — الطبقتان معًا توفران دفاعًا متعمقًا. في الدرس القادم ستجمع كل شيء معًا بتأمين تطبيق ويب كامل من البداية إلى النهاية.