أساسيات Spring Security

تسجيل الدخول عبر النموذج وHTTP Basic

18 دقيقة الدرس 7 من 13

تسجيل الدخول عبر النموذج وHTTP Basic

يأتي Spring Security مزوَّدًا بآليتين جاهزتين للمصادقة تغطيان أكثر أنماط تطبيقات الويب شيوعًا: تسجيل الدخول عبر النموذج (Form Login) للجلسات التي يقودها المتصفح، وHTTP Basic للعملاء من الأجهزة الآلية أو واجهات برمجة التطبيقات. في الدروس السابقة هيّأت SecurityFilterChain وأعددت UserDetailsService. الآن ستربط هذه القطع بسير عمل تسجيل الدخول الفعلية.

كيف تنتظم آليات المصادقة داخل سلسلة الفلاتر

كل آلية في Spring Security مُنفَّذة على شكل فلتر servlet يجلس داخل SecurityFilterChain. عندما يصل طلب، تمشي السلسلة عبر فلاترها بالترتيب. فلتران مرتبطان بهذا الدرس:

  • UsernamePasswordAuthenticationFilter — يعترض POST /login، ويستخرج بيانات الاعتماد من جسم الطلب، ويفوّض الأمر إلى AuthenticationManager.
  • BasicAuthenticationFilter — يقرأ الترويسة Authorization: Basic <base64> في كل طلب ويحاول المصادقة قبل أن يصل الطلب إلى وحدات التحكم الخاصة بك.

لا تُنشئ هذه الفلاتر مباشرةً، بل يبنيها Spring Security عندما تستدعي .formLogin() أو .httpBasic() على DSL الخاص بـ HttpSecurity.

ضبط Form Login

أدنى إعداد ممكن يُفعِّل صفحة تسجيل الدخول المدمجة ويربط كل الإعدادات الافتراضية:

