الفصل 1: مقدمة إلى Spring Security
مقدمة
قبل إعداد أي شيء، من الضروري فهم كيفية عمل Spring Security. هذا الفصل يضع الأسس التي ستسمح لك بإعداد الأمان بشكل صحيح.
🎯 أهداف الفصل
في نهاية هذا الفصل، ستعرف:
- ✅ لماذا الأمان ضروري
- ✅ الفرق بين Authentication و Authorization
- ✅ بنية Spring Security
- ✅ كيف تعمل سلسلة الفلاتر
- ✅ المفاهيم الأساسية: Principal، Credentials، Authorities
1. لماذا تأمين تطبيق؟
التهديدات
أمثلة حقيقية
| السنة | الشركة | التأثير |
|---|---|---|
| 2023 | MOVEit | أكثر من 60 مليون مستخدم متأثر |
| 2021 | 533 مليون حساب مكشوف | |
| 2019 | Capital One | أكثر من 100 مليون بيانات مسروقة |
| 2017 | Equifax | 147 مليون بيانات شخصية |
الركائز الثلاثة للأمان (CIA)
┌─────────────────────────────────────────────────┐
│ │
│ C - السرية │
│ فقط الأشخاص المصرح لهم يمكنهم رؤية البيانات │
│ │
│ I - النزاهة │
│ لا يمكن تعديل البيانات بدون إذن │
│ │
│ A - التوفر │
│ يبقى النظام متاحًا للمستخدمين الشرعيين │
│ │
└─────────────────────────────────────────────────┘
2. Authentication vs Authorization
الفرق الأساسي
| المفهوم | السؤال | المثال |
|---|---|---|
| Authentication | "من أنت؟" | تسجيل الدخول بالبريد الإلكتروني/كلمة المرور |
| Authorization | "هل لديك الحق؟" | الوصول إلى /admin (محجوز للمسؤولين) |
تشبيه: نادي VIP
في Spring Security
// Authentication: من هو المتصل؟
Authentication auth = SecurityContextHolder.getContext().getAuthentication();
String username = auth.getName(); // "jean@example.com"
// Authorization: ما هي حقوقه؟
Collection<? extends GrantedAuthority> authorities = auth.getAuthorities();
// [ROLE_USER] - ليس ROLE_ADMIN!
3. بنية Spring Security
نظرة عامة
المكونات الرئيسية
1. SecurityFilterChain
سلسلة الفلاتر التي تعترض جميع الطلبات:
@Configuration
@EnableWebSecurity
public class SecurityConfig {
@Bean
public SecurityFilterChain filterChain(HttpSecurity http) throws Exception {
return http
.authorizeHttpRequests(auth -> auth
.requestMatchers("/public/**").permitAll()
.requestMatchers("/admin/**").hasRole("ADMIN")
.anyRequest().authenticated()
)
.formLogin(Customizer.withDefaults())
.build();
}
}
2. AuthenticationManager
ينسق عملية التحقق:
3. UserDetailsService
واجهة لتحميل المستخدمين:
public interface UserDetailsService {
UserDetails loadUserByUsername(String username) throws UsernameNotFoundException;
}
4. PasswordEncoder
يشفر كلمات المرور (أبدًا بشكل واضح!):
@Bean
public PasswordEncoder passwordEncoder() {
return new BCryptPasswordEncoder();
}
// الاستخدام
String encoded = passwordEncoder.encode("monMotDePasse");
// $2a$10$N9qo8uLOickgx2ZMRZoMye...
boolean matches = passwordEncoder.matches("monMotDePasse", encoded);
// true
4. سلسلة الفلاتر بالتفصيل
ترتيب الفلاتر (مبسط)
1. CorsFilter → إدارة CORS
2. CsrfFilter → حماية CSRF
3. LogoutFilter → إدارة تسجيل الخروج
4. UsernamePasswordAuthFilter → تسجيل الدخول عبر النموذج
5. BasicAuthenticationFilter → HTTP Basic Auth
6. BearerTokenAuthFilter → JWT/OAuth2
7. AnonymousAuthenticationFilter → مستخدم مجهول
8. ExceptionTranslationFilter → إدارة الأخطاء
9. FilterSecurityInterceptor → قرار الوصول النهائي
تصور التدفق
5. المفاهيم الأساسية
Principal
هوية المستخدم المتصل:
@GetMapping("/profile")
public String profile(Principal principal) {
String username = principal.getName();
return "مرحبًا بك " + username;
}
كائن Authentication
يحتوي على جميع معلومات التحقق:
public interface Authentication {
Collection<? extends GrantedAuthority> getAuthorities(); // الأدوار/الأذونات
Object getCredentials(); // كلمة المرور (غالبًا ما يتم مسحها بعد التحقق)
Object getDetails(); // تفاصيل إضافية (IP، إلخ)
Object getPrincipal(); // المستخدم (UserDetails)
boolean isAuthenticated();
}
GrantedAuthority
يمثل دورًا أو إذنًا:
// الأدوار (بادئة ROLE_)
new SimpleGrantedAuthority("ROLE_ADMIN");
new SimpleGrantedAuthority("ROLE_USER");
// الأذونات
new SimpleGrantedAuthority("READ_USERS");
new SimpleGrantedAuthority("WRITE_USERS");
new SimpleGrantedAuthority("DELETE_USERS");
SecurityContext
يخزن Authentication للمستخدم الحالي:
// استرجاع المستخدم المتصل
SecurityContext context = SecurityContextHolder.getContext();
Authentication auth = context.getAuthentication();
if (auth != null && auth.isAuthenticated()) {
String username = auth.getName();
// ...
}
UserDetails
واجهة تمثل مستخدمًا:
public interface UserDetails {
Collection<? extends GrantedAuthority> getAuthorities();
String getPassword();
String getUsername();
boolean isAccountNonExpired();
boolean isAccountNonLocked();
boolean isCredentialsNonExpired();
boolean isEnabled();
}
6. أنواع التحقق
1. تسجيل الدخول بالنموذج (الجلسة)
العميل → POST /login (user, pass)
الخادم → Set-Cookie: JSESSIONID=xyz
العميل → GET /api/data (Cookie: JSESSIONID=xyz)
الخادم → تحقق من الجلسة → 200 OK
المزايا: بسيط، يُدار بواسطة المتصفح العيوب: غير مناسب لـ API REST عديم الحالة
2. HTTP Basic
العميل → GET /api/data
الخادم → 401 + WWW-Authenticate: Basic
العميل → GET /api/data (Authorization: Basic base64(user:pass))
الخادم → 200 OK
المزايا: بسيط العيوب: يتم إرسال credentials مع كل طلب
3. JWT (Token)
العميل → POST /login (user, pass)
الخادم → {"token": "eyJhbGciOiJIUzI1..."}
العميل → GET /api/data (Authorization: Bearer eyJhbGciOiJIUzI1...)
الخادم → تحقق من token → 200 OK
المزايا: عديم الحالة، مثالي لـ API REST العيوب: إدارة انتهاء الصلاحية
4. OAuth2
العميل → إعادة توجيه إلى Google
Google → تسجيل دخول المستخدم
Google → إعادة توجيه مع رمز
الخادم → تبادل الرمز مقابل token
الخادم → استرجاع ملف تعريف المستخدم
المزايا: لا حاجة لإدارة كلمة المرور، تجربة مستخدم حديثة العيوب: اعتماد خارجي
7. التكوين الحديث (Spring Security 6+)
الطريقة القديمة (مهملة)
// ❌ لا تستخدم
@Configuration
@EnableWebSecurity
public class SecurityConfig extends WebSecurityConfigurerAdapter {
@Override
protected void configure(HttpSecurity http) throws Exception {
http.authorizeRequests()
.antMatchers("/admin/**").hasRole("ADMIN")
.anyRequest().authenticated();
}
}
الطريقة الجديدة (Spring Security 6+)
// ✅ استخدم هذا النهج
@Configuration
@EnableWebSecurity
public class SecurityConfig {
@Bean
public SecurityFilterChain filterChain(HttpSecurity http) throws Exception {
return http
.authorizeHttpRequests(auth -> auth
.requestMatchers("/admin/**").hasRole("ADMIN")
.anyRequest().authenticated()
)
.formLogin(Customizer.withDefaults())
.build();
}
@Bean
public UserDetailsService userDetailsService() {
UserDetails user = User.withDefaultPasswordEncoder()
.username("user")
.password("password")
.roles("USER")
.build();
return new InMemoryUserDetailsManager(user);
}
}
8. التكوين الأولي
تبعية Maven
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-security</artifactId>
</dependency>
السلوك الافتراضي
بدون أي تكوين، يقوم Spring Security بـ:
- حماية جميع النقاط النهائية
- توليد كلمة مرور عشوائية (تظهر في السجلات)
- تفعيل تسجيل الدخول بالنموذج على
/login - تفعيل تسجيل الخروج على
/logout
Using generated security password: 8e3d7a2c-1234-5678-abcd-ef0123456789
التكوين الأدنى
@Configuration
@EnableWebSecurity
public class SecurityConfig {
@Bean
public SecurityFilterChain filterChain(HttpSecurity http) throws Exception {
return http
.authorizeHttpRequests(auth -> auth
.requestMatchers("/", "/home", "/public/**").permitAll()
.anyRequest().authenticated()
)
.formLogin(form -> form
.loginPage("/login")
.permitAll()
)
.logout(logout -> logout
.permitAll()
)
.build();
}
}
9. الملخص
المفاهيم الأساسية
| المفهوم | الوصف |
|---|---|
| Authentication | التحقق من الهوية (من؟) |
| Authorization | التحقق من الحقوق (ماذا؟) |
| SecurityFilterChain | سلسلة فلاتر الأمان |
| AuthenticationManager | ينظم عملية التحقق |
| UserDetailsService | تحميل المستخدمين |
| PasswordEncoder | تشفير كلمات المرور |
| SecurityContext | تخزين المستخدم الحالي |
بنية مبسطة
طلب HTTP
↓
SecurityFilterChain (فلاتر)
↓
AuthenticationManager
↓
UserDetailsService + PasswordEncoder
↓
SecurityContext (تخزين)
↓
التطبيق (Controller)
نقاط مهمة
✅ Spring Security 6+ يستخدم @Bean، وليس extends WebSecurityConfigurerAdapter
✅ دائمًا تشفير كلمات المرور (BCrypt)
✅ فهم Authentication vs Authorization
✅ يتم تكوين الأمان عبر SecurityFilterChain
🎯 الخطوة التالية
الآن بعد أن فهمت البنية، دعنا ننتقل إلى الممارسة مع التحقق الأساسي.