الفصل 5: الأمان المتقدم - الوحدات 11 إلى 18
الأهداف
- إتقان Spring Security بشكل معمق
- تنفيذ أمان قوي خطوة بخطوة
- فهم كل مكون في سلسلة الأمان
المورد الرئيسي
ملاحق الأمان
هذا الفصل مبني على الملاحق-الأمان-1 من المستودع، وهو دليل ممتاز خطوة بخطوة:
الوحدات 11 إلى 18: مشروع كامل لأمان Spring Boot.
خطة وحدات الأمان
| الوحدة | العنوان | المحتوى |
|---|---|---|
| 11 | مقدمة في Spring Security | مفاهيم أساسية، بنية |
| 12 | التكوين الأولي | SecurityFilterChain، HttpSecurity |
| 13 | المصادقة | UserDetails، UserDetailsService |
| 14 | تشفير كلمات المرور | BCryptPasswordEncoder |
| 15 | JWT - النظرية | الهيكل، المطالبات، التوقيع |
| 16 | JWT - التنفيذ | JwtService، التوليد، التحقق |
| 17 | الفلاتر المخصصة | JwtAuthenticationFilter |
| 18 | التفويض | الأدوار، @PreAuthorize، أمان الطريقة |
الوحدة 11: مقدمة في Spring Security
ما هو Spring Security؟
Spring Security هو إطار الأمان القياسي لتطبيقات Spring. يوفر:
- المصادقة: التحقق من هوية المستخدم
- التفويض: التحكم في الوصول إلى الموارد
- الحماية من الهجمات: CSRF، XSS، إلخ.
بنية Spring Security
┌───────────────────────────────────────────────────────────┐
│ SecurityFilterChain │
│ │
│ Request ──► Filter 1 ──► Filter 2 ──► ... ──► Controller
│ │
│ كل فلتر يمكن أن: │
│ - يمرر الطلب │
│ - يحظر الطلب (401/403) │
│ - يعدل الطلب │
└───────────────────────────────────────────────────────────┘
تبعية Maven
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-security</artifactId>
</dependency>
الوحدة 12: التكوين الأولي
SecurityConfig الأساسية
@Configuration
@EnableWebSecurity
public class SecurityConfig {
@Bean
public SecurityFilterChain securityFilterChain(HttpSecurity http) throws Exception {
return http
.csrf(csrf -> csrf.disable()) // تعطيل CSRF لواجهة برمجة التطبيقات REST
.authorizeHttpRequests(auth -> auth
.anyRequest().permitAll() // السماح بكل شيء في البداية
)
.build();
}
}
التكوين التدريجي
@Bean
public SecurityFilterChain securityFilterChain(HttpSecurity http) throws Exception {
return http
.csrf(csrf -> csrf.disable())
.authorizeHttpRequests(auth -> auth
// نقاط النهاية العامة
.requestMatchers("/api/contact").permitAll()
.requestMatchers("/api/auth/**").permitAll()
.requestMatchers("/h2-console/**").permitAll()
// نقاط النهاية المحمية
.requestMatchers("/api/admin/**").authenticated()
// كل ما تبقى
.anyRequest().authenticated()
)
.headers(headers -> headers.frameOptions(f -> f.disable())) // للوحة H2
.build();
}
الوحدة 13: المصادقة
كيان المستخدم
@Entity
@Table(name = "users")
public class User implements UserDetails {
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;
@Column(unique = true)
private String email;
private String password;
@Enumerated(EnumType.STRING)
private Role role;
@Override
public Collection<? extends GrantedAuthority> getAuthorities() {
return List.of(new SimpleGrantedAuthority("ROLE_" + role.name()));
}
@Override
public String getUsername() {
return email;
}
@Override
public boolean isAccountNonExpired() { return true; }
@Override
public boolean isAccountNonLocked() { return true; }
@Override
public boolean isCredentialsNonExpired() { return true; }
@Override
public boolean isEnabled() { return true; }
}
UserDetailsService
@Service
@RequiredArgsConstructor
public class UserDetailsServiceImpl implements UserDetailsService {
private final UserRepository userRepository;
@Override
public UserDetails loadUserByUsername(String email) throws UsernameNotFoundException {
return userRepository.findByEmail(email)
.orElseThrow(() -> new UsernameNotFoundException("المستخدم غير موجود: " + email));
}
}
تكوين AuthenticationProvider
@Configuration
public class SecurityConfig {
@Bean
public AuthenticationProvider authenticationProvider(
UserDetailsService userDetailsService,
PasswordEncoder passwordEncoder) {
DaoAuthenticationProvider provider = new DaoAuthenticationProvider();
provider.setUserDetailsService(userDetailsService);
provider.setPasswordEncoder(passwordEncoder);
return provider;
}
@Bean
public AuthenticationManager authenticationManager(AuthenticationConfiguration config)
throws Exception {
return config.getAuthenticationManager();
}
}
الوحدة 14: تشفير كلمات المرور
BCryptPasswordEncoder
@Bean
public PasswordEncoder passwordEncoder() {
return new BCryptPasswordEncoder();
}
الاستخدام عند التسجيل
@Service
@RequiredArgsConstructor
public class AuthService {
private final UserRepository userRepository;
private final PasswordEncoder passwordEncoder;
public User register(RegisterRequest request) {
User user = new User();
user.setEmail(request.getEmail());
user.setPassword(passwordEncoder.encode(request.getPassword())); // التشفير!
user.setRole(Role.USER);
return userRepository.save(user);
}
}
التحقق عند تسجيل الدخول
public String login(LoginRequest request) {
// AuthenticationManager يدير التحقق من كلمة المرور تلقائيًا
Authentication authentication = authenticationManager.authenticate(
new UsernamePasswordAuthenticationToken(
request.getEmail(),
request.getPassword()
)
);
// إذا وصلنا إلى هنا، فقد نجحت المصادقة
User user = (User) authentication.getPrincipal();
return jwtService.generateToken(user);
}
الوحدة 15: JWT - النظرية
هيكل JWT
JWT (JSON Web Token) يتكون من ثلاثة أجزاء مفصولة بنقاط:
xxxxx.yyyyy.zzzzz
│ │ │
│ │ └── التوقيع
│ └── الحمولة (المطالبات)
└── الرأس
الرأس
{
"alg": "HS256",
"typ": "JWT"
}
الحمولة (المطالبات)
{
"sub": "user@example.com", // الموضوع (المعرف)
"iat": 1609459200, // الإصدار في (تاريخ الإنشاء)
"exp": 1609545600, // انتهاء الصلاحية
"roles": ["USER"] // مطالبات مخصصة
}
التوقيع
HMACSHA256(
base64UrlEncode(header) + "." + base64UrlEncode(payload),
secret_key
)
الوحدة 16: JWT - التنفيذ
التبعية
<dependency>
<groupId>io.jsonwebtoken</groupId>
<artifactId>jjwt-api</artifactId>
<version>0.12.3</version>
</dependency>
<dependency>
<groupId>io.jsonwebtoken</groupId>
<artifactId>jjwt-impl</artifactId>
<version>0.12.3</version>
<scope>runtime</scope>
</dependency>
<dependency>
<groupId>io.jsonwebtoken</groupId>
<artifactId>jjwt-jackson</artifactId>
<version>0.12.3</version>
<scope>runtime</scope>
</dependency>
JwtService
@Service
public class JwtService {
@Value("${jwt.secret}")
private String secretKey;
@Value("${jwt.expiration}")
private long expiration; // بالملي ثانية
// توليد رمز
public String generateToken(UserDetails userDetails) {
return Jwts.builder()
.subject(userDetails.getUsername())
.claim("roles", userDetails.getAuthorities().stream()
.map(GrantedAuthority::getAuthority)
.toList())
.issuedAt(new Date())
.expiration(new Date(System.currentTimeMillis() + expiration))
.signWith(getSigningKey())
.compact();
}
// استخراج اسم المستخدم
public String extractUsername(String token) {
return extractClaim(token, Claims::getSubject);
}
// التحقق من صحة الرمز
public boolean isTokenValid(String token, UserDetails userDetails) {
String username = extractUsername(token);
return username.equals(userDetails.getUsername()) && !isTokenExpired(token);
}
private boolean isTokenExpired(String token) {
return extractClaim(token, Claims::getExpiration).before(new Date());
}
private <T> T extractClaim(String token, Function<Claims, T> resolver) {
Claims claims = Jwts.parser()
.verifyWith(getSigningKey())
.build()
.parseSignedClaims(token)
.getPayload();
return resolver.apply(claims);
}
private SecretKey getSigningKey() {
byte[] keyBytes = Decoders.BASE64.decode(secretKey);
return Keys.hmacShaKeyFor(keyBytes);
}
}
تكوين application.yml
jwt:
secret: "votre-cle-secrete-tres-longue-au-moins-256-bits-encodee-en-base64"
expiration: 86400000 # 24 ساعة بالملي ثانية
الوحدة 17: الفلاتر المخصصة
JwtAuthenticationFilter
@Component
@RequiredArgsConstructor
public class JwtAuthenticationFilter extends OncePerRequestFilter {
private final JwtService jwtService;
private final UserDetailsService userDetailsService;
@Override
protected void doFilterInternal(
HttpServletRequest request,
HttpServletResponse response,
FilterChain filterChain) throws ServletException, IOException {
// 1. استرجاع رأس Authorization
String authHeader = request.getHeader("Authorization");
// 2. التحقق مما إذا كان الرأس موجودًا ويبدأ بـ "Bearer "
if (authHeader == null || !authHeader.startsWith("Bearer ")) {
filterChain.doFilter(request, response);
return;
}
// 3. استخراج الرمز
String token = authHeader.substring(7);
String username = jwtService.extractUsername(token);
// 4. إذا تم العثور على اسم المستخدم ولم يتم المصادقة بعد
if (username != null && SecurityContextHolder.getContext().getAuthentication() == null) {
// 5. تحميل المستخدم
UserDetails userDetails = userDetailsService.loadUserByUsername(username);
// 6. التحقق من صحة الرمز
if (jwtService.isTokenValid(token, userDetails)) {
// 7. إنشاء المصادقة
UsernamePasswordAuthenticationToken authToken =
new UsernamePasswordAuthenticationToken(
userDetails,
null,
userDetails.getAuthorities()
);
authToken.setDetails(new WebAuthenticationDetailsSource().buildDetails(request));
// 8. وضع في SecurityContext
SecurityContextHolder.getContext().setAuthentication(authToken);
}
}
// 9. متابعة سلسلة الفلاتر
filterChain.doFilter(request, response);
}
}
التكامل في SecurityConfig
@Bean
public SecurityFilterChain securityFilterChain(HttpSecurity http) throws Exception {
return http
.csrf(csrf -> csrf.disable())
.authorizeHttpRequests(auth -> auth
.requestMatchers("/api/contact").permitAll()
.requestMatchers("/api/auth/**").permitAll()
.requestMatchers("/api/admin/**").hasRole("ADMIN")
.anyRequest().authenticated()
)
.sessionManagement(session ->
session.sessionCreationPolicy(SessionCreationPolicy.STATELESS)
)
.authenticationProvider(authenticationProvider)
// إضافة فلترنا قبل UsernamePasswordAuthenticationFilter
.addFilterBefore(jwtAuthenticationFilter, UsernamePasswordAuthenticationFilter.class)
.build();
}
الوحدة 18: التفويض
التعليقات التوضيحية للأمان
@Configuration
@EnableMethodSecurity // تفعيل @PreAuthorize، @PostAuthorize، إلخ.
public class SecurityConfig {
// ...
}
@PreAuthorize
@RestController
@RequestMapping("/api/admin")
public class AdminController {
// متاح فقط لـ ADMIN
@GetMapping("/leads")
@PreAuthorize("hasRole('ADMIN')")
public List<LeadDto> getAllLeads() {
return leadService.getAllLeads();
}
// متاح لـ ADMIN و MANAGER
@GetMapping("/leads/stats")
@PreAuthorize("hasAnyRole('ADMIN', 'MANAGER')")
public StatsDto getStats() {
return leadService.getStats();
}
// التحقق الديناميكي
@GetMapping("/users/{id}")
@PreAuthorize("#id == principal.id or hasRole('ADMIN')")
public UserDto getUser(@PathVariable Long id) {
return userService.getUser(id);
}
}
@PostAuthorize
@GetMapping("/{id}")
@PostAuthorize("returnObject.email == principal.username or hasRole('ADMIN')")
public LeadDto getLead(@PathVariable Long id) {
return leadService.getLead(id);
}
الأمان على مستوى الخدمة
@Service
public class LeadService {
@PreAuthorize("hasRole('ADMIN')")
public void deleteLead(Long id) {
leadRepository.deleteById(id);
}
@PreAuthorize("isAuthenticated()")
public List<LeadDto> getMyLeads() {
// استرجاع المستخدم المتصل
Authentication auth = SecurityContextHolder.getContext().getAuthentication();
String email = auth.getName();
return leadRepository.findByCreatedBy(email);
}
}
البنية النهائية الكاملة
┌─────────────────────────────────────────────────────────────────┐
│ طلب العميل │
│ Authorization: Bearer <token> │
└───────────────────────────────┬─────────────────────────────────┘
│
▼
┌─────────────────────────────────────────────────────────────────┐
│ SecurityFilterChain │
│ │
│ ┌───────────────────────────────────────────────────────────┐ │
│ │ 1. CorsFilter │ │
│ │ - يدير الطلبات عبر الأصل │ │
│ └───────────────────────────────────────────────────────────┘ │
│ │ │
│ ▼ │
│ ┌───────────────────────────────────────────────────────────┐ │
│ │ 2. JwtAuthenticationFilter │ │
│ │ - يستخرج الرمز من الرأس │ │
│ │ - يتحقق من صحة الرمز │ │
│ │ - يحمل UserDetails │ │
│ │ - ينشئ Authentication │ │
│ │ - يضع في SecurityContext │ │
│ └───────────────────────────────────────────────────────────┘ │
│ │ │
│ ▼ │
│ ┌───────────────────────────────────────────────────────────┐ │
│ │ 3. AuthorizationFilter │ │
│ │ - يتحقق من الأذونات │ │
│ │ - hasRole()، hasAuthority() │ │
│ │ - يعيد 403 إذا لم يكن مصرحًا │ │
│ └───────────────────────────────────────────────────────────┘ │
└───────────────────────────────┬─────────────────────────────────┘
│
▼
┌─────────────────────────────────────────────────────────────────┐
│ Controller │
│ @PreAuthorize تم التحقق منها │
└─────────────────────────────────────────────────────────────────┘
قائمة التحقق من الإتقان
بعد دراسة هذه الوحدات، يجب أن تكون قادرًا على:
- تكوين SecurityFilterChain
- تنفيذ UserDetails و UserDetailsService
- تشفير كلمات المرور باستخدام BCrypt
- توليد والتحقق من رموز JWT
- إنشاء فلتر مصادقة مخصص
- استخدام @PreAuthorize للتفويض
- كتابة اختبارات الأمان
المورد الكامل
الوصول المباشر
للتفاصيل الكاملة لكل وحدة مع جميع أمثلة الكود:
** ملاحق الأمان - الوحدات 11 إلى 18**
إنه مشروع أمان ممتاز للدراسة بعمق!
تهانينا!
لقد أكملت الوحدة 16 حول مشروع e-contact-backend. لديك الآن:
- قمت بتكوين بيئة باستخدام GitHub Codespaces
- أتقنت استكشاف أخطاء Spring Boot وإصلاحها
- دمجت واجهة أمامية مع الخلفية
- تعمقت في مفاهيم الأمان
- فهمت Spring Security من الألف إلى الياء
⏭️ الخطوات التالية
- الممارسة: استنساخ المستودع والتجربة
- التعديل: إضافة ميزات جديدة
- الاختبار: كتابة اختبارات الأمان الخاصة بك
- النشر: وضع في الإنتاج على مزود سحابي