404 Not Found

404 Not Found


nginx

أساسيات Spring Security

Spring Security هو المعيار الصناعي لأمان Java—بثلاثيته من سلاسل الفلاتر، والمصادقة، والتفويض—يحمي أمان واجهات API.

1. ما ستتعلمه


2. قصة حقيقية لمدير منتج

(1) نقطة الألم: واجهات API غير محمية

خلال اجتماع مراجعة المنتج، اكتشف Charlie أن أيًا من واجهات OrderFlow API لم يكن مصادقاً—أي شخص يمكنه استدعاء واجهات API لإنشاء طلبات أو حذف منتجات. والأخطر من ذلك، أن Bob كان قد كشف نقطة نهاية Actuator على الإنترنت العام، مما سمح للماسحات بالحصول على معلومات قاعدة بيانات الإنتاج. طلب Charlie تنفيذ ضوابط أمنية فوراً، واحتاجت Alice إلى أسرع حل ممكن.

(2) حل Spring Security

Spring Security يمكنه تأمين واجهة API الخاصة بك ببضعة أسطر فقط من التهيئة:

JAVA
@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) سلسلة الفلاتر

100%
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) ▶ مثال: تهيئة أمان أساسية

JAVA
@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;
    }
}

الناتج:

TEXT
// التنفيذ ناجح
خيار التهيئة الوصف توصية REST API
CSRF حماية من تزوير الطلبات عبر المواقع معطل (غير مطلوب لواجهات API عديمة الحالة)
CORS مشاركة الموارد عبر الأصل تهيئة النطاقات المسموح بها
Session إدارة الجلسة STATELESS (عديم الحالة)
httpBasic مصادقة HTTP الأساسية للتطوير/الاختبار
formLogin تسجيل الدخول بالنموذج لتطبيقات الويب التقليدية

5. مصادقة المستخدمين في الذاكرة

(1) ▶ مثال: InMemoryUserDetailsManager

JAVA
@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();
    }
}

الناتج:

TEXT
// التنفيذ ناجح
PasswordEncoder الأمان حالات الاستخدام
BCryptPasswordEncoder عالي (يتضمن قيمة ملح) موصى به لبيئات الإنتاج
Argon2PasswordEncoder الأعلى (مقاوم لـ GPU) متطلبات أمان عالية
NoOpPasswordEncoder بدون (نص عادي) للاختبار فقط
🔒 الأمان: لا تستخدم أبداً NoOpPasswordEncoder في بيئة الإنتاج. BCrypt هو الحد الأدنى؛ Argon2 أعلى.

(2) ▶ مثال: اختبار المصادقة

BASH
# الوصول بدون بيانات اعتماد -> 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

الناتج:

TEXT
{"status":"ok","data":{}}

6. التفويض على مستوى الطريقة

(1) ▶ مثال: تفويض طريقة @PreAuthorize

JAVA
@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);
    }
}

الناتج:

TEXT
// التنفيذ ناجح
التعليقات الميزات حالات الاستخدام
@PreAuthorize تعبير SpEL، فحص قبل الوصول إلى الطريقة الأكثر مرونة، موصى به
@PostAuthorize تعبير SpEL؛ فحص بعد تنفيذ الطريقة يجب تحديد الصلاحيات بناءً على قيمة الإرجاع
@Secured قائمة أدوار، لا يدعم SpEL فحص أدوار بسيط
@RolesAllowed تعليقات معيار JSR-250 توافق بين الأطر
📌 نقطة رئيسية: لاستخدام التفويض على مستوى الطريقة، أضف @EnableMethodSecurity إلى صنف التهيئة.

(2) ▶ مثال: تهيئة @EnableMethodSecurity

JAVA
@Configuration
@EnableWebSecurity
@EnableMethodSecurity
public class SecurityConfig {
    // ... SecurityFilterChain وUserDetailsService beans
}

الناتج:

TEXT
// التنفيذ ناجح

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 حسب الدور

JAVA
@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();
}

الناتج:

TEXT
// التنفيذ ناجح

8. مثال شامل: تهيئة أمان OrderFlow

JAVA
// 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) { /* ... */ }
}

❓ أسئلة شائعة

س لماذا يجب تعطيل CSRF لواجهات REST API؟
ج حماية CSRF تعتمد على ملفات تعريف الارتباط (cookies) والجلسات. واجهات REST API عادةً عديمة الحالة (تستخدم مصادقة قائمة على الرموز) ولا تستخدم ملفات تعريف الارتباط، لذا لا يمكن نجاح هجمات CSRF؛ لذا يمكن تعطيل حماية CSRF.
س ما الفرق بين hasRole وhasAuthority؟
ج hasRole("ADMIN") يضيف تلقائياً بادئة "ROLE_" للتحقق من صلاحية ROLE_ADMIN. hasAuthority("ADMIN") لا يضيف بادئة ويتحقق مباشرة من صلاحية ADMIN. نوصي باستخدام hasRole بشكل متسق.
س هل يمكن استخدام InMemoryUserDetailsManager في الإنتاج؟
ج لا. إدارة المستخدمين في الذاكرة مخصصة للتطوير والاختبار فقط. في بيئة الإنتاج، تحتاج إلى استخدام UserDetailsService مخصص لتحميل معلومات المستخدم من قاعدة البيانات.
س كيف أسترجع معلومات المستخدم المسجل دخوله حالياً؟
ج هناك ثلاث طرق: 1) SecurityContextHolder.getContext().getAuthentication()؛ 2) حقن معامل في طريقة المتحكم Principal principal؛ 3) @AuthenticationPrincipal UserDetails user. نوصي بالطريقة الثالثة.
س هل يمكن تخصيص ترتيب فلاتر Spring Security؟
ج نعم، يمكنك التحكم في ترتيب SecurityFilterChain باستخدام تعليق @Order. يمكن تهيئة SecurityFilterChains متعددة لمطابقة مسارات طلبات مختلفة.
س أيهما أختار، httpBasic أم formLogin؟
ج استخدم httpBasic أو Bearer Token لواجهات REST API؛ استخدم formLogin لتطبيقات الويب التقليدية. يستخدم هذا الدرس httpBasic كمقدمة؛ الدروس اللاحقة ستنتقل إلى JWT.

📖 ملخص


📝 تمارين

  1. تمرين أساسي (الصعوبة: ⭐): قم بتهيئة Spring Security لـ OrderFlow لتنفيذ permitAll لاستعلامات المنتجات، authenticated لعمليات الطلبات، وhasRole("ADMIN") لواجهات API الإدارية. اختبر باستخدام مصادقة HTTP Basic ومستخدمين في الذاكرة.

  2. تمرين متقدم (الصعوبة ⭐⭐): نفّذ تفويض طريقة @PreAuthorize—المستخدمون يمكنهم عرض طلباتهم فقط، بينما ADMIN يمكنه عرض جميع الطلبات. تلميح: أنشئ Bean مساعد OrderSecurity وأشِر إليه في SpEL.

  3. تحدٍ (الصعوبة: ⭐⭐⭐): نفّذ UserDetailsService مخصص لتحميل معلومات المستخدم والدور من قاعدة البيانات (JPA)، مستبدلاً InMemoryUserDetailsManager. فكّر في التصميم الأمني لتخزين كلمات المرور وعملية تسجيل المستخدمين.

Web-Tutorial.com

فريق Web-Tutorial التقني

منصة دروس برمجية يديرها عدة مطورين. كل درس يتم كتابته ومراجعته بواسطة مطورين متخصصين في المجال. نعمل على ضمان دقة وموثوقية المحتوى — إذا لاحظت أي مشكلة، فيرجى إخبارنا.

100%