المستخدمون وخدمة تفاصيلهم UserDetailsService
المستخدمون وخدمة تفاصيلهم UserDetailsService
لا يتحدث Spring Security مع قاعدة بياناتك مباشرةً. بدلًا من ذلك يُفوّض مهمة تحميل المستخدم إلى واجهة برمجية واحدة هي UserDetailsService. مهما كان المخزن الذي تستخدمه — خريطة في الذاكرة، أو قاعدة بيانات علائقية، أو LDAP، أو خدمة مصغّرة بعيدة — فإنك تُغلّفه في فئة تُطبّق هذه الواجهة ويعمل Spring Security بنفس الطريقة. هذا الفصل هو أحد أوضح القرارات التصميمية في الإطار، وفهمه هو مفتاح تخصيص عملية المصادقة.
العقود الأساسية
تحمل واجهتان تقريبًا كل الثقل:
UserDetailsService— واجهة بمنهج واحد. تُطبّقloadUserByUsername(String username)وتُعيد كائنUserDetails. يستدعيها Spring Security أثناء المصادقة.UserDetails— تُمثّل الكيان الرئيسي (principal). تكشف عن كلمة المرور المُشفَّرة ومجموعة كائناتGrantedAuthority(الأدوار والصلاحيات) وأربعة أعلام منطقية:isEnabledوisAccountNonExpiredوisAccountNonLockedوisCredentialsNonExpired.
إذا لم يكن اسم المستخدم موجودًا، يجب أن يرمي loadUserByUsername استثناء UsernameNotFoundException ولا يُعيد null أبدًا. إعادة null تتسبب في NullPointerException مدفون داخل الإطار — ارمِ الاستثناء دائمًا.
المستخدمون في الذاكرة — إعداد سريع للتطوير
أسرع طريقة لتهيئة المستخدمين هي توفير حبة InMemoryUserDetailsManager. هذا مناسب للعروض التوضيحية والاختبارات الآلية والتطوير المحلي — لكن ليس للإنتاج.
{bcrypt}$2a$...) أو أن تُمرَّر عبر حبة PasswordEncoder. إذا استخدمت User.withDefaultPasswordEncoder() ستظهر لك رسالة إهمال (deprecation warning) — هي للنماذج فحسب، ليس للكود الحقيقي. اربط دائمًا حبة PasswordEncoder صريحة.
مساعد roles("USER") هو اختصار نحوي: ينشئ كائن GrantedAuthority بالقيمة ROLE_USER. إذا احتجت أسماء صلاحيات لا تتبع اتفاقية البادئة ROLE_، استخدم .authorities("READ_REPORTS", "WRITE_REPORTS") بدلًا من ذلك.
تحميل المستخدمين من قاعدة البيانات — النمط الحقيقي
في تطبيق الإنتاج، يعيش مستخدموك في جدول قاعدة بيانات تديره JPA. تُطبّق UserDetailsService وتحقن مستودع Spring Data الخاص بك:
يكتشف Spring Security تلقائيًا حبة UserDetailsService الوحيدة ويربطها بمزوّد المصادقة. لا تحتاج إلى أي تهيئة إضافية لتوصيل الاثنين.
loadUserByUsername داخل آلية المصادقة في Spring Security، وهي خارج أي جلسة Hibernate مفتوحة. إذا حمّلت الأدوار بشكل كسول ستصطدم بـ LazyInitializationException وقت التشغيل. ضع علامة FetchType.EAGER على مجموعة الأدوار، أو استدعِ Hibernate.initialize(user.getRoles()) بينما المعاملة لا تزال مفتوحة، أو استخدم استعلام JPQL مع JOIN FETCH.
دمج UserDetailsService مع SecurityFilterChain
تربط UserDetailsService المخصّصة بتهيئة الأمان بحقنها وبناء AuthenticationProvider:
DaoAuthenticationProvider هو التطبيق القياسي الذي يستدعي loadUserByUsername، ثم يتحقق من كلمة المرور المُدخَلة مقابل التجزئة المخزّنة باستخدام PasswordEncoder الخاص بك. كما يتحقق من الأعلام الأربعة في UserDetails قبل منح الوصول — هنا تُطبّق قفل الحساب والانتهاء وتعطيله دون كتابة أي منطق مصادقة بنفسك.
أعلام UserDetails ولماذا تهم
isEnabled()— تُعيدfalseلمنع تسجيل الدخول للحسابات المحذوفة ناعمًا أو غير الموثّقة.isAccountNonLocked()— تُعيدfalseبعد عدد كبير من محاولات تسجيل الدخول الفاشلة.isAccountNonExpired()— تُعيدfalseللحسابات التي انتهى اشتراكها.isCredentialsNonExpired()— تُعيدfalseلإجبار المستخدم على تغيير كلمة المرور بعد فترة محددة.
كل علم يرمي فئة فرعية مختلفة من AuthenticationException، لذا يمكن لكود معالجة الأخطاء التمييز بين حساب مقفل وكلمة مرور منتهية وعرض رسالة مناسبة للمستخدم.
UsernameNotFoundException برسالة عامة ("بيانات اعتماد خاطئة" لا "المستخدم alice غير موجود"). يُخفي DaoAuthenticationProvider في Spring Security استثناء UsernameNotFoundException افتراضيًا (يُعيد رميه كـ BadCredentialsException عام) لمنع هجمات تعداد المستخدمين. لا تُعطّل هذا السلوك.
اعتبار الأنظمة الموزّعة: الخدمات عديمة الحالة
في معمارية الخدمات المصغّرة، استدعاء loadUserByUsername — والوصول إلى قاعدة البيانات — مع كل طلب مكلف ويُنشئ اقترانًا بين خدمتك ومخزن المستخدمين. الحل النموذجي هو المصادقة مرة واحدة (عبر بوابة أو خدمة مصادقة)، وإصدار رمز مُوقَّع (JWT)، وجعل الخدمات المنبثقة تتحقق من الرمز محليًا دون استدعاء UserDetailsService على الإطلاق. ستستكشف هذا النمط في الدرس الثامن (قواعد التفويض) وفي درس JWT المخصّص. الآن، افهم أن UserDetailsService هو خطّاف وقت المصادقة؛ التحقق من الرمز يحل محله في التدفقات عديمة الحالة.
الخلاصة
UserDetailsService هي نقطة التوسّع الوحيدة التي يمنحك إياها Spring Security لتحميل بيانات المستخدم. طبّقها لدمج أي مخزن: InMemoryUserDetailsManager للاختبارات، وخدمة مدعومة بـ JPA للإنتاج. أعِد كائن UserDetails مُعبَّأ بصحة — بكلمة مرور مُشفَّرة وصلاحيات وقيم أعلام دقيقة — واربطه بـ DaoAuthenticationProvider. من تلك النقطة، يتولّى الإطار التحقق من كلمة المرور وفحص الأعلام وتوجيه استثناءات المصادقة نيابةً عنك.