@Bean public SecurityFilterChain securityFilterChain(HttpSecurity http) throws Exception { http .authorizeHttpRequests(auth -> auth .requestMatchers("/public/**").permitAll() .anyRequest().authenticated() ) .formLogin(Customizer.withDefaults()); // صفحة /login المدمجة + معالج POST return http.build(); }

بهذا الإعداد وحده، يُعاد توجيه أي طلب غير مصادَق عليه لعنوان URL محمي إلى /login، يُصيّر Spring نموذج تسجيل الدخول المدمج، وعند نجاح POST إلى /login يُعاد توجيه المستخدم إلى العنوان الأصلي (أو /). عند الفشل يعود إلى /login?error.

تخصيص سير عمل Form Login

في المشاريع الحقيقية تريد دائمًا صفحة تسجيل دخول خاصة بك وعنوان نجاح مخصص ومعالج فشل محدد:

.formLogin(form -> form .loginPage("/auth/login") // GET — عرض Thymeleaf/MVC الخاص بك .loginProcessingUrl("/auth/login") // POST — يتعامل معه Spring .usernameParameter("email") // تجاوز الاسم الافتراضي "username" .passwordParameter("pass") // تجاوز الاسم الافتراضي "password" .defaultSuccessUrl("/dashboard", true) // اذهب دائمًا هنا بعد تسجيل الدخول .failureUrl("/auth/login?error=true") // إعادة توجيه عند بيانات اعتماد خاطئة .permitAll() // صفحة تسجيل الدخول نفسها عامة )

الفارق الجوهري: loginPage() هو نقطة نهاية GET تخدمها وحدة تحكم MVC الخاصة بك؛ أما loginProcessingUrl() فهو نقطة نهاية POST يعترضها فلتر Spring Security — لا تكتب أي دالة تحكم لها.

loginPage مقابل loginProcessingUrl: يمكن أن يكونا نفس المسار (كما هو موضح أعلاه) لأن طريقة HTTP تميز بينهما — GET يُصيّر النموذج، POST يُرسل بيانات الاعتماد. يُسجّل Spring Security معالج POST؛ وأنت تُسجّل معالج GET في @Controller الخاص بك.

ضبط تسجيل الخروج

يتزاوج Form Login بشكل طبيعي مع معالج تسجيل خروج. يُسجّل Spring Security المسار POST /logout افتراضيًا، مما يُبطل الجلسة ويمسح SecurityContext. خصّصه هكذا:

.logout(logout -> logout .logoutUrl("/auth/logout") .logoutSuccessUrl("/auth/login?logout=true") .invalidateHttpSession(true) .deleteCookies("JSESSIONID") )
استخدم POST دائمًا لتسجيل الخروج. نقطة نهاية تسجيل خروج مبنية على GET عرضة لهجمات تزوير الطلبات عبر المواقع (CSRF): يمكن للمهاجم تضمين وسم <img src="/logout"> غير مرئي في أي صفحة لإجبار الضحية على تسجيل الخروج. يفرض Spring Security استخدام POST افتراضيًا — لا تغيّره إلى GET إلا إذا عطّلت حماية CSRF لذلك العنوان أيضًا.

ضبط HTTP Basic

يُفعَّل HTTP Basic باستدعاء واحد:

@Bean public SecurityFilterChain securityFilterChain(HttpSecurity http) throws Exception { http .authorizeHttpRequests(auth -> auth .anyRequest().authenticated() ) .httpBasic(basic -> basic .realmName("My API") // يظهر في نافذة بيانات اعتماد المتصفح ) .sessionManagement(session -> session .sessionCreationPolicy(SessionCreationPolicy.STATELESS) // لا جلسة من جانب الخادم ); return http.build(); }

يُشفّر العميل بيانات الاعتماد كـ Base64(username:password) ويُرسلها في كل طلب داخل ترويسة Authorization. يُفكّك Spring Security ترميزها ويتحقق منها مقابل UserDetailsService في كل استدعاء.

بيانات اعتماد HTTP Basic مُرمَّزة بـ Base64 فحسب، وليست مُشفَّرة. ترميز Base64 قابل للعكس بسهولة. يجب استخدام HTTPS في الإنتاج لمنع اعتراض بيانات الاعتماد أثناء النقل. لا تنشر HTTP Basic على HTTP العادي أبدًا.

HTTP Basic في الأنظمة الموزعة

في معماريات الخدمات المصغرة، يُستخدم HTTP Basic أحيانًا للمصادقة بين الخدمات عندما تكون الخدمات بالفعل داخل شبكة خاصة مع TLS متبادل، أو عندما يدور مدير أسرار مخصص بيانات الاعتماد تلقائيًا. ميزته البساطة — لا تبادل رمز، لا انتهاء صلاحية، لا تحديث. عيبه هو بالضبط ذلك: بيانات الاعتماد طويلة الأجل ولا يمكن إبطالها دون إعادة نشر جميع المستهلكين. لأي شيء يواجه المستخدمين أو عبر حدود الشبكة، يُفضَّل استخدام رموز JWT Bearer (مغطاة في البرنامج التعليمي التالي).

دمج كلتا الآليتين

نمط شائع هو تقديم واجهة مستخدم للمتصفح عبر Form Login وواجهة REST API عبر HTTP Basic (أو JWT) من نفس التطبيق، باستخدام حبتَي SecurityFilterChain منفصلتين مع أنماط securityMatcher مختلفة:

// السلسلة 1: REST API — عديمة الحالة، HTTP Basic @Bean @Order(1) public SecurityFilterChain apiChain(HttpSecurity http) throws Exception { http .securityMatcher("/api/**") .authorizeHttpRequests(auth -> auth.anyRequest().authenticated()) .httpBasic(Customizer.withDefaults()) .sessionManagement(s -> s.sessionCreationPolicy(SessionCreationPolicy.STATELESS)) .csrf(csrf -> csrf.disable()); // عملاء REST لا يستخدمون ملفات تعريف الارتباط return http.build(); } // السلسلة 2: واجهة ويب — ذات حالة، Form Login @Bean @Order(2) public SecurityFilterChain webChain(HttpSecurity http) throws Exception { http .authorizeHttpRequests(auth -> auth .requestMatchers("/public/**").permitAll() .anyRequest().authenticated() ) .formLogin(form -> form .loginPage("/login") .defaultSuccessUrl("/dashboard", true) .permitAll() ) .logout(Customizer.withDefaults()); return http.build(); }

التعليق التوضيحي @Order بالغ الأهمية: يُقيّم Spring السلاسل بترتيب تصاعدي، لذا يجب أن يأتي المُطابق الأكثر تحديدًا (/api/**) أولًا. بدون @Order، يستخدم Spring Boot ترتيبًا افتراضيًا وقد يُطبّق السلسلة الخاطئة.

عطّل CSRF فقط حيث يكون ذلك آمنًا. تعتمد هجمات CSRF على قيام المتصفحات بإرفاق ملفات تعريف الارتباط تلقائيًا بالطلبات العابرة للمواقع. عملاء REST عديمو الحالة (curl، التطبيقات المحمولة، الخدمات المصغرة الأخرى) لا يستخدمون ملفات تعريف الارتباط للجلسات، لذا لا تُضيف حماية CSRF أي قيمة لهم. يجب على سير عمل Form Login التي تواجه المتصفح إبقاء CSRF مُفعَّلًا.

تذكّرني (Remember Me)

يدعم Form Login ملف تعريف ارتباط "تذكّرني" يُعيد مصادقة المستخدمين عبر الجلسات دون إعادة إدخال بيانات الاعتماد:

.rememberMe(rm -> rm .key("uniqueAndSecret") // يوقّع الرمز؛ التغيير = إبطال جميع الرموز .tokenValiditySeconds(86400) // يوم واحد .userDetailsService(userDetailsService) )

الرمز عبارة عن تجزئة لـ username + expiry + password-hash + key. يؤدي تغيير كلمة مرور المستخدم أو key تلقائيًا إلى إبطال جميع رموز "تذكّرني" القائمة — خاصية مفيدة لإجبار تسجيل الخروج عبر الأجهزة.

الخلاصة

Form Login وHTTP Basic هما آليتا المصادقة الأساسيتان في Spring Security. Form Login مبني لمستخدمي المتصفح: يدير صفحة تسجيل الدخول، ومعالجة POST، وإعادة التوجيه عند النجاح، وإنشاء الجلسة. HTTP Basic مبني للعملاء البرمجيين: يقرأ بيانات الاعتماد من ترويسة في كل طلب ويعمل بشكل أفضل كحالة عديمة الحالة. تضبط كليهما عبر DSL الخاص بـ HttpSecurity. في الإنتاج، اقرنهما دائمًا بـ HTTPS، واستخدم حبوب SecurityFilterChain منفصلة عندما يخدم تطبيق واحد كلًّا من المتصفحات ومستهلكي API.