المصادقة مقابل التفويض
المصادقة مقابل التفويض
يقوم كل نظام محمي على ركيزتين متمايزتين: المصادقة (من أنت؟) والتفويض (ما الذي يُسمح لك بفعله؟). هذان السؤالان متتاليان منطقيًا ولا يجوز الخلط بينهما أبدًا. إن الخلط بينهما هو أحد أكثر مصادر الثغرات الأمنية شيوعًا في التطبيقات الحقيقية — فقاعدة تفويض مُهيَّأة بشكل خاطئ تتيح لمستخدم مصادَق عليه الوصول إلى مورد لا يجب أن يراه تُعدّ اختراقًا للبيانات لا مجرد فشل في تسجيل الدخول.
يتولى Spring Security معالجة كلا الأمرين، لكن عبر نظامين فرعيين منفصلَين ومُحدَّدَي المعالم. يمنحك هذا الدرس الأساس المفاهيمي وواجهة Spring Security 6 العملية لكل منهما.
المصادقة — إثبات الهوية
المصادقة هي عملية التحقق من أن الكيان الرئيسي (مستخدم أو خدمة أو جهاز) هو فعلًا من يدّعي أنه إياه. الآلية الكلاسيكية هي اسم المستخدم وكلمة المرور، لكن المفهوم أوسع من ذلك: رمز JSON Web Token، وشهادة X.509 للعميل، ورمز وصول OAuth 2، وتأكيد SAML — كلها أشكال من بيانات الاعتماد التي يمكن استخدامها للمصادقة على كيان رئيسي.
في Spring Security تكون نتيجة المصادقة الناجحة كائنًا من نوع Authentication مخزَّنًا في SecurityContext. يحتوي على ثلاثة عناصر:
- الكيان الرئيسي (Principal) — من جرى التحقق منه (عادةً كائن
UserDetails). - بيانات الاعتماد (Credentials) — ما استُخدم لإثبات الهوية (تُمحى بعد المصادقة لأغراض أمنية).
- الصلاحيات (Authorities) — مجموعة الأذونات الممنوحة (كائنات
GrantedAuthority) المرتبطة بهذا الكيان.
يمكنك فحص الكيان الرئيسي المصادَق عليه حاليًا في أي مكان من تطبيقك:
Authentication في ThreadLocal، لذا يكون متاحًا تلقائيًا طوال عمر طلب الـ servlet دون الحاجة إلى تمريره كمعامل. في التطبيقات التفاعلية (WebFlux) يُخزَّن السياق في الخط الأنابيبي التفاعلي عوضًا عن ذلك — لا تفترض أبدًا دلالات thread-local هناك.
التفويض — تطبيق ما هو مسموح به
يعمل التفويض بعد المصادقة. بمجرد أن يعرف النظام هوية المُستدعي، يجب أن يقرر ما إذا كان يجوز لهذا المُستدعي تنفيذ الإجراء المطلوب على المورد المطلوب. يُعبّر Spring Security عن التفويض بصلاحيات ممنوحة (أدوار وأذونات) تُفحص مقابل قواعد تُهيّئها أنت.
ثمة مكانان رئيسيان يُطبّق فيهما Spring Security التفويض:
- طبقة HTTP — قواعد تُطبَّق على الطلبات الواردة قبل وصولها إلى وحدة التحكم، تُهيَّأ عبر
SecurityFilterChain. - طبقة الدوال — تعليقات توضيحية مثل
@PreAuthorizeتُطبَّق على دوال الخدمة أو وحدة التحكم، وتُطبَّق عبر AOP.
يبدو إعداد التفويض البسيط في Spring Security 6 على النحو التالي:
ROLE_. عندما تستدعي hasRole("ADMIN") يبحث Spring Security عن صلاحية باسم ROLE_ADMIN — يُضيف البادئة تلقائيًا. عندما تستدعي hasAuthority("API_READ") يبحث عن النص الحرفي ذاته. استخدم الأدوار للوظائف العامة (ADMIN, USER, MANAGER) والصلاحيات الدقيقة للقدرات المحددة (INVOICE_WRITE, REPORT_READ).
لماذا يهم الترتيب — ولماذا يجب أن يبقيا منفصلَين
المصادقة تعمل دائمًا أولًا. إذا لم يُمكن التحقق من هوية الطلب فلا شيء للتفويض — يُرفض الطلب بـ 401 Unauthorized. فقط بعد وجود كائن Authentication صالح في SecurityContext تُفعَّل قواعد التفويض. أما رفض التفويض فيُعيد 403 Forbidden.
تحمل هاتان الرموز دلالات محددة يعتمد عليها عملاء واجهة برمجية التطبيقات:
401— "لم تُثبت هويتك. قدّم بيانات اعتماد صالحة."403— "أعرف هويتك، لكن لا يُسمح لك بهذا."
403 Forbidden لطلب غير مصادَق على نقطة نهاية محمية، فأنت تُخبر المهاجمين بأن نقطة النهاية موجودة ومحمية. أعد دائمًا 401 أولًا، واحتفظ بـ 403 للحالات التي يكون فيها المستخدم مصادَقًا لكنه غير مُخوَّل. يتعامل Spring Security مع هذا بشكل صحيح افتراضيًا — لا تتجاوز AuthenticationEntryPoint وAccessDeniedHandler إلا إذا كنت تفهم التبعات.
المصادقة والتفويض في واجهة REST API عديمة الحالة
تخزّن تطبيقات الويب التقليدية كائن Authentication في جلسة HTTP (ذات حالة). أما واجهات REST API عديمة الحالة — والخدمات المصغّرة — فتتبع مقاربة مختلفة: يُقدّم العميل رمزًا مكتفيًا بذاته (عادةً JWT) مع كل طلب، ويتحقق منه الخادم دون الرجوع إلى أي مخزن جلسات.
في هذا النموذج تتحول الركيزتان قليلًا:
- المصادقة تحدث عند خطوة التحقق من الرمز: يُتحقق من توقيع JWT، ويُفحص انتهاء صلاحيته، وتُستخرج هوية الموضوع والادعاءات المضمّنة لإعادة بناء كائن
AuthenticationفيSecurityContext. - التفويض ثم يسير بشكل متطابق: تُفحص الصلاحيات المضمّنة في الرمز (أو المُجلَبة من قاعدة البيانات) مقابل القواعد.
العقد الداخلي لـ Spring Security
داخليًا يفصل Spring Security المسألتين على مستوى واجهة برمجة التطبيقات:
AuthenticationManager/AuthenticationProvider— مسؤولان عن المصادقة. يأخذان رمزًا غير مصادَق (مثلUsernamePasswordAuthenticationTokenببيانات اعتماد خام)، يتحققان منه، ويُعيدان كائنAuthenticationموثوقًا ومكتملًا.AccessDecisionManager/AuthorizationManager— مسؤولان عن التفويض. يتلقيان كائنAuthenticationالمصادَق عليه وكائنًا محميًا (طلب HTTP أو استدعاء دالة) ويقرران منح الوصول أو رفضه.
ستُهيّئ هذه الواجهات وتُوسّعها طوال هذا البرنامج التعليمي. الخلاصة المعمارية الآن: تصميم Spring Security يُطبّق الفصل بين المصادقة والتفويض على مستوى الأنواع، مما يجعل الخلط بينهما عن طريق الخطأ أصعب.
الخلاصة
تُجيب المصادقة عن سؤال "من أنت؟" وتُنتج كائن Authentication موثوقًا في SecurityContext. يُجيب التفويض عن سؤال "ما الذي يجوز لك فعله؟" ويتحقق من صلاحيات ذلك الكائن مقابل القواعد التي تُعرّفها. المصادقة دائمًا أولًا؛ فشلها يُعطي 401، أما فشل التفويض فيُعطي 403. يُطبّق Spring Security الحد الفاصل بين المسألتين على مستوى واجهة برمجة التطبيقات، سواء في سلسلة مرشّح HTTP أو في طبقة AOP للأمان على مستوى الدوال. الحفاظ على وضوح هذا النموذج الذهني سيُجنّبك أكثر فئات الأخطاء الأمنية الناجمة عن سوء الإعداد شيوعًا.