انتقل إلى المحتوى الرئيسي

الفصل 5: الأمان المتقدم

مقدمة​

المصادقة ليست سوى خط الدفاع الأول. يغطي هذا الفصل آليات الحماية المتقدمة ضد الهجمات الشائعة والإدارة الدقيقة للأذونات.


🎯 أهداف الفصل​

في نهاية هذا الفصل، ستعرف:

  1. ✅ الحماية ضد هجمات CSRF
  2. ✅ تكوين CORS للـ API
  3. ✅ استخدام @PreAuthorize و@Secured
  4. ✅ تنفيذ نظام RBAC
  5. ✅ تكوين رؤوس الأمان

1. حماية CSRF​

ما هو CSRF؟​

Cross-Site Request Forgery: يقوم المهاجم بتنفيذ إجراءات لمستخدم متصل دون موافقته.

الحماية الافتراضية​

يقوم Spring Security بتفعيل CSRF افتراضيًا لتطبيقات الويب:

// افتراضيًا، CSRF مُفعل
// يتم التحقق من رمز CSRF تلقائيًا على POST، PUT، DELETE

رمز CSRF في النماذج​

<!-- Thymeleaf - تلقائي مع th:action -->
<form th:action="@{/transfer}" method="post">
<!-- يتم إضافة رمز CSRF تلقائيًا -->
<input type="hidden" name="_csrf" th:value="${_csrf.token}"/>
...
</form>

<!-- أو بشكل صريح -->
<form action="/transfer" method="post">
<input type="hidden"
th:name="${_csrf.parameterName}"
th:value="${_csrf.token}"/>
...
</form>

CSRF لـ API REST​

بالنسبة لـ API REST بدون حالة مع JWT، قم بتعطيل CSRF:

@Bean
public SecurityFilterChain filterChain(HttpSecurity http) throws Exception {
return http
.csrf(csrf -> csrf.disable()) // ← تعطيل لـ API REST
.sessionManagement(session -> session
.sessionCreationPolicy(SessionCreationPolicy.STATELESS)
)
// ...
.build();
}
warning

قم بتعطيل CSRF فقط إذا كنت تستخدم مصادقة بدون حالة (JWT، OAuth2). بالنسبة للجلسات، احتفظ به مفعلًا!

CSRF انتقائي​

@Bean
public SecurityFilterChain filterChain(HttpSecurity http) throws Exception {
return http
.csrf(csrf -> csrf
.ignoringRequestMatchers("/api/**") // معطل لـ /api/
.csrfTokenRepository(CookieCsrfTokenRepository.withHttpOnlyFalse())
)
.build();
}

2. تكوين CORS​

ما هو CORS؟​

Cross-Origin Resource Sharing: يتحكم في أي المجالات يمكنها الوصول إلى الـ API الخاص بك.

Frontend: https://app.example.com
Backend: https://api.example.com ← مجال مختلف!

بدون تكوين CORS → الطلبات محظورة من قبل المتصفح

التكوين العام​

@Configuration
public class CorsConfig {

@Bean
public CorsConfigurationSource corsConfigurationSource() {
CorsConfiguration configuration = new CorsConfiguration();

// الأصول المسموح بها
configuration.setAllowedOrigins(List.of(
"http://localhost:3000",
"https://app.example.com"
));

// الطرق HTTP المسموح بها
configuration.setAllowedMethods(List.of(
"GET", "POST", "PUT", "DELETE", "OPTIONS"
));

// الرؤوس المسموح بها
configuration.setAllowedHeaders(List.of(
"Authorization",
"Content-Type",
"X-Requested-With"
));

// السماح بالاعتمادات (الكوكيز، رأس Authorization)
configuration.setAllowCredentials(true);

// مدة تخزين preflight
configuration.setMaxAge(3600L);

UrlBasedCorsConfigurationSource source = new UrlBasedCorsConfigurationSource();
source.registerCorsConfiguration("/**", configuration);
return source;
}
}

التكامل مع Spring Security​

@Bean
public SecurityFilterChain filterChain(HttpSecurity http) throws Exception {
return http
.cors(Customizer.withDefaults()) // ← تفعيل CORS
.csrf(csrf -> csrf.disable())
// ...
.build();
}

CORS لكل Controller​

@RestController
@RequestMapping("/api/public")
@CrossOrigin(origins = "http://localhost:3000") // ← محدد
public class PublicController {
// ...
}

// أو لكل طريقة
@GetMapping("/data")
@CrossOrigin(origins = "*", maxAge = 3600)
public Data getData() {
// ...
}

3. الأمان على مستوى الطريقة​

