
최근 진행하던 Spring Boot WebFlux 기반 프로젝트에서 보안 컴포넌트 호환성 문제와 실시간 API 에러 추적의 어려움이라는 두 가지 주요 문제를 해결했습니다. 이번 포스팅에서는 WebFlux 환경에 맞게 보안 설정을 전환하고, 디버깅을 위한 로깅 및 에러 핸들링을 어떻게 강화했는지 상세하게 다룹니다.
WebFlux의 Non-blocking 아키텍처를 도입하여 성능을 향상시키는 과정에서, 기존의 서블릿 기반(Servlet-based) 컴포넌트들이 발목을 잡았습니다. 또한, 핵심 기능인 실시간 주식 데이터 API 엔드포인트(/api/stocks/realtime/{symbol})에서 발생하는 500 Internal Server Error의 원인을 명확히 파악할 수 없어 빠른 해결이 필요했습니다.
| 구분 | 문제 컴포넌트 | 상세 내용 | 해결 필요성 |
|---|---|---|---|
| 보안 호환성 | JwtAuthenticationEntryPoint | HttpServletRequest, HttpServletResponse 등 서블릿 API 사용 | WebFlux 환경으로의 전면 전환 |
| 보안 호환성 | JwtAuthenticationFilter | OncePerRequestFilter 상속으로 WebFlux와 호환 불가 | WebFilter 인터페이스로 대체 |
| 보안 호환성 | SecurityConfig | 서블릿용 CorsConfigurationSource 사용으로 타입 불일치 | Reactive 타입으로 변경 |
| 에러 추적 | /api/stocks/realtime/{symbol} | 500 오류 발생 시 로깅 정보 부족으로 원인 파악 불가 | 단계별 상세 로깅 및 예외 체인 추적 강화 |
WebFlux 환경에서는 서블릿 기반의 필터나 엔트리 포인트를 사용할 수 없습니다. 모든 컴포넌트를 리액티브 스트림(Reactive Stream) 패턴에 맞춰 Mono<T> 또는 Flux<T>를 사용하는 방식으로 변경했습니다.
JwtAuthenticationEntryPoint 변환 (AuthenticationEntryPoint → ServerAuthenticationEntryPoint)| Before (서블릿) | After (WebFlux - Reactive) |
|---|---|
AuthenticationEntryPoint 구현 | ServerAuthenticationEntryPoint 구현 |
파라미터: HttpServletRequest, HttpServletResponse | 파라미터: ServerWebExchange, AuthenticationException |
반환 타입: void (응답 직접 처리) | 반환 타입: Mono<Void> (비동기 처리) |
이제 ServerWebExchange를 통해 ServerHttpResponse에 접근하고, 응답을 비동기적으로(Non-blocking) 처리하여 Mono<Void>를 반환합니다.
JwtAuthenticationFilter 변환 (OncePerRequestFilter → WebFilter)OncePerRequestFilter 대신 WebFlux에서 사용하는 WebFilter 인터페이스를 구현했습니다.
WebFilter 사용: HTTP 요청 처리를 리액티브하게 가로채 처리합니다.ReactiveSecurityContextHolder 도입: 서블릿의 SecurityContextHolder 대신, Mono를 통해 비동기적으로 보안 컨텍스트를 저장하고 관리합니다. 모든 작업이 Mono.just()와 .flatMap() 등의 체인으로 구성됩니다.SecurityConfig 수정 및 설정 추가CorsConfigurationSource를 org.springframework.web.cors.reactive.CorsConfigurationSource로 변경했습니다..exceptionHandling() 설정을 추가하여 JwtAuthenticationEntryPoint를 인증 엔트리 포인트로 명확하게 등록했습니다.// SecurityConfig.kt 일부
.exceptionHandling { handling ->
handling.authenticationEntryPoint(jwtAuthenticationEntryPoint)
}
// ...
// CorsConfigurationSource도 Reactive 타입 사용
가장 큰 문제였던 500 에러의 원인 파악을 위해 Python API 클라이언트와 핸들러 레이어의 로깅 수준을 극단적으로 끌어올리고 타입 안전성을 확보했습니다.
외부 API와의 통신을 담당하는 PythonApiClient에 에러 디버깅을 위한 3단계 로깅 전략을 적용했습니다.
INFO 레벨로 로깅합니다.null 처리 문제가 발생했는지 정확히 추적합니다.특히, 파싱 에러의 주범이었던
volume필드에 대해null안전성 체크 및 타입 변환 안정화 로직을 추가하여 예외 발생 가능성을 줄였습니다.
컨트롤러에서 예외를 최종 처리하는 StockHandler에서는 발생한 예외에 대해 타입별 상세 로깅을 추가했습니다.
application.yml)상세 로깅이 누락되지 않도록 주요 컴포넌트의 로깅 레벨을 조정했습니다.
logging:
level:
# API 클라이언트 및 핸들러 로깅 레벨을 INFO로 상향 조정
com.sleekydz86.backend.infrastructure.client: INFO
com.sleekydz86.backend.global.handler: INFO
서버 측의 에러 핸들링 개선과 더불어, 프론트엔드 dashboard.js에서도 서버 에러 응답을 더욱 상세하게 처리하도록 개선하여 사용자 경험(UX)을 향상시켰습니다.
| Before | After |
|---|---|
| "데이터를 불러올 수 없습니다."라는 단일 메시지 출력 | 서버 응답 상태(Status) 및 상세 데이터를 콘솔에 출력 |
사용자에게 상세 에러 메시지 표시 (데이터를 불러올 수 없습니다: [서버 에러 메시지]) | 네트워크 오류와 서버 오류를 구분하여 처리 |
이를 통해 개발자는 콘솔에서 즉시 서버 상태를 파악할 수 있고, 사용자에게도 보다 명확한 오류 안내가 가능해졌습니다.
이번 마이그레이션을 통해 WebFlux 기반 개발 시 반드시 숙지해야 할 보안 패턴을 다시 한번 확인할 수 있었습니다.
Mono와 Flux를 사용하여 Non-blocking 방식으로 구성해야 합니다.WebFilter: 요청을 가로채 처리하는 로직(예: JWT 필터)은 WebFilter 구현체를 통해 작성하며, 서블릿의 필터 순서와 마찬가지로 실행 순서를 잘 관리해야 합니다.ThreadLocal을 사용하는 서블릿 방식 대신, ReactiveSecurityContextHolder를 통해 관리되어야 합니다.| 상태 | 항목 | 확인 내용 |
|---|---|---|
| O | Spring Boot 서버 시작 | 서버가 WebFlux 환경에서 정상적으로 구동됨 |
| O | WebFlux 보안 컴포넌트 | 컴파일 성공 및 WebFilter/ServerAuthenticationEntryPoint 정상 동작 확인 |
| X | JWT 인증 필터 | 토큰 유효성 및 인증 로직 정상 동작 확인 |
| X | 실시간 API 엔드포인트 | /api/stocks/realtime/AAPL 정상 응답 확인 |
| X | 에러 로깅 | 에러 발생 시 강화된 상세 로그 정상 출력 확인 |
마이그레이션 및 에러 핸들링 개선 작업을 완료했으며, 현재 최종 테스트 단계에 있습니다.
이 경험이 WebFlux 도입을 고려하는 다른 개발자분들에게 도움이 되길 바랍니다!