Backend/spring

[Spring Security] Web vs WebFlux Spring Security 설정

dddzr 2025. 2. 23. 18:17

📌 Spring Security 설정 방법 비교

Spring Security 설정 방식은 Spring MVC(Web)Spring WebFlux에서 차이가 있다!! 🚀
아래에서 클래스 차이, 필터 적용 방식, UserDetailsService 사용 방식을 비교해보자. 😊

 

🔥 Spring MVC vs WebFlux 차이 요약

  Spring MVC (Web) Spring WebFlux
Security 설정 클래스 @EnableWebSecurity @EnableWebFluxSecurity
FilterChain 타입 SecurityFilterChain SecurityWebFilterChain
요청 인증 설정 authorizeHttpRequests() authorizeExchange()
필터 추가 방식 addFilterBefore(jwtFilter, UsernamePasswordAuthenticationFilter.class) addFilterBefore(jwtFilter, SecurityWebFiltersOrder.AUTHENTICATION) (WebFlux 전용 필터 체인 순서)
예외 처리 authenticationEntryPoint(new HttpStatusEntryPoint(HttpStatus.UNAUTHORIZED)) authenticationEntryPoint(new HttpStatusServerEntryPoint(HttpStatus.UNAUTHORIZED))
UserDetailsService UserDetailsService,
InMemoryUserDetailsManager
ReactiveUserDetailsService,
MapReactiveUserDetailsService

 

✅ 1. Security Config 작성 방법

1️⃣ Spring MVC (Web) - SecurityConfig

Spring MVC는 서블릿 기반 보안 필터 체인(SecurityFilterChain)을 사용.

@Configuration
@EnableWebSecurity
public class SecurityConfig {
    private final JwtAuthenticationFilter jwtAuthenticationFilter;
    private final UserDetailsService userDetailsService;
    
    public SecurityConfig(JwtAuthenticationFilter jwtAuthenticationFilter, UserDetailsService userDetailsService) {
        this.jwtAuthenticationFilter = jwtAuthenticationFilter;
        this.userDetailsService = userDetailsService;
    }

    @Bean
    public AuthenticationManager authenticationManager(AuthenticationConfiguration authenticationConfiguration) 
            throws Exception {
        return authenticationConfiguration.getAuthenticationManager();
    }

    @Bean
    public SecurityFilterChain securityFilterChain(HttpSecurity http) throws Exception {
        http
            .csrf(csrf -> csrf.disable()) // CSRF 비활성화
            .authorizeHttpRequests(auth -> auth
                .requestMatchers("/auth/**", "/h2-console/**").permitAll()
                .anyRequest().authenticated()
            )

            .sessionManagement(session -> session
                .sessionCreationPolicy(SessionCreationPolicy.STATELESS) // JWT 기반 인증 (세션 X)
            )

            .addFilterBefore(jwtAuthenticationFilter, UsernamePasswordAuthenticationFilter.class) // JWT 필터 적용
            
            .exceptionHandling(exception -> exception
                .authenticationEntryPoint(new HttpStatusEntryPoint(HttpStatus.UNAUTHORIZED)) // 401 처리
            );

        return http.build();
    }

    @Bean
    public PasswordEncoder passwordEncoder() {
        return new BCryptPasswordEncoder();
    }

    @Bean
    public UserDetailsService userDetailsService() {
        UserDetails user = User.withUsername("user")
                .password(passwordEncoder().encode("password"))
                .roles("USER")
                .build();
        return new InMemoryUserDetailsManager(user);
    }

}

 

2️⃣ Spring WebFlux - SecurityConfig

WebFlux는 비동기 방식의 SecurityWebFilterChain을 사용.

@Configuration
@EnableWebFluxSecurity
public class SecurityConfig {

    private final JwtAuthenticationFilter jwtAuthenticationFilter;

    public SecurityConfig(JwtAuthenticationFilter jwtAuthenticationFilter) {
        this.jwtAuthenticationFilter = jwtAuthenticationFilter;
    }

    @Bean
    public SecurityWebFilterChain securityWebFilterChain(ServerHttpSecurity http) {
        return http
            .csrf(ServerHttpSecurity.CsrfSpec::disable) // CSRF 비활성화

            .authorizeExchange(auth -> auth
                .pathMatchers("/auth/**").permitAll()
                .anyExchange().authenticated()
            )

            .addFilterBefore(jwtAuthenticationFilter, SecurityWebFiltersOrder.AUTHENTICATION) // JWT 필터 적용

            .exceptionHandling(exception -> exception
                .authenticationEntryPoint(new HttpStatusServerEntryPoint(HttpStatus.UNAUTHORIZED)) // 401 처리
            )
            .build();
    }

    @Bean
    public PasswordEncoder passwordEncoder() {
        return new BCryptPasswordEncoder();
    }

