سلسلة فلاتر الأمان
سلسلة فلاتر الأمان
كل طلب HTTP يصل إلى تطبيق Spring Boot يمرّ عبر خط أنابيب من فلاتر servlet قبل أن يلمس وحدات التحكم الخاصة بك. يندمج Spring Security في هذا الخط بسلسلته المرتّبة الخاصة من الفلاتر — سلسلة فلاتر الأمان (Security Filter Chain). فهم هذه السلسلة أمر جوهري: فهي من يحدّد ما يحدث للطلبات غير المصادَق عليها، وكيف تُستخرج بيانات الاعتماد، وكيف تُدار الجلسات، وما هي استجابات الخطأ المُرسَلة. إن بدا سلوك أمني ما غير متوقّع فسلسلة الفلاتر هي دائمًا أول مكان عليك النظر فيه.
كيف تعمل فلاتر Servlet
تُعرّف مواصفة Java Servlet واجهةً باسم Filter بتابع وحيد هو doFilter(request, response, chain). يستطيع الفلتر فحص الطلب وتعديله، ثم استدعاء chain.doFilter() لتمرير التحكم إلى الفلتر التالي في السلسلة، ثم فحص الاستجابة وتعديلها في طريق العودة. الفلاتر مرتّبة وكل فلتر يلفّ الذي يليه — مكوّنةً سلسلةً حقيقية.
يسجّل Spring Security فلترًا واحدًا في سلسلة فلاتر حاوية servlet العادية يُسمّى DelegatingFilterProxy. يفوّض هذا الوكيل كل الأعمال إلى حبّة Spring مُدارة باسم springSecurityFilterChain. خلف تلك الحبّة يقبع FilterChainProxy الذي يحتوي على مثيل أو أكثر من SecurityFilterChain — كل منها قائمة من فلاتر Spring Security المحدّدة.
الترتيب الافتراضي للفلاتر
عند إضافة spring-boot-starter-security إلى المشروع يُهيّئ Spring Boot تلقائيًا SecurityFilterChain بالفلاتر التالية (مختصرة، بالترتيب):
DisableEncodeUrlFilter— يمنع تسرّب معرّفات الجلسات في عناوين URL.WebAsyncManagerIntegrationFilter— يُوزّعSecurityContextعلى خيوط async.SecurityContextHolderFilter— يُحمّلSecurityContextمن المستودع (عادةً جلسة HTTP) في بداية الطلب ويُنظّفه بعدها.HeaderWriterFilter— يُضيف ترويسات استجابة HTTP المتعلقة بالأمان (X-Frame-Options وX-Content-Type-Options وغيرها).CsrfFilter— يتحقق من رموز CSRF في الطلبات التي تغيّر الحالة.LogoutFilter— يعترض عنوان URL لتسجيل الخروج ويُنظّف سياق الأمان والجلسة.UsernamePasswordAuthenticationFilter— يعالج طلبات POST لتسجيل الدخول عبر النموذج.DefaultLoginPageGeneratingFilter— يخدم نموذج تسجيل الدخول المدمج (يُزال عند تقديم نموذجك الخاص).BearerTokenAuthenticationFilter— يستخرج رموز JWT أو الرموز المعتمة من ترويسةAuthorization: Bearer(موجود فقط عند تهيئة OAuth2 Resource Server).RequestCacheAwareFilter— يُعيد تشغيل الطلب الأصلي بعد نجاح إعادة توجيه تسجيل الدخول.SecurityContextHolderAwareRequestWrapper— يلفّ الطلب لكشف Servlet security API.AnonymousAuthenticationFilter— إذا لم يُعيَّن أي مصادقة حتى الآن يُدخل مدير مجهول حتى لا يرى الكود اللاحق null أبدًا.ExceptionTranslationFilter— يلتقطAccessDeniedExceptionوAuthenticationExceptionويحوّلهما إلى استجابات HTTP (401/302 أو 403).AuthorizationFilter— يُطبّق قواعد الوصول التي أعلنتها فيSecurityFilterChain.
كيف يتدفق الطلب عبر السلسلة
لنتأمّل طلب GET غير مصادَق عليه يستهدف موردًا محميًا:
- يبحث
SecurityContextHolderFilterعنSecurityContextموجود في الجلسة — لا يجد شيئًا فيضع سياقًا فارغًا. - تبحث فلاتر المصادقة (UsernamePassword وBearer وغيرها) عن بيانات اعتماد في الطلب — لا تجد شيئًا فتمرّ دون تعيين مدير.
- يرى
AnonymousAuthenticationFilterأنه لا مصادقة مضبوطة فيُدخلAnonymousAuthenticationToken. - يُقيّم
AuthorizationFilterالقاعدة المرتبطة بعنوان URL المطلوب (مثلauthenticated()) في مواجهة الرمز المجهول — فيرفض الوصول. - يلتقط
ExceptionTranslationFilterالـAccessDeniedException. بما أن المدير الحالي مجهول يتعامل معه باعتباره مصادقة مفقودة ويُعيد توجيه المتصفح إلى صفحة تسجيل الدخول (أو يُعيد 401 للواجهات البرمجية عديمة الحالة).
الآن لنتأمّل طلب POST إلى /login ببيانات اعتماد صحيحة:
- يُطابق
UsernamePasswordAuthenticationFilterعنوان URL ويستخرج اسم المستخدم وكلمة المرور ثم يفوّض إلىAuthenticationManager. - يُحمّل
AuthenticationManagerالمستخدم عبرUserDetailsServiceويتحقق من كلمة المرور ويُعيد كائنAuthenticationممتلئًا. - يخزّن الفلتر هذا في
SecurityContextويحفظ السياق في الجلسة. - يُعيد
AuthenticationSuccessHandlerالمُهيَّأ توجيه المستخدم إلى وجهته الأصلية.
سلاسل SecurityFilterChain متعددة
يسمح Spring Security 6 بتعريف حبّات SecurityFilterChain متعددة، كل منها مُطابَقة لنمط عنوان URL مختلف. هذا هو الأسلوب الاصطلاحي لتطبيق قواعد أمان مختلفة على — مثلًا — واجهة REST البرمجية (JWT عديمة الحالة) وواجهة المستخدم الإدارية (تسجيل دخول بالنموذج مع حالة).
يُقيّم FilterChainProxy مُطابِق securityMatcher لكل سلسلة بترتيب @Order ويستخدم أول سلسلة مُطابِقة. لا يُستشار ما بعدها. نسيان تعيين مُطابِق على سلسلة ذات أولوية أدنى يجعلها فعليًا تلتقط كل شيء وقد تبتلع طلبات مخصّصة لسلاسلك الأخرى.
@Order سيرمي Spring استثناءً عند بدء التشغيل. اعطِ دائمًا ترتيبًا صريحًا وفريدًا لكل حبّة SecurityFilterChain في إعداد متعدد السلاسل.
إضافة فلتر مخصّص
يمكنك إدراج فلترك الخاص في موضع محدد داخل السلسلة باستخدام addFilterBefore أو addFilterAfter أو addFilterAt. حالة استخدام شائعة هي التحقق من ترويسة طلب مخصّصة قبل أن تعمل فلاتر المصادقة القياسية:
يضمن التوسّع من OncePerRequestFilter تشغيل فلترك مرةً واحدةً بالضبط لكل طلب — وهذا مهمّ في حاويات servlet التي قد تُرسل داخليًا (مثل forward وerror dispatches).
فحص السلسلة أثناء التشغيل
أثناء التطوير يمكنك تسجيل كل فلتر يعالج طلبًا بضبط علامة debug في Spring Security:
أو على مستوى التعليق التوضيحي:
debug = true في الإنتاج أبدًا. فهو يُسجّل تفاصيل الطلبات الكاملة — الترويسات والمعاملات وقرارات الأمان — مما قد يكشف معلومات حساسة في مجمّعات السجلات.
سياق الأمان ونشره عبر الخيوط
يُخزَّن SecurityContext في ThreadLocal افتراضيًا. هذا يعني أنه متاح في أي مكان في مكدس استدعاءات نفس الخيط — وحدات تحكمك وخدماتك ومستودعاتك — دون تمريره صراحةً. لكن إن أنتجت خيطًا جديدًا (مثل CompletableFuture أو @Async أو خيوط افتراضية) فلن يُورَث السياق تلقائيًا. يوفّر Spring Security كلًّا من DelegatingSecurityContextExecutor واستراتيجية MODE_INHERITABLETHREADLOCAL للتعامل مع ذلك، وهو ما يُغطّى في درس أمان مستوى التابع.
الخلاصة
سلسلة فلاتر الأمان هي العمود الفقري لـ Spring Security. كل طلب يُعترَض بواسطة DelegatingFilterProxy ويُوجَّه عبر FilterChainProxy ويُعالَج بقائمة مرتّبة من الفلاتر التي تُحمّل سياق الأمان وتُحاول المصادقة وتحتاط بالمجهول ثم تُطبّق التفويض. تتيح لك السلاسل المتعددة تطبيق استراتيجيات أمان مختلفة على نطاقات عناوين URL مختلفة. تندرج الفلاتر المخصّصة بسهولة في هذه السلسلة في أي موضع. في الدرس التالي ستُحدّد الفرق بين المصادقة (من أنت؟) والتفويض (ماذا يمكنك أن تفعل؟).