امنیت در Spring Boot

مقدمه

امنیت یکی از حیاتی‌ترین ستون‌های هر سیستم نرم‌افزاری است. در دنیای 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 یک کتابخانه جامع است که امنیت را در سطوح مختلف فراهم می‌کند:

  1. Transport Security: استفاده از HTTPS.
  2. Authentication: شناسایی هویت کاربر.
  3. Authorization: کنترل دسترسی به منابع.
  4. 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


اشتباهات رایج

  1. اشتباه گرفتن Authentication با Authorization: احراز هویت یعنی کی هستی؟ دسترسی یعنی اجازه داری این کار رو بکنی؟ هرگز این دو را در یک لایه منطقی ترکیب نکنید.
  2. استفاده نادرست از permitAll: باز گذاشتن مسیرهای حساس به دلیل تنبلی در پیکربندی.
  3. مدیریت خطا در Filter: فیلترها خارج از ControllerAdvice هستند؛ پس برای مدیریت استثناها در فیلتر، باید از یک AuthenticationEntryPoint اختصاصی استفاده کنید.

بهترین شیوه‌ها (Best Practices)

  • همیشه از Stateless بودن برای REST APIها استفاده کنید.
  • از DelegatingFilterProxy برای مدیریت فیلترها استفاده کنید.
  • رمز عبورها را همیشه با BCryptPasswordEncoder هش کنید.

معماری پیشنهادی برای پروژه واقعی

  1. لایه Filter: فقط برای استخراج و اعتبارسنجی JWT.
  2. لایه SecurityFilterChain: برای تعیین دسترسی‌های کلی URLها.
  3. لایه Method Security: برای جزئیات دسترسی (مثلاً دسترسی به یک رکورد خاص بر اساس ID کاربر).

سوالاتی که شاید در ذهن شما شکل گرفته باشد

  1. آیا Interceptor برای امنیت مناسب است؟ خیر، چون در لایه MVC است و فیلترهای امنیتی قبل از آن اجرا می‌شوند.
  2. چرا SecurityContextHolder ایستا (Static) است؟ چون از ThreadLocal استفاده می‌کند تا در تمام طول اجرای یک درخواست در دسترس باشد.
  3. تفاوت hasRole با hasAuthority چیست؟ hasRole به صورت خودکار پیشوند ROLE_ را اضافه می‌کند.
  4. چگونه CSRF را غیرفعال کنم؟ برای APIهای Stateless که از JWT استفاده می‌کنند، غیرفعال کردن آن امن است.
  5. آیا می‌توان همزمان از Session و JWT استفاده کرد؟ بله، اما توصیه نمی‌شود؛ معماری خود را یکپارچه کنید.
  6. اگر فیلتر خطا بدهد چه می‌شود؟ فیلترها استثنای Spring MVC را پرتاب نمی‌کنند؛ باید از ExceptionHandlerFilter دستی استفاده کنید.
  7. کدام لایه برای بررسی مالکیت منابع (Resource Ownership) بهتر است؟ Method Security (@PreAuthorize).
  8. آیا OncePerRequestFilter تضمین می‌کند که فقط یک بار اجرا شود؟ بله، اگر به درستی در Beanهای Spring تعریف شود.

جمع‌بندی اجرایی

برای پیاده‌سازی یک معماری امنیتی استاندارد در Spring Boot:

  1. احراز هویت: از یک OncePerRequestFilter اختصاصی برای استخراج JWT استفاده کنید.
  2. دسترسی‌های کلی: از SecurityFilterChain برای تعریف permitAll و authenticated استفاده کنید.
  3. دسترسی‌های دقیق: از @EnableMethodSecurity و @PreAuthorize برای محدود کردن دسترسی به متدهای سرویس یا کنترلر استفاده کنید.

با رعایت این سه لایه، شما یک معماری امنیتی منعطف، تست‌شدنی و مقیاس‌پذیر خواهید داشت.