مقدمه
امنیت یکی از حیاتیترین ستونهای هر سیستم نرمافزاری است. در دنیای Spring Boot، امنیت نه یک افزونه، بلکه یک معماری توکار و یکپارچه است که توسط فریمورک Spring Security مدیریت میشود. برای توسعهدهندگانی که از اکوسیستمهایی مانند Node.js یا NestJS وارد دنیای Java میشوند، درک مفاهیم Authentication، Guard و Middleware در Spring ممکن است در ابتدا چالشبرانگیز باشد، چرا که Spring از الگوهای طراحی متفاوت و عمیقتری استفاده میکند. این مقاله راهنمای کاملی برای درک این مفاهیم و پیادهسازی یک معماری امنیتی حرفهای است.
چرا امنیت Route در Spring Boot متفاوت به نظر میرسد؟
در بسیاری از فریمورکهای مدرن، مفاهیم امنیت به صورت کامپوننتهای جداگانه (مثل Guard در NestJS) تعریف میشوند. در Spring Boot، مفهوم "امنیت" به صورت یک Servlet Filter Chain پیادهسازی شده است. این یعنی قبل از اینکه درخواست به Controller شما برسد، باید از چندین لایه امنیتی عبور کند. برخلاف NestJS که امنیت را بیشتر در لایه Metadata و Decorator میبیند، Spring امنیت را در لایه Infrastructure (شبکه و فیلترها) مدیریت میکند.
تعریف دقیق Authentication
Authentication به معنای احراز هویت است؛ یعنی اثبات اینکه "شما چه کسی هستید؟". در Spring Security، این مفهوم با اینترفیس Authentication شناخته میشود که اطلاعات کاربر (مانند Principal و Credentials) را در خود نگه میدارد. این اطلاعات پس از تایید توسط AuthenticationManager در SecurityContextHolder قرار میگیرد.
تعریف دقیق Guard و معادل آن در Spring Boot
در NestJS، مفهوم Guard برای تصمیمگیری درباره اجازه دسترسی به یک مسیر خاص استفاده میشود. در Spring Boot، ما اصطلاح رسمی Guard نداریم، اما معادل عملیاتی آن ترکیبی از SecurityFilterChain (برای امنیت مبتنی بر URL) و Method Security (با استفاده از @PreAuthorize) است.
دلیل اینکه Spring از اصطلاح Guard استفاده نمیکند، ماهیت AOP (برنامهنویسی جنبهگرا) آن است. در Spring دسترسیها معمولاً از طریق AccessDecisionManager یا AuthorizationManager کنترل میشوند که بسیار منعطفتر از یک Guard ساده هستند.
تعریف دقیق Middleware و معادل آن در Spring Boot
معادل دقیق Middleware در Spring Boot همان Filterها هستند. Filterها در لایه Servlet Container (مانند Tomcat) اجرا میشوند و قبل از ورود درخواست به DispatcherServlet عمل میکنند.
Filter: در سطحServletعمل میکند (قبل از رسیدن به سیستم MVC).Interceptor: در سطحSpring MVCعمل میکند (بعد از ورود به سیستم MVC و قبل از اجرایController).
نقش Spring Security در معماری امنیت
Spring Security یک کتابخانه جامع است که امنیت را در سطوح مختلف فراهم میکند:
- Transport Security: استفاده از
HTTPS. - Authentication: شناسایی هویت کاربر.
- Authorization: کنترل دسترسی به منابع.
- Protection: جلوگیری از حملاتی نظیر
CSRF،Session FixationوClickjacking.
تشریح کامل SecurityFilterChain
SecurityFilterChain قلب تپنده Spring Security است. این کلاس زنجیرهای از Filterها را مدیریت میکند که هر درخواست باید از آنها عبور کند.
@Configuration
@EnableWebSecurity
public class SecurityConfig {
@Bean
public SecurityFilterChain filterChain(HttpSecurity http) throws Exception {
return http
.csrf(csrf -> csrf.disable())
.authorizeHttpRequests(auth -> auth
.requestMatchers("/api/auth/**").permitAll()
.requestMatchers("/api/admin/**").hasRole("ADMIN")
.anyRequest().authenticated()
)
.addFilterBefore(jwtAuthFilter, UsernamePasswordAuthenticationFilter.class)
.build();
}
}
پیادهسازی JWT Authentication با OncePerRequestFilter
برای پیادهسازی احراز هویت بدون وضعیت (Stateless)، از OncePerRequestFilter استفاده میکنیم تا اطمینان حاصل شود فیلتر در هر درخواست فقط یک بار اجرا میشود.
@Component
public class JwtAuthenticationFilter extends OncePerRequestFilter {
@Override
protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain filterChain)
throws ServletException, IOException {
String token = request.getHeader("Authorization");
if (token != null && jwtService.validateToken(token)) {
UsernamePasswordAuthenticationToken auth = new UsernamePasswordAuthenticationToken(user, null, authorities);
SecurityContextHolder.getContext().setAuthentication(auth);
}
filterChain.doFilter(request, response);
}
}
امنیت در سطح متد با @PreAuthorize
با استفاده از @EnableMethodSecurity میتوانید مستقیماً در سطح متد کنترل دسترسی داشته باشید:
@RestController
@RequestMapping("/api/admin")
public class AdminController {
@PreAuthorize("hasRole('ADMIN')")
@GetMapping("/dashboard")
public String getDashboard() {
return "Welcome Admin";
}
}
جدول مقایسهای: Spring Boot در برابر NestJS
مفهومدر NestJSدر Spring BootMiddlewareMiddlewareFilterAccess ControlGuardSecurityFilterChain / Method SecurityRequest InterceptionInterceptorHandlerInterceptorRole/PermissionReflector / SetMetadata@PreAuthorize / Expression-based Access
اشتباهات رایج
- اشتباه گرفتن Authentication با Authorization: احراز هویت یعنی کی هستی؟ دسترسی یعنی اجازه داری این کار رو بکنی؟ هرگز این دو را در یک لایه منطقی ترکیب نکنید.
- استفاده نادرست از permitAll: باز گذاشتن مسیرهای حساس به دلیل تنبلی در پیکربندی.
- مدیریت خطا در Filter: فیلترها خارج از
ControllerAdviceهستند؛ پس برای مدیریت استثناها در فیلتر، باید از یکAuthenticationEntryPointاختصاصی استفاده کنید.
بهترین شیوهها (Best Practices)
- همیشه از
Statelessبودن برایREST APIها استفاده کنید. - از
DelegatingFilterProxyبرای مدیریت فیلترها استفاده کنید. - رمز عبورها را همیشه با
BCryptPasswordEncoderهش کنید.
معماری پیشنهادی برای پروژه واقعی
- لایه Filter: فقط برای استخراج و اعتبارسنجی
JWT. - لایه SecurityFilterChain: برای تعیین دسترسیهای کلی URLها.
- لایه Method Security: برای جزئیات دسترسی (مثلاً دسترسی به یک رکورد خاص بر اساس
IDکاربر).
سوالاتی که شاید در ذهن شما شکل گرفته باشد
- آیا Interceptor برای امنیت مناسب است؟ خیر، چون در لایه MVC است و فیلترهای امنیتی قبل از آن اجرا میشوند.
- چرا SecurityContextHolder ایستا (Static) است؟ چون از
ThreadLocalاستفاده میکند تا در تمام طول اجرای یک درخواست در دسترس باشد. - تفاوت hasRole با hasAuthority چیست؟
hasRoleبه صورت خودکار پیشوندROLE_را اضافه میکند. - چگونه CSRF را غیرفعال کنم؟ برای APIهای
Statelessکه از JWT استفاده میکنند، غیرفعال کردن آن امن است. - آیا میتوان همزمان از Session و JWT استفاده کرد؟ بله، اما توصیه نمیشود؛ معماری خود را یکپارچه کنید.
- اگر فیلتر خطا بدهد چه میشود؟ فیلترها استثنای
Spring MVCرا پرتاب نمیکنند؛ باید ازExceptionHandlerFilterدستی استفاده کنید. - کدام لایه برای بررسی مالکیت منابع (Resource Ownership) بهتر است؟
Method Security(@PreAuthorize). - آیا
OncePerRequestFilterتضمین میکند که فقط یک بار اجرا شود؟ بله، اگر به درستی درBeanهایSpringتعریف شود.
جمعبندی اجرایی
برای پیادهسازی یک معماری امنیتی استاندارد در Spring Boot:
- احراز هویت: از یک
OncePerRequestFilterاختصاصی برای استخراجJWTاستفاده کنید. - دسترسیهای کلی: از
SecurityFilterChainبرای تعریفpermitAllوauthenticatedاستفاده کنید. - دسترسیهای دقیق: از
@EnableMethodSecurityو@PreAuthorizeبرای محدود کردن دسترسی به متدهای سرویس یا کنترلر استفاده کنید.
با رعایت این سه لایه، شما یک معماری امنیتی منعطف، تستشدنی و مقیاسپذیر خواهید داشت.