(24.09.08 추가)

작성 후 문제의 원인을 알게되어 별도의 글에 정리했다.

[SpringBoot] WebMvcConfiguration의 동작 원리

 


💬 프로젝트 상황

현재 개발중인 프로젝트의 환경은 다음과 같다.

  • SpringBoot3
  • JDK 21
  • Spring Security 사용하지 않음

그런데 서버 첫 배포 후, 분명 다음과 같이 WebMvcConfigurer를 통해 설정을 추가해주었는데도
프론트와 연동하는 과정 중에 CORS 에러가 발생했다.

 

설정 코드

  • CorsConfig.java
@Configuration
public class CorsConfig implements WebMvcConfigurer {

    @Override
    public void addCorsMappings(CorsRegistry registry) {
        registry.addMapping("/**")
                .allowedOrigins("*")
                .allowedMethods("OPTIONS", "GET", "POST", "PUT", "PATCH", "DELETE");
    }
}

 

그리고 컨트롤러 앞단에는 아무런 filter도 없고 Jwt 토큰을 검증하는 Interceptor 하나만 존재하는 상황이었다.

  • JwtInterceptor.java
@Component
@RequiredArgsConstructor
public class JwtInterceptor implements HandlerInterceptor {

    @Override
    public boolean preHandle(
            HttpServletRequest request, HttpServletResponse response, Object handler
    ) throws IOException {

        if (isRequestUriInWhiteList(request)) {
            return true;
        }

        // ... 토큰 검증 등 로직 생략

        return true;
    }
}

 

filter로 임시 해결

옛날에도 비슷한 상황에서 filter로 해결한 적이 있어
혹시나 해서 CorsConfig를 삭제하고 CorsFilter를 구현해서 적용했는데, 문제가 해결이 되었다.

 

  • CorsFilter.java
@Component
public class CorsFilter implements Filter {

    @Override
    public void doFilter(ServletRequest servletRequest, ServletResponse servletResponse, FilterChain chain)
            throws ServletException, IOException {
        HttpServletResponse response = (HttpServletResponse) servletResponse;

        response.setHeader(HttpHeaders.ACCESS_CONTROL_ALLOW_ORIGIN, "*");
        response.setHeader(HttpHeaders.ACCESS_CONTROL_ALLOW_METHODS, "GET, POST, PUT, PATCH, DELETE, OPTIONS");
        response.setHeader(HttpHeaders.ACCESS_CONTROL_MAX_AGE, "3600");
        response.setHeader(HttpHeaders.ACCESS_CONTROL_ALLOW_HEADERS, "*");

        if (CorsUtils.isPreFlightRequest((HttpServletRequest) servletRequest)) {
            return;
        }

        chain.doFilter(servletRequest, servletResponse);
    }
}

💡 참고:

  • filter 클래스에 @Component를 달아주면 FilterRegistrationBean을 통해 등록 하지 않아도 filter가 작동한다.
  • filterSpringContext 안에 있지 않지만 Spring 1.2부터 DelegatingFilterProxy의 등장으로 인해 스프링 빈으로 등록할 수 있게 되었다.

 

그런데 왜 filter는 되고 WebMvcConfigurer는 안되는 건지... 😡
지금까지 사이드 프로젝트를 여러 개 진행하면서 스프링부트 버전2, 3에 관계없이 어떨 때는 WebMvcConfigurer 설정이 먹히고
어땔 때는 안 먹힌다.

 

(당최 이유를 알 수가 없네...)

 

저번에는 찝찝해도 일단 해결되어서 넘어갔지만, 이번에는 이유를 알아 내야겠다.

 

🔥 문제 원인 찾기

몇주간 구글링을 해봤지만 원하는 결과가 나오지 않아서 스택오버플로우에서 방랑하다가 힌트를 얻었다.

servelet-context-path와 WebMvcConfigurer 설정과의 충돌?

🔗 링크:

 

해당 이미지의 글을 간단히 번역해 보면 application.yml 설정에 servelet-context-path/api를 추가했는데,
이게 WebMvcConfigurer 설정과 충돌이 일어날 수도 있다는 내용이었다.

 

흠... 테스트 해보니 이건 아닌 것 같았다.

(혹시 누군가에게 도움이 될 수 있으니 해당 내용은 남겨두려 한다.)

 

💣 찾았다 범인...! Preflight 요청

혼탁해진 머리와 마음을 가다듬고 다시 디버깅해보니...
Preflight 요청이 interceptor에서 JWT 토큰 검증 로직에 걸린 걸 발견했다...

 

아뿔사...

 

filter 코드 보고 interceptor 코드 다시 보니.
filter에서는 Preflight 요청을 처리해줬고, interceptor는 해당 코드가 없었다.

 

그러니까 안되지,, WebMvcConfigurer 문제가 아니었다.

 

  • Preflight 요청을 처리하는 코드
if (CorsUtils.isPreFlightRequest((HttpServletRequest) servletRequest)) {
    return;
}

💡 참고:

  • 이전에는 request.getMethod().equals("OPTIONS") 이런식으로 Preflight 요청을 처리하는 코드를 직접 작성했었다.
  • 그런데 위 메서드는 Spring에서 제공하는 것이다.
  • 해당 코드의 내부 로직을 보면 스프링이 Preflight 요청을 훨씬 정교하게 처리해주고 있기 때문에 가급적이면 스프링이 제공해주는 코드를 쓰자.

 

👋 정리

프론트가 당장 API 요청이 불가능해진 상황이라 마음이 급해져 이전에 해결했던 방법으로 휘뚜루 마뚜루 일단 적용해서 해결했는데
시간 내서 코드 롤백하고 디버깅 안했으면 원인을 모른 채로 지나갔을 것 같다. 이제라도 알아서 다행이다.

  • CORS 관련 설정을 할 때 preflight 요청 처리 꼭꼭 잊지 말자.
  • 옛날 코드 그냥 복붙하지 말고 다시 살펴보자.

로컬에서 에러가 나는건 괜찮은데, 개발 서버에서 에러가 나면 나 때문에 프론트 일정이 같이 밀리는 것 같아서 마음이 급해진다.