أساسيات Spring Security
Spring Security هو المعيار الصناعي لأمان Java—بثلاثيته من سلاسل الفلاتر، والمصادقة، والتفويض—يحمي أمان واجهات API.
1. ما ستتعلمه
- بنية سلسلة فلاتر Spring Security وتهيئة
SecurityFilterChain - تهيئة
HttpSecurity: تفويض المسارات، CSRF، CORS - مصادقة المستخدمين في الذاكرة
UserDetailsServiceوPasswordEncoder - التفويض على مستوى الطريقة
@PreAuthorize/@Secured - Alice تقيّد الوصول إلى واجهة إدارة الطلبات بدور ADMIN فقط
2. قصة حقيقية لمدير منتج
(1) نقطة الألم: واجهات API غير محمية
خلال اجتماع مراجعة المنتج، اكتشف Charlie أن أيًا من واجهات OrderFlow API لم يكن مصادقاً—أي شخص يمكنه استدعاء واجهات API لإنشاء طلبات أو حذف منتجات. والأخطر من ذلك، أن Bob كان قد كشف نقطة نهاية Actuator على الإنترنت العام، مما سمح للماسحات بالحصول على معلومات قاعدة بيانات الإنتاج. طلب Charlie تنفيذ ضوابط أمنية فوراً، واحتاجت Alice إلى أسرع حل ممكن.
(2) حل Spring Security
Spring Security يمكنه تأمين واجهة API الخاصة بك ببضعة أسطر فقط من التهيئة:
@Configuration
@EnableWebSecurity
public class SecurityConfig {
@Bean
public SecurityFilterChain filterChain(HttpSecurity http) throws Exception {
http.csrf(csrf -> csrf.disable())
.authorizeHttpRequests(auth -> auth
.requestMatchers("/api/v1/admin/**").hasRole("ADMIN")
.requestMatchers("/api/v1/orders/**").authenticated()
.anyRequest().permitAll()
)
.httpBasic(Customizer.withDefaults());
return http.build();
}
}
(3) العوائد
بعد أن نفذت Alice المصادقة والتفويض لـ OrderFlow باستخدام Spring Security، أصبحت واجهة API الإدارية متاحة فقط للمستخدمين بدور ADMIN، وواجهة API القياسية تتطلب تسجيل دخول المستخدمين، ونقاط نهاية Actuator قُيدت بالوصول من الشبكة الداخلية. كان Charlie راضياً عن التوافق الأمني.
3. بنية Spring Security
(1) سلسلة الفلاتر
sequenceDiagram
participant Client
participant Chain as SecurityFilterChain
participant Auth as Authentication Filter
participant Authz as Authorization Filter
participant Controller
Client->>Chain: HTTP Request
Chain->>Auth: 1. المصادقة
alt بيانات الاعتماد غير صالحة
Auth-->>Client: 401 Unauthorized
end
Auth->>Authz: 2. التفويض
alt الوصول مرفوض
Authz-->>Client: 403 Forbidden
end
Authz->>Controller: 3. التوجيه إلى المتحكم
Controller-->>Client: 200 OK
| المفاهيم الرئيسية | الوصف |
|---|---|
SecurityFilterChain |
سلسلة فلاتر مرتبة، يمر عبرها كل طلب بالتسلسل |
Authentication |
المصادقة: تأكيد "من أنت" |
Authorization |
التفويض: تأكيد "ماذا يمكنك أن تفعل" |
SecurityContext |
سياق الأمان يحتوي على معلومات المستخدم الحالي |
GrantedAuthority |
معلومات الصلاحيات/الدور |
(2) العلاقات بين المكونات الأساسية
| المكون | المسؤوليات | الواجهة |
|---|---|---|
AuthenticationManager |
مدير المصادقة | authenticate() |
ProviderManager |
التنفيذ الافتراضي لـ AuthenticationManager | يفوّض إلى AuthenticationProvider |
UserDetailsService |
تحميل معلومات المستخدم | loadUserByUsername() |
PasswordEncoder |
ترميز كلمة المرور | encode() / matches() |
4. تهيئة SecurityFilterChain
(1) ▶ مثال: تهيئة أمان أساسية
@Configuration
@EnableWebSecurity
public class SecurityConfig {
@Bean
public SecurityFilterChain filterChain(HttpSecurity http) throws Exception {
http
.csrf(csrf -> csrf.disable())
.cors(cors -> cors.configurationSource(corsConfigurationSource()))
.sessionManagement(session ->
session.sessionCreationPolicy(SessionCreationPolicy.STATELESS))
.authorizeHttpRequests(auth -> auth
.requestMatchers("/api/v1/products/**").permitAll()
.requestMatchers("/api/v1/orders/**").authenticated()
.requestMatchers("/api/v1/admin/**").hasRole("ADMIN")
.anyRequest().authenticated()
)
.httpBasic(Customizer.withDefaults());
return http.build();
}
@Bean
public CorsConfigurationSource corsConfigurationSource() {
CorsConfiguration config = new CorsConfiguration();
config.setAllowedOrigins(List.of("https://orderflow.example.com"));
config.setAllowedMethods(List.of("GET", "POST", "PUT", "DELETE"));
config.setAllowedHeaders(List.of("*"));
UrlBasedCorsConfigurationSource source = new UrlBasedCorsConfigurationSource();
source.registerCorsConfiguration("/api/**", config);
return source;
}
}
الناتج:
// التنفيذ ناجح
| خيار التهيئة | الوصف | توصية REST API |
|---|---|---|
| CSRF | حماية من تزوير الطلبات عبر المواقع | معطل (غير مطلوب لواجهات API عديمة الحالة) |
| CORS | مشاركة الموارد عبر الأصل | تهيئة النطاقات المسموح بها |
| Session | إدارة الجلسة | STATELESS (عديم الحالة) |
| httpBasic | مصادقة HTTP الأساسية | للتطوير/الاختبار |
| formLogin | تسجيل الدخول بالنموذج | لتطبيقات الويب التقليدية |
5. مصادقة المستخدمين في الذاكرة
(1) ▶ مثال: InMemoryUserDetailsManager
@Configuration
@EnableWebSecurity
public class SecurityConfig {
@Bean
public UserDetailsService userDetailsService(PasswordEncoder encoder) {
UserDetails admin = User.builder()
.username("alice")
.password(encoder.encode("admin123"))
.roles("ADMIN", "CUSTOMER")
.build();
UserDetails customer = User.builder()
.username("bob")
.password(encoder.encode("customer123"))
.roles("CUSTOMER")
.build();
return new InMemoryUserDetailsManager(admin, customer);
}
@Bean
public PasswordEncoder passwordEncoder() {
return new BCryptPasswordEncoder();
}
}
الناتج:
// التنفيذ ناجح
| PasswordEncoder | الأمان | حالات الاستخدام |
|---|---|---|
BCryptPasswordEncoder |
عالي (يتضمن قيمة ملح) | موصى به لبيئات الإنتاج |
Argon2PasswordEncoder |
الأعلى (مقاوم لـ GPU) | متطلبات أمان عالية |
NoOpPasswordEncoder |
بدون (نص عادي) | للاختبار فقط |
NoOpPasswordEncoder في بيئة الإنتاج. BCrypt هو الحد الأدنى؛ Argon2 أعلى.
(2) ▶ مثال: اختبار المصادقة
# الوصول بدون بيانات اعتماد -> 401
curl http://localhost:8080/api/v1/orders
# الوصول ببيانات اعتماد العميل -> 200
curl -u bob:customer123 http://localhost:8080/api/v1/orders
# الوصول لنقطة نهاية الإدارة كعميل -> 403
curl -u bob:customer123 http://localhost:8080/api/v1/admin/dashboard
# الوصول لنقطة نهاية الإدارة كمدير -> 200
curl -u alice:admin123 http://localhost:8080/api/v1/admin/dashboard
الناتج:
{"status":"ok","data":{}}
6. التفويض على مستوى الطريقة
(1) ▶ مثال: تفويض طريقة @PreAuthorize
@Service
public class OrderService {
@Transactional(readOnly = true)
@PreAuthorize("hasAnyRole('ADMIN', 'CUSTOMER')")
public Order getOrder(Long orderId) {
return orderRepository.findById(orderId).orElseThrow();
}
@Transactional
@PreAuthorize("hasRole('ADMIN')")
public void deleteOrder(Long orderId) {
orderRepository.deleteById(orderId);
}
@Transactional
@PreAuthorize("hasRole('ADMIN') or #customerId == authentication.principal.id")
public List<Order> getCustomerOrders(Long customerId) {
return orderRepository.findByCustomerId(customerId);
}
}
الناتج:
// التنفيذ ناجح
| التعليقات | الميزات | حالات الاستخدام |
|---|---|---|
@PreAuthorize |
تعبير SpEL، فحص قبل الوصول إلى الطريقة | الأكثر مرونة، موصى به |
@PostAuthorize |
تعبير SpEL؛ فحص بعد تنفيذ الطريقة | يجب تحديد الصلاحيات بناءً على قيمة الإرجاع |
@Secured |
قائمة أدوار، لا يدعم SpEL | فحص أدوار بسيط |
@RolesAllowed |
تعليقات معيار JSR-250 | توافق بين الأطر |
@EnableMethodSecurity إلى صنف التهيئة.
(2) ▶ مثال: تهيئة @EnableMethodSecurity
@Configuration
@EnableWebSecurity
@EnableMethodSecurity
public class SecurityConfig {
// ... SecurityFilterChain وUserDetailsService beans
}
الناتج:
// التنفيذ ناجح
7. مرجع سريع لقواعد تفويض المسارات
| طريقة المطابقة | الوصف | مثال |
|---|---|---|
requestMatchers(String) |
مطابقة مسار بنمط Ant | /api/v1/orders/** |
requestMatchers(HttpMethod, String) |
تقييد بطرق HTTP | POST /api/v1/orders |
anyRequest() |
مطابقة جميع الطلبات | يوضع في النهاية كالتقاط العام |
| طريقة التفويض | الوصف |
|---|---|
permitAll() |
متاح للجميع |
authenticated() |
يتطلب مصادقة |
hasRole("ADMIN") |
يتطلب دور ADMIN |
hasAnyRole("A", "B") |
يتطلب أي دور |
denyAll() |
حظر جميع الوصول |
(1) ▶ مثال: تقييد الوصول إلى API حسب الدور
@Bean
public SecurityFilterChain filterChain(HttpSecurity http) throws Exception {
http.csrf(csrf -> csrf.disable())
.authorizeHttpRequests(auth -> auth
.requestMatchers(HttpMethod.GET, "/api/v1/products/**").permitAll()
.requestMatchers(HttpMethod.POST, "/api/v1/products").hasRole("ADMIN")
.requestMatchers(HttpMethod.PUT, "/api/v1/products/**").hasRole("ADMIN")
.requestMatchers(HttpMethod.DELETE, "/api/v1/products/**").hasRole("ADMIN")
.requestMatchers("/api/v1/orders/**").hasAnyRole("ADMIN", "CUSTOMER")
.requestMatchers("/actuator/**").hasRole("ADMIN")
.anyRequest().authenticated()
)
.httpBasic(Customizer.withDefaults());
return http.build();
}
الناتج:
// التنفيذ ناجح
8. مثال شامل: تهيئة أمان OrderFlow
// SecurityConfig.java
package com.orderflow.config;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.security.config.annotation.method.configuration.EnableMethodSecurity;
import org.springframework.security.config.annotation.web.builders.HttpSecurity;
import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity;
import org.springframework.security.config.http.SessionCreationPolicy;
import org.springframework.security.core.userdetails.User;
import org.springframework.security.core.userdetails.UserDetailsService;
import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder;
import org.springframework.security.crypto.password.PasswordEncoder;
import org.springframework.security.provisioning.InMemoryUserDetailsManager;
import org.springframework.security.web.SecurityFilterChain;
@Configuration
@EnableWebSecurity
@EnableMethodSecurity
public class SecurityConfig {
@Bean
public SecurityFilterChain filterChain(HttpSecurity http) throws Exception {
http
.csrf(csrf -> csrf.disable())
.sessionManagement(s -> s.sessionCreationPolicy(SessionCreationPolicy.STATELESS))
.authorizeHttpRequests(auth -> auth
.requestMatchers(HttpMethod.GET, "/api/v1/products/**").permitAll()
.requestMatchers("/api/v1/orders/**").hasAnyRole("CUSTOMER", "ADMIN")
.requestMatchers("/api/v1/admin/**").hasRole("ADMIN")
.requestMatchers("/actuator/health").permitAll()
.requestMatchers("/actuator/**").hasRole("ADMIN")
.anyRequest().authenticated()
)
.httpBasic(Customizer.withDefaults());
return http.build();
}
@Bean
public UserDetailsService userDetailsService(PasswordEncoder encoder) {
var admin = User.builder()
.username("alice").password(encoder.encode("admin123"))
.roles("ADMIN", "CUSTOMER").build();
var customer = User.builder()
.username("bob").password(encoder.encode("pass123"))
.roles("CUSTOMER").build();
return new InMemoryUserDetailsManager(admin, customer);
}
@Bean
public PasswordEncoder passwordEncoder() {
return new BCryptPasswordEncoder();
}
}
// OrderController مع أمان على مستوى الطريقة
@RestController
@RequestMapping("/api/v1/orders")
public class OrderController {
@GetMapping
@PreAuthorize("hasAnyRole('CUSTOMER', 'ADMIN')")
public List<Order> listOrders() { /* ... */ }
@GetMapping("/{id}")
@PreAuthorize("hasRole('ADMIN') or @orderSecurity.isOwner(#id, authentication)")
public Order getOrder(@PathVariable Long id) { /* ... */ }
@DeleteMapping("/{id}")
@PreAuthorize("hasRole('ADMIN')")
public void deleteOrder(@PathVariable Long id) { /* ... */ }
}
❓ أسئلة شائعة
hasRole وhasAuthority؟hasRole("ADMIN") يضيف تلقائياً بادئة "ROLE_" للتحقق من صلاحية ROLE_ADMIN. hasAuthority("ADMIN") لا يضيف بادئة ويتحقق مباشرة من صلاحية ADMIN. نوصي باستخدام hasRole بشكل متسق.SecurityContextHolder.getContext().getAuthentication()؛ 2) حقن معامل في طريقة المتحكم Principal principal؛ 3) @AuthenticationPrincipal UserDetails user. نوصي بالطريقة الثالثة.📖 ملخص
- البنية الأساسية لـ Spring Security: SecurityFilterChain → المصادقة → التفويض
SecurityFilterChainتهيئة قواعد تفويض المسارات: permitAll / authenticated / hasRole- أفضل ممارسات REST API: تعطيل CSRF، جلسات STATELESS، HTTP Basic أو رموز Bearer
@PreAuthorize+ SpEL: تنفيذ تفويض دقيق على مستوى الطريقة باستخدام التعبيراتBCryptPasswordEncoderهو الحد الأدنى المعياري لأمان ترميز كلمات المرور- InMemoryUserDetailsManager للاستخدام في التطوير فقط؛ في بيئة الإنتاج، يجب تنفيذ UserDetailsService مخصص
📝 تمارين
-
تمرين أساسي (الصعوبة: ⭐): قم بتهيئة Spring Security لـ OrderFlow لتنفيذ
permitAllلاستعلامات المنتجات،authenticatedلعمليات الطلبات، وhasRole("ADMIN")لواجهات API الإدارية. اختبر باستخدام مصادقة HTTP Basic ومستخدمين في الذاكرة. -
تمرين متقدم (الصعوبة ⭐⭐): نفّذ تفويض طريقة
@PreAuthorize—المستخدمون يمكنهم عرض طلباتهم فقط، بينما ADMIN يمكنه عرض جميع الطلبات. تلميح: أنشئ Bean مساعد OrderSecurity وأشِر إليه في SpEL. -
تحدٍ (الصعوبة: ⭐⭐⭐): نفّذ
UserDetailsServiceمخصص لتحميل معلومات المستخدم والدور من قاعدة البيانات (JPA)، مستبدلاًInMemoryUserDetailsManager. فكّر في التصميم الأمني لتخزين كلمات المرور وعملية تسجيل المستخدمين.



