💬 들어가기 전에
작년 6월 첫 번째 Spring
프로젝트를 진행하며 처음 CORS
오류를 마주했고, WebMvcConfigurer
설정을 통해 해결했다.
이후 프로젝트를 진행하며 Jwt
토큰을 도입하게 되어 토큰 인증용 Interceptor
를 만들게 되었다.
그런데 이때 preflight
요청이 제대로 처리가 되지 않아 CORS
오류가 또 터져버렸다.
preflight
요청은 WebMvcConfigurer
가 처리해준다고 생각했는데,
Interceptor
를 만들었다고 preflight
요청이 처리가 되지 않는 것이 이상했다.
당시에는 엉뚱한 곳에서 원인을 찾고 있었던 것 같고, 엉뚱한 내용을 블로그에 작성했다.
(지금 보니까 내용이 아주 엉망이다.)
[Spring] filter 기능 구현 후 CORS 에러가 발생하는 이슈
그때 제대로 이해하지 않고 넘어갔다가
최근 회사에서 오랜만에 CORS
설정을 하다가 똑같은 실수를 반복했고 또 비슷한 내용으로 엉망인 블로그 글을 써버렸기에
WebMvcConfiguration
을 통해 CORS
설정을 하면 Spring
이 어떻게 처리를 하고 있는지 파헤쳐 보기로 했다.
(또 다시 써버린 엉망인 블로그 글은 발행했다가 바로 비공개 처리했는데, 그것 또한 기록이기에 이 글을 쓰면서 다시 공개로 바꾸었다.)
🔍 WebMvcConfigurer의 동작 원리
위에서 언급한
"WebMvcConfigurer
가 preflight
요청을 처리해준다고 생각했는데, 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
의 처리가 완료되면 CorsInterceptor
의 prehandle
메서드가 완료되고 다음 Interceptor
로직으로 넘어가게 된다.
✔️ 정리
WebMvcConfigurer
를 통해 CORS
설정을 했고, 커스텀 Interceptor
가 있다고 했을 때 동작 과정은 다음과 같다.
WebMvcConfigurer
를 통해 설정한CORS
설정을CorsConfiguration
으로 만들어CorsInterceptor
로 넘긴다.- 요청이 들어오면
HandlerExcutionChain
이 등록된 인터셉터를 찾아 하나씩 실행한다.
- 이때 첫번째로 실행되는
Interceptor
는AbstractHandlerMapping
내부에 있는CorsInterceptor
이며,preflight
에 대한 요청을 적절히 처리하고 다음Interceptor
로 넘긴다.
(커스텀Interceptor
의order
를 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);
}
}
하지만 스프링의 CorsInterceptor
는 Preflight
에 대한 처리를 하고 다음 Interceptor
로 처리를 넘기기 때문에 preflight
요청에 대한 처리를 다음 Interceptor
에서 해줘야 하는데...
나는 해당 요청에 대한 처리를 WebMvcConfigurer
가 해주고 있다고 생각했기 때문에 필요한 처리를 해주지 않았고
원인을 엉뚱한 곳에서 찾고 있었다.
(확인하지도 않고 왜 생각의 회로가 무의식적으로 그렇게 흘러갔을까...)
👋 마치며
일단 한달 넘게 마음의 짐으로 가지고 있던 문제의 원인을 파악하게 되어 다행이다.
그리구... 지금까지 벨로그에 작성했던 글을 포함해 약 50개 정도를 글을 발행했는데
처음으로 다른 블로그 참고없이, 다른 사람의 도움없이 원리를 파헤쳐본 거라 조금 뿌듯하긴 한데...
WebMvcConfigurer
가 CORS
대한 처리를 Interceptor
를 통해 하고 있다는 것을 1년이 지나 처음 알게 되었다.
반성한다.
암튼 위 과정을 글로만 보면 잘 이해가 가지 않을 수 있다.
혹시나 동작 과정을 직접 살펴보고 싶다면 HandlerExcutionChain
클래스의 applyPreHandle
에 디버깅 포인트를 찍고 확인해보는 것을 추천해 본다.