اختبار: Spring Security
اختبر معرفتك بأمان Spring، JWT وOAuth2.
30 سؤالاً - انقر على كل سؤال لكشف الإجابة.
الأسئلة 1-10: أساسيات الأمان
السؤال 1: ما هي التبعية التي يجب إضافتها لـ Spring Security؟
الإجابة:
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-security</artifactId>
</dependency>
بشكل افتراضي، جميع المسارات محمية.
السؤال 2: ماذا يحدث عند إضافة Spring Security بدون تكوين؟
الإجابة:
- جميع المسارات تتطلب مصادقة
- يتم عرض نموذج تسجيل دخول
- يتم إنشاء مستخدم
userبكلمة مرور مولدة (معروضة في السجلات) - يتم تفعيل CSRF
السؤال 3: كيف يتم تكوين الأمان في Spring Boot 3؟
الإجابة:
@Configuration
@EnableWebSecurity
public class SecurityConfig {
@Bean
public SecurityFilterChain filterChain(HttpSecurity http) throws Exception {
http
.csrf(csrf -> csrf.disable())
.authorizeHttpRequests(auth -> auth
.requestMatchers("/api/public/**").permitAll()
.requestMatchers("/api/admin/**").hasRole("ADMIN")
.anyRequest().authenticated()
)
.httpBasic(Customizer.withDefaults());
return http.build();
}
}
السؤال 4: ما الفرق بين authenticated() و permitAll()؟
الإجابة:
permitAll(): يمكن الوصول إليه بدون مصادقةauthenticated(): يتطلب مستخدمًا مصادقًاhasRole("X"): يتطلب الدور XhasAuthority("X"): يتطلب السلطة X
السؤال 5: كيف يتم تعريف المستخدمين في الذاكرة؟
الإجابة:
@Bean
public UserDetailsService userDetailsService() {
UserDetails user = User.builder()
.username("user")
.password(passwordEncoder().encode("password"))
.roles("USER")
.build();
UserDetails admin = User.builder()
.username("admin")
.password(passwordEncoder().encode("admin"))
.roles("ADMIN")
.build();
return new InMemoryUserDetailsManager(user, admin);
}
@Bean
public PasswordEncoder passwordEncoder() {
return new BCryptPasswordEncoder();
}
السؤال 6: كيف يتم تنفيذ UserDetailsService مع قاعدة بيانات؟
الإجابة:
@Service
public class CustomUserDetailsService implements UserDetailsService {
private final UserRepository userRepository;
@Override
public UserDetails loadUserByUsername(String username) {
User user = userRepository.findByEmail(username)
.orElseThrow(() -> new UsernameNotFoundException("User not found"));
return org.springframework.security.core.userdetails.User.builder()
.username(user.getEmail())
.password(user.getPassword())
.roles(user.getRole().name())
.build();
}
}
السؤال 7: لماذا يتم تشفير كلمات المرور؟
الإجابة:
- الأمان: كلمات المرور غير قابلة للقراءة إذا تم اختراق قاعدة البيانات
- BCrypt: يولد تجزئة مختلفة في كل مرة (ملح مدمج)
- إلزام: Spring Security يرفض كلمات المرور النصية
@Bean
public PasswordEncoder passwordEncoder() {
return new BCryptPasswordEncoder();
}
السؤال 8: ما الفرق بين Role و Authority؟
الإجابة:
- Role: يتم إضافة البادئة
ROLE_تلقائيًا - Authority: يتم استخدامها كما هي
// هذان متكافئان:
.hasRole("ADMIN") // يتحقق من ROLE_ADMIN
.hasAuthority("ROLE_ADMIN")
// سلطات مخصصة
.hasAuthority("READ_USERS")
.hasAuthority("WRITE_USERS")
السؤال 9: ما هو CSRF ومتى يتم تعطيله؟
الإجابة: CSRF (Cross-Site Request Forgery) يحمي من الطلبات الضارة.
تعطيله لـ API REST بدون حالة (JWT):
http.csrf(csrf -> csrf.disable())
ابقه مفعلاً للتطبيقات ذات الجلسات.
السؤال 10: كيف يتم استرداد المستخدم المتصل؟
الإجابة:
// في وحدة تحكم
@GetMapping("/me")
public User getCurrentUser(@AuthenticationPrincipal UserDetails userDetails) {
return userService.findByEmail(userDetails.getUsername());
}
// أو عبر SecurityContextHolder
Authentication auth = SecurityContextHolder.getContext().getAuthentication();
String username = auth.getName();
الأسئلة 11-20: JWT
السؤال 11: ما هو JWT؟
الإجابة: JSON Web Token - رمز مكون من 3 أجزاء:
- Header: الخوارزمية والنوع
- Payload: المطالبات (البيانات)
- Signature: التحقق من السلامة
التنسيق: header.payload.signature (مشفرة Base64)
السؤال 12: ما هي المطالبات القياسية لـ JWT؟
الإجابة:
| المطالبة | الوصف |
|---|---|
sub | الموضوع (المستخدم) |
iss | المصدر (المرسل) |
exp | انتهاء الصلاحية |
iat | تم الإصدار في |
nbf | ليس قبل |
jti | معرف JWT (فريد) |
السؤال 13: كيف يتم توليد JWT؟
الإجابة:
@Service
public class JwtService {
@Value("${jwt.secret}")
private String secret;
public String generateToken(UserDetails user) {
return Jwts.builder()
.setSubject(user.getUsername())
.setIssuedAt(new Date())
.setExpiration(new Date(System.currentTimeMillis() + 86400000))
.signWith(getSigningKey(), SignatureAlgorithm.HS256)
.compact();
}
private Key getSigningKey() {
return Keys.hmacShaKeyFor(Decoders.BASE64.decode(secret));
}
}
السؤال 14: كيف يتم التحقق من صحة JWT؟
الإجابة:
public boolean isTokenValid(String token, UserDetails userDetails) {
try {
Claims claims = Jwts.parserBuilder()
.setSigningKey(getSigningKey())
.build()
.parseClaimsJws(token)
.getBody();
String username = claims.getSubject();
Date expiration = claims.getExpiration();
return username.equals(userDetails.getUsername())
&& !expiration.before(new Date());
} catch (JwtException e) {
return false;
}
}
السؤال 15: كيف يتم تنفيذ فلتر JWT؟
الإجابة:
@Component
public class JwtAuthFilter extends OncePerRequestFilter {
@Override
protected void doFilterInternal(HttpServletRequest request,
HttpServletResponse response, FilterChain chain) {
String header = request.getHeader("Authorization");
if (header != null && header.startsWith("Bearer ")) {
String token = header.substring(7);
String username = jwtService.extractUsername(token);
if (username != null && SecurityContextHolder.getContext().getAuthentication() == null) {
UserDetails user = userDetailsService.loadUserByUsername(username);
if (jwtService.isTokenValid(token, user)) {
UsernamePasswordAuthenticationToken auth =
new UsernamePasswordAuthenticationToken(user, null, user.getAuthorities());
SecurityContextHolder.getContext().setAuthentication(auth);
}
}
}
chain.doFilter(request, response);
}
}
السؤال 16: كيف يتم تكوين فلتر JWT؟
الإجابة:
@Bean
public SecurityFilterChain filterChain(HttpSecurity http) throws Exception {
http
.csrf(csrf -> csrf.disable())
.sessionManagement(session ->
session.sessionCreationPolicy(SessionCreationPolicy.STATELESS))
.authorizeHttpRequests(auth -> auth
.requestMatchers("/api/auth/**").permitAll()
.anyRequest().authenticated()
)
.addFilterBefore(jwtAuthFilter, UsernamePasswordAuthenticationFilter.class);
return http.build();
}
السؤال 17: ما هو Refresh Token؟
الإجابة: رمز طويل الأمد للحصول على رموز وصول جديدة بدون إعادة تسجيل الدخول:
- Access Token: مدة قصيرة (15 دقيقة - 1 ساعة)
- Refresh Token: مدة طويلة (7-30 يومًا)
// نقطة نهاية للتحديث
@PostMapping("/refresh")
public TokenResponse refresh(@RequestBody RefreshRequest request) {
String refreshToken = request.getRefreshToken();
// تحقق وتوليد رمز وصول جديد
}
السؤال 18: كيف يتم تخزين سر JWT بشكل آمن؟
الإجابة:
- متغيرات البيئة
- Vault (HashiCorp Vault)
- AWS Secrets Manager
- Spring Cloud Config (مشفرة)
# لا تقم أبدًا بارتكاب السر!
jwt:
secret: ${JWT_SECRET}
السؤال 19: كيف يتم إبطال JWT؟
الإجابة: JWT غير مرتبطة بالحالة، لذا لا يمكن إبطالها مباشرة. الحلول:
- القائمة السوداء: تخزين الرموز الملغاة (Redis)
- انتهاء صلاحية قصير: الرموز صالحة لفترة قصيرة
- إصدار المستخدم: إبطال في كل تغيير لكلمة المرور
السؤال 20: كيف يتم تأمين نقاط النهاية باستخدام التعليقات التوضيحية؟
الإجابة:
@EnableMethodSecurity
@Configuration
public class SecurityConfig { }
@RestController
public class UserController {
@PreAuthorize("hasRole('ADMIN')")
@GetMapping("/admin/users")
public List<User> getAllUsers() { }
@PreAuthorize("#id == authentication.principal.id or hasRole('ADMIN')")
@GetMapping("/users/{id}")
public User getUser(@PathVariable Long id) { }
}
الأسئلة 21-30: OAuth2 والأمان المتقدم
السؤال 21: ما هو OAuth2؟
الإجابة: بروتوكول تفويض يسمح لتطبيق بالوصول إلى موارد مستخدم عبر مزود طرف ثالث (Google، GitHub، إلخ) بدون مشاركة كلمة المرور.
السؤال 22: ما هي الأدوار في OAuth2؟
الإجابة:
- Resource Owner: المستخدم
- Client: التطبيق
- Authorization Server: Google، GitHub...
- Resource Server: API المحمية
السؤال 23: كيف يتم تكوين OAuth2 مع Google؟
الإجابة:
spring:
security:
oauth2:
client:
registration:
google:
client-id: ${GOOGLE_CLIENT_ID}
client-secret: ${GOOGLE_CLIENT_SECRET}
scope: openid, profile, email
http.oauth2Login(Customizer.withDefaults());
السؤال 24: كيف يتم تخصيص نجاح تسجيل الدخول OAuth2؟
الإجابة:
http.oauth2Login(oauth -> oauth
.successHandler((request, response, authentication) -> {
OAuth2User oauthUser = (OAuth2User) authentication.getPrincipal();
userService.processOAuthUser(oauthUser);
response.sendRedirect("/dashboard");
})
);
السؤال 25: ما هو CORS؟
الإجابة: Cross-Origin Resource Sharing - يسمح بالطلبات من نطاقات أخرى:
@Configuration
public class CorsConfig {
@Bean
public CorsConfigurationSource corsConfigurationSource() {
CorsConfiguration config = new CorsConfiguration();
config.setAllowedOrigins(List.of("http://localhost:3000"));
config.setAllowedMethods(List.of("GET", "POST", "PUT", "DELETE"));
config.setAllowedHeaders(List.of("*"));
config.setAllowCredentials(true);
UrlBasedCorsConfigurationSource source = new UrlBasedCorsConfigurationSource();
source.registerCorsConfiguration("/**", config);
return source;
}
}
السؤال 26: كيف يتم إضافة رؤوس الأمان؟
الإجابة:
http.headers(headers -> headers
.contentSecurityPolicy(csp -> csp.policyDirectives("default-src 'self'"))
.frameOptions(frame -> frame.deny())
.xssProtection(xss -> xss.enable())
);
السؤال 27: كيف يتم تحديد محاولات تسجيل الدخول؟
الإجابة: تنفيذ محدد معدل:
@Component
public class LoginAttemptService {
private final LoadingCache<String, Integer> attemptsCache;
public void loginFailed(String ip) {
int attempts = attemptsCache.get(ip);
attemptsCache.put(ip, attempts + 1);
}
public boolean isBlocked(String ip) {
return attemptsCache.get(ip) >= MAX_ATTEMPTS;
}
}
السؤال 28: كيف يتم تدقيق الوصول الآمن؟
الإجابة:
@Component
public class SecurityAuditListener {
@EventListener
public void onAuthSuccess(AuthenticationSuccessEvent event) {
log.info("Login successful: {}", event.getAuthentication().getName());
}
@EventListener
public void onAuthFailure(AuthenticationFailureBadCredentialsEvent event) {
log.warn("Login failed: {}", event.getAuthentication().getName());
}
}
السؤال 29: كيف يتم اختبار الأمان؟
الإجابة:
@WebMvcTest(UserController.class)
@Import(SecurityConfig.class)
class UserControllerSecurityTest {
@Test
@WithMockUser(roles = "ADMIN")
void adminCanAccessAdminEndpoint() {
mockMvc.perform(get("/admin/users"))
.andExpect(status().isOk());
}
@Test
@WithMockUser(roles = "USER")
void userCannotAccessAdminEndpoint() {
mockMvc.perform(get("/admin/users"))
.andExpect(status().isForbidden());
}
}
السؤال 30: ما هي أفضل ممارسات الأمان في Spring؟
الإجابة:
- تشفير كلمات المرور دائمًا (BCrypt)
- استخدام HTTPS في الإنتاج
- تكوين CORS بشكل صارم
- تنفيذ تحديد المعدل
- التحقق من جميع المدخلات
- تسجيل أحداث الأمان
- استخدام أسرار قوية وتخزينها خارجيًا
- تحديث التبعيات بانتظام
النتيجة
| النتيجة | المستوى |
|---|---|
| 27-30 | ممتاز - أمان متقن |
| 21-26 | جيد - بعض المراجعات |
| 15-20 | متوسط - أعد قراءة الفصول |
| أقل من 15 | بحاجة إلى مراجعة |