    @Bean
    public ReactiveUserDetailsService userDetailsService() {
        UserDetails user = User.withUsername("user")
                .password(passwordEncoder().encode("password"))
                .roles("USER")
                .build();
        return new MapReactiveUserDetailsService(user);
    }

}



✅ 2.  filter 작성 방식

Spring MVC와 WebFlux는 보안 필터를 적용하는 방식이 다르기 때문에 JWT 인증 필터를 작성하는 방식도 다르다!!

 

🔥  Spring MVC vs WebFlux 차이점

  Spring MVC (Web) Spring WebFlux
필터 인터페이스 OncePerRequestFilter (서블릿 기반) WebFilter (리액티브 기반)
*보안 컨텍스트 저장 방식 SecurityContextHolder.getContext().setAuthentication(auth) ReactiveSecurityContextHolder.withSecurityContext(Mono.just(securityContext))
필터 체인 호출 filterChain.doFilter(request, response) chain.filter(exchange)
요청 처리 방식 동기(Synchronous) 비동기(Reactive)

⭐보안 컨텍스트 저장 방식

👉 Spring MVC(스레드 기반) 환경 - SecurityContextHolder

  • SecurityContextHolder가 "현재 쓰레드"에 인증 정보를 저장!
  • 한 요청 안에서 같은 스레드라면 어디서든 SecurityContextHolder.getContext()로 인증 정보를 가져올 수 있다.

 

👉 Spring WebFlux(리액티브 기반) 환경 - ReactiveSecurityContextHolder

  • Spring WebFlux는 비동기 환경. 즉, 하나의 요청을 처리하는 동안 여러 스레드가 오가면서 실행될 수 있다!
  • ReactiveSecurityContextHolder가 현재 요청의 리액티브 컨텍스트(Reactor Context)에 저장, 요청이 끝날 때까지 인증 정보를 유지.

 

1️⃣ Spring MVC(Web) - OncePerRequestFilter 사용

Spring MVC에서는 서블릿 기반 FilterChain을 사용하기 때문에 ncePerRequestFilter를 확장해서 필터를 만든다.

  • OncePerRequestFilter를 확장하여 요청 당 한 번만 실행
  • SecurityContextHolder에 인증 정보 저장
  • FilterChain.doFilter()를 호출하여 다음 필터로 요청을 넘김
@Component
public class JwtAuthenticationFilter extends OncePerRequestFilter {
    private final JwtTokenProvider jwtTokenProvider;

    public JwtAuthenticationFilter(JwtTokenProvider jwtTokenProvider) {
        this.jwtTokenProvider = jwtTokenProvider;
    }

    @Override
    protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, 
                                    FilterChain filterChain) throws ServletException, IOException {
        String token = getTokenFromRequest(request);

        if (token != null && jwtTokenProvider.validateToken(token)) {
            Authentication auth = jwtTokenProvider.getAuthentication(token);
            SecurityContextHolder.getContext().setAuthentication(auth);
        }

        filterChain.doFilter(request, response); // 다음 필터로 전달
    }


    private String getTokenFromRequest(HttpServletRequest request) {
        String bearerToken = request.getHeader("Authorization");
        if (bearerToken != null && bearerToken.startsWith("Bearer ")) {
            return bearerToken.substring(7);
        }
        return null;
    }

}



2️⃣ Spring WebFlux - WebFilter 사용

Spring WebFlux에서는 비동기(reactive) 환경이므로 WebFilter를 구현해서 필터를 만든다.

  • WebFilter 인터페이스를 구현
  • 비동기 방식(Mono<Void>)으로 필터 처리
  • chain.filter(exchange)를 호출하여 다음 필터로 요청을 넘김
  • ReactiveSecurityContextHolder.withSecurityContext()를 사용해 보안 컨텍스트 저장
@Component
public class JwtAuthenticationFilter implements WebFilter {
    private final JwtTokenProvider jwtTokenProvider;

    public JwtAuthenticationFilter(JwtTokenProvider jwtTokenProvider) {
        this.jwtTokenProvider = jwtTokenProvider;
    }

    @Override
    public Mono<Void> filter(ServerWebExchange exchange, WebFilterChain chain) {
        String token = getTokenFromRequest(exchange);

        if (token != null && jwtTokenProvider.validateToken(token)) {
            Authentication auth = jwtTokenProvider.getAuthentication(token);
            SecurityContext securityContext = new SecurityContextImpl(auth);           

            return chain.filter(exchange)
                        .contextWrite(ReactiveSecurityContextHolder.withSecurityContext(Mono.just(securityContext)));
        }
        return chain.filter(exchange); // 다음 필터로 전달
    }

    private String getTokenFromRequest(ServerWebExchange exchange) {
        String bearerToken = exchange.getRequest().getHeaders().getFirst(HttpHeaders.AUTHORIZATION);
        if (bearerToken != null && bearerToken.startsWith("Bearer ")) {
            return bearerToken.substring(7);
        }
        return null;
    }
}