💬 들어가기 전에

작년 6월 첫 번째 Spring 프로젝트를 진행하며 처음 CORS 오류를 마주했고, WebMvcConfigurer 설정을 통해 해결했다.

 

이후 프로젝트를 진행하며 Jwt 토큰을 도입하게 되어 토큰 인증용 Interceptor를 만들게 되었다.

그런데 이때 preflight 요청이 제대로 처리가 되지 않아 CORS 오류가 또 터져버렸다.

 

preflight 요청은 WebMvcConfigurer가 처리해준다고 생각했는데,

Interceptor를 만들었다고 preflight 요청이 처리가 되지 않는 것이 이상했다.

 

당시에는 엉뚱한 곳에서 원인을 찾고 있었던 것 같고, 엉뚱한 내용을 블로그에 작성했다.
(지금 보니까 내용이 아주 엉망이다.)

[Spring] filter 기능 구현 후 CORS 에러가 발생하는 이슈

 

그때 제대로 이해하지 않고 넘어갔다가

최근 회사에서 오랜만에 CORS 설정을 하다가 똑같은 실수를 반복했고 또 비슷한 내용으로 엉망인 블로그 글을 써버렸기에

WebMvcConfiguration을 통해 CORS 설정을 하면 Spring이 어떻게 처리를 하고 있는지 파헤쳐 보기로 했다.

 

(또 다시 써버린 엉망인 블로그 글은 발행했다가 바로 비공개 처리했는데, 그것 또한 기록이기에 이 글을 쓰면서 다시 공개로 바꾸었다.)

 

🔍 WebMvcConfigurer의 동작 원리

위에서 언급한

"WebMvcConfigurerpreflight 요청을 처리해준다고 생각했는데, Interceptor가 생긴 이후로 preflight 요청이 처리가 되지 않는 것"
에 대한 의문을 해결하려면 우선 WebMvcConfigurer의 동작 원리에 대해 이해해야 한다고 생각했다.

 

자 그럼 차근차근 확인해보자.

 


 

우리는 보통 다음과 같이 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");
    }
}

 


 

addCorsMapping()의 파라미터로 받은 CorsRegistry는 우리가 작성한 설정을 엮어 CorsConfiguration 객체를 만든다.

 

CorsRegistry.java

 

그렇다면 해당 CorsConfiguration은 어디서 사용될까?

 


 

 

AbstractHandlerMapping 클래스를 보면 CorsInterceptor라는 클래스가 있고 해당 인터셉터가 해당 설정을 읽고 관련 처리를 해준다.

 

CorsInterceptor.java

 


 

실제 CORS 처리는 CorsProcessor가 해주고 있는데 해당 클래스의 내부를 살펴보면 CORS 요청인지 확인하고, 이미 처리된 요청인지, preflight 요청인지 등을 판단한 후, 적절히 처리하는 역할을 하고 있다.

 

DefaultCorsProcessor.java

 

CorsProcessor의 처리가 완료되면 CorsInterceptorprehandle 메서드가 완료되고 다음 Interceptor 로직으로 넘어가게 된다.

 

✔️ 정리

WebMvcConfigurer를 통해 CORS 설정을 했고, 커스텀 Interceptor가 있다고 했을 때 동작 과정은 다음과 같다.

 

  • WebMvcConfigurer를 통해 설정한 CORS 설정을 CorsConfiguration으로 만들어 CorsInterceptor로 넘긴다.
  • 요청이 들어오면 HandlerExcutionChain이 등록된 인터셉터를 찾아 하나씩 실행한다.

  • 이때 첫번째로 실행되는 InterceptorAbstractHandlerMapping 내부에 있는 CorsInterceptor이며, preflight에 대한 요청을 적절히 처리하고 다음 Interceptor로 넘긴다.
    (커스텀 Interceptororder를 1로 설정해도 CorsInterceptor가 먼저 실행된다.)

 

🧐 그럼 왜 JwtInterceptor를 만들고 Preflight에 대한 처리 로직이 동작 하지 않았나?

내가 잘못 생각하고 있던 부분이 있었다.

 

CorsFilter를 다음과 같이 작성했던 적이 있었는데 무의식적으로 WebMvcConfigurer가 당연히 filter와 같이 preflight 요청을 받으면 뒤로 넘기지 않고 앞으로 튕겨내고 있다고 생각했다.

 

CorsFilter.java

@Component
public class CorsFilter implements Filter {

    private static final List<String> ALLOWED_HEADERS = List.of(
            "content-type",
            "Authorization",
            // ... 생략
    );

    @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, String.join(", ", ALLOWED_HEADERS));

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

        chain.doFilter(servletRequest, servletResponse);
    }
}

 

하지만 스프링의 CorsInterceptorPreflight에 대한 처리를 하고 다음 Interceptor로 처리를 넘기기 때문에 preflight 요청에 대한 처리를 다음 Interceptor에서 해줘야 하는데...

 

나는 해당 요청에 대한 처리를 WebMvcConfigurer가 해주고 있다고 생각했기 때문에 필요한 처리를 해주지 않았고

원인을 엉뚱한 곳에서 찾고 있었다.

 

(확인하지도 않고 왜 생각의 회로가 무의식적으로 그렇게 흘러갔을까...)

 

👋 마치며

일단 한달 넘게 마음의 짐으로 가지고 있던 문제의 원인을 파악하게 되어 다행이다.

 

그리구... 지금까지 벨로그에 작성했던 글을 포함해 약 50개 정도를 글을 발행했는데
처음으로 다른 블로그 참고없이, 다른 사람의 도움없이 원리를 파헤쳐본 거라 조금 뿌듯하긴 한데...

 

WebMvcConfigurerCORS 대한 처리를 Interceptor를 통해 하고 있다는 것을 1년이 지나 처음 알게 되었다.

반성한다.

 

암튼 위 과정을 글로만 보면 잘 이해가 가지 않을 수 있다.
혹시나 동작 과정을 직접 살펴보고 싶다면 HandlerExcutionChain 클래스의 applyPreHandle에 디버깅 포인트를 찍고 확인해보는 것을 추천해 본다.