📌 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;
}
}
'Backend > spring' 카테고리의 다른 글
[Spring Security] CSRF & CORS 개념 및 설정 방법 (0) | 2025.02.23 |
---|---|
[error] conversionServicePostProcessor Bean 중복 오류 (0) | 2025.02.23 |
web vs webflux (0) | 2025.02.23 |
[Spring Security] Spring Security 임시 계정 (0) | 2025.02.23 |
[Spring Security] Spring Security란? (0) | 2025.02.16 |