التفعيل​

@Configuration
@EnableWebSecurity
@EnableMethodSecurity // ← تفعيل @PreAuthorize، @PostAuthorize، @Secured
public class SecurityConfig {
// ...
}

@PreAuthorize​

التحقق قبل تنفيذ الطريقة:

@RestController
@RequestMapping("/api")
public class UserController {

// التحقق من دور
@GetMapping("/admin/users")
@PreAuthorize("hasRole('ADMIN')")
public List<User> getAllUsers() { ... }

// التحقق من أدوار متعددة
@DeleteMapping("/users/{id}")
@PreAuthorize("hasAnyRole('ADMIN', 'MANAGER')")
public void deleteUser(@PathVariable Long id) { ... }

// التحقق من سلطة/إذن
@PostMapping("/users")
@PreAuthorize("hasAuthority('CREATE_USER')")
public User createUser(@RequestBody User user) { ... }

// تعبير معقد
@PutMapping("/users/{id}")
@PreAuthorize("hasRole('ADMIN') or #id == authentication.principal.id")
public User updateUser(@PathVariable Long id, @RequestBody User user) { ... }

// الوصول إلى كائن المصادقة
@GetMapping("/me")
@PreAuthorize("isAuthenticated()")
public User getCurrentUser(Authentication auth) { ... }
}

@PostAuthorize​

التحقق بعد التنفيذ (مفيد للتحقق من النتيجة):

@GetMapping("/users/{id}")
@PostAuthorize("returnObject.username == authentication.name or hasRole('ADMIN')")
public User getUser(@PathVariable Long id) {
return userRepository.findById(id).orElseThrow();
}

@Secured (قديم)​

أبسط ولكن أقل مرونة:

@Secured("ROLE_ADMIN")
public void adminOnly() { ... }

@Secured({"ROLE_ADMIN", "ROLE_MANAGER"})
public void adminOrManager() { ... }

تعبيرات SpEL المتاحة​

التعبيرالوصف
hasRole('ROLE')لديه الدور (يضيف ROLE_ تلقائيًا)
hasAnyRole('R1', 'R2')لديه أحد الأدوار
hasAuthority('AUTH')لديه السلطة الدقيقة
hasAnyAuthority('A1', 'A2')لديه أحد السلطات
isAuthenticated()متصل
isAnonymous()غير متصل
isFullyAuthenticated()ليس عبر remember-me
permitAllمسموح دائمًا
denyAllمرفوض دائمًا
#paramNameالوصول إلى المعلمات
returnObjectالنتيجة (@PostAuthorize)
authenticationكائن Authentication
principalالمستخدم (UserDetails)

4. نظام RBAC (التحكم في الوصول المعتمد على الأدوار)​

نموذج البيانات​

الكيانات​

@Entity
@Table(name = "permissions")
@Data
public class Permission {
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;

@Column(unique = true, nullable = false)
private String name; // READ_USERS, WRITE_USERS, DELETE_USERS
}

@Entity
@Table(name = "roles")
@Data
public class Role {
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;

@Column(unique = true, nullable = false)
private String name; // ROLE_ADMIN, ROLE_USER, ROLE_MANAGER

@ManyToMany(fetch = FetchType.EAGER)
@JoinTable(
name = "role_permissions",
joinColumns = @JoinColumn(name = "role_id"),
inverseJoinColumns = @JoinColumn(name = "permission_id")
)
private Set<Permission> permissions = new HashSet<>();
}

UserDetails مع الأذونات​

public class CustomUserDetails implements UserDetails {
private final User user;

@Override
public Collection<? extends GrantedAuthority> getAuthorities() {
Set<GrantedAuthority> authorities = new HashSet<>();

for (Role role : user.getRoles()) {
// إضافة الدور
authorities.add(new SimpleGrantedAuthority(role.getName()));

// إضافة أذونات الدور
for (Permission permission : role.getPermissions()) {
authorities.add(new SimpleGrantedAuthority(permission.getName()));
}
}

return authorities;
}
// ...
}

الاستخدام​

// حسب الدور
@PreAuthorize("hasRole('ADMIN')")
public void adminAction() { }

// حسب الإذن
@PreAuthorize("hasAuthority('DELETE_USERS')")
public void deleteUser(Long id) { }

// مزيج
@PreAuthorize("hasRole('MANAGER') and hasAuthority('APPROVE_ORDERS')")
public void approveOrder(Long orderId) { }

5. رؤوس الأمان​

التكوين​

@Bean
public SecurityFilterChain filterChain(HttpSecurity http) throws Exception {
return http
.headers(headers -> headers
// سياسة أمان المحتوى
.contentSecurityPolicy(csp -> csp
.policyDirectives("default-src 'self'; script-src 'self'")
)
// X-Frame-Options (clickjacking)
.frameOptions(frame -> frame.sameOrigin())
// X-Content-Type-Options
.contentTypeOptions(Customizer.withDefaults())
// X-XSS-Protection
.xssProtection(xss -> xss.headerValue(XXssProtectionHeaderWriter.HeaderValue.ENABLED_MODE_BLOCK))
// HSTS (HTTPS إلزامي)
.httpStrictTransportSecurity(hsts -> hsts
.includeSubDomains(true)
.maxAgeInSeconds(31536000)
)
)
.build();
}

الرؤوس الناتجة​

Content-Security-Policy: default-src 'self'; script-src 'self'
X-Frame-Options: SAMEORIGIN
X-Content-Type-Options: nosniff
X-XSS-Protection: 1; mode=block
Strict-Transport-Security: max-age=31536000; includeSubDomains

6. الحماية ضد الهجمات​

القوة الغاشمة (تحديد المحاولات)​

@Service
public class LoginAttemptService {
private final Cache<String, Integer> attemptsCache = CacheBuilder.newBuilder()
.expireAfterWrite(1, TimeUnit.HOURS)
.build();

public void loginSucceeded(String key) {
attemptsCache.invalidate(key);
}

public void loginFailed(String key) {
int attempts = attemptsCache.get(key, () -> 0);
attemptsCache.put(key, attempts + 1);
}

public boolean isBlocked(String key) {
return attemptsCache.get(key, () -> 0) >= 5;
}
}

التكامل مع المصادقة​

@Component
@RequiredArgsConstructor
public class AuthenticationFailureListener
implements ApplicationListener<AuthenticationFailureBadCredentialsEvent> {

private final LoginAttemptService loginAttemptService;

@Override
public void onApplicationEvent(AuthenticationFailureBadCredentialsEvent event) {
String username = (String) event.getAuthentication().getPrincipal();
loginAttemptService.loginFailed(username);
}
}

@Component
@RequiredArgsConstructor
public class AuthenticationSuccessListener
implements ApplicationListener<AuthenticationSuccessEvent> {

private final LoginAttemptService loginAttemptService;

@Override
public void onApplicationEvent(AuthenticationSuccessEvent event) {
String username = event.getAuthentication().getName();
loginAttemptService.loginSucceeded(username);
}
}

7. تدقيق الأمان​

تسجيل الأحداث​

@Component
@Slf4j
public class SecurityAuditListener {

@EventListener
public void onSuccess(AuthenticationSuccessEvent event) {
log.info("LOGIN SUCCESS: {} at {}",
event.getAuthentication().getName(),
LocalDateTime.now());
}

@EventListener
public void onFailure(AuthenticationFailureBadCredentialsEvent event) {
log.warn("LOGIN FAILED: {} at {}",
event.getAuthentication().getPrincipal(),
LocalDateTime.now());
}

@EventListener
public void onAccessDenied(AuthorizationDeniedEvent event) {
log.warn("ACCESS DENIED: {} tried to access {}",
event.getAuthentication().get().getName(),
event.getSource());
}
}

8. ملخص​

الحمايات الأساسية​

الحمايةضدالتكوين
CSRFالطلبات المزورةمفعل افتراضيًا (تعطيل لـ API REST)
CORSالطلبات عبر المجالاتcors(Customizer.withDefaults())
XSSالسكربتات الضارةالرؤوس + CSP
Clickjackingالإطارات الضارةX-Frame-Options
Brute Forceالهجمات بالقوة الغاشمةتحديد المحاولات

أمان الطريقة​

@PreAuthorize("hasRole('ADMIN')")           // حسب الدور
@PreAuthorize("hasAuthority('DELETE_USER')") // حسب الإذن
@PreAuthorize("#id == authentication.principal.id") // حسب التعبير

قائمة التحقق من الأمان​

✅ CSRF مفعل للجلسات
✅ CORS مكون للـ API
✅ كلمات المرور مشفرة (BCrypt)
✅ رؤوس الأمان مكونة
✅ @PreAuthorize على الطرق الحساسة
✅ تدقيق أحداث الأمان
✅ الحماية ضد القوة الغاشمة

🎯 الخطوة التالية​

لديك الآن كل النظرية! حان وقت التطبيق مع الأعمال العملية.

➡️ TP8.1: تسجيل الدخول باستخدام النموذج


⬅️ العودة إلى الوحدة 8