문제 상황
JWT를 이용한 로그인 기능을 구현하고 나서 프런트 쪽에서 CORS 에러가 발생하고 서버에서는 JwtInterceptor에서 오류가 발생했다고 나오는 상황이었다.
구현한 JwtAuthenticationInterceptor 코드
@Slf4j
@RequiredArgsConstructor
public class JwtAuthenticationInterceptor implements HandlerInterceptor {
private final JwtProvider jwtProvider;
@Override
public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
String accessToken = getToken(request);
if (accessToken == null) { //헤더에 토큰이 없는 경우
throw new BusinessException(TOKEN_REQUIRED);
}
if (StringUtils.hasText(accessToken)) { //토큰이 있는 경우
return jwtProvider.validationToken(accessToken); //토큰 검증(마감기한, signature, jwt 형식인지)
}
return true;
}
private String getToken(HttpServletRequest request) {
String token = request.getHeader(ACCESS_HEADER_STRING);
if (StringUtils.hasText(token) && token.startsWith(ACCESS_PREFIX_STRING)) {
return token.substring(ACCESS_PREFIX_STRING.length());
}
return null;
}
}
코드에서 오류를 해보니 header에 토큰이 담겨 있지 않아서 오류가 발생하고 있었다!
하지만 프런트 측에 확인해 보니 헤더에 토큰을 정상적으로 담아서 보내고 있는 상황이었다.
원인은 preflight 요청에 의해서 발생한 문제였다.
Preflight Request란?
prefight Request를 알아보기 전에 CORS에 대해 먼저 알고 넘어가자
CORS란?
- Cross-Origin Resource sharing은 말 그래도 Origin 이 다른 경우 리소스를 공유할 수 있게 해주는 정책이다.
여기서 말하는 Origin은 URL r구조에서 Protocol + Host + Port를 합친것을 말한다.
- 브라우저는 기본적으로 SOP(Same-Origin Policy) 정책을 따른다고 한다.
CORS HTTP(s) 요청에는 두 가지 요청 방식이 있다.
1. Simple Request 요청 -> 서버에게 바로 요청을 보내는 방법
- 서버에 API를 요청하고, 서버는 Access-Control-Allow-Origin 헤더를 포함한 응답을 브라우저에 보낸다.
- 브라우저는 Access-Control-Allow-Origin 헤더를 확인해서 CORS 동작을 수행할지 판단
Simple Request 조건
- GET, HEAD, POST를 사용해야 한다.
- Accept, Accept-Language, Content-Language, Content-Type, DPR, Downlink, Save-Data, Viewport-Width, Width 외의 헤더를 사용한 안된다.
- Authorization 헤더를 포함할 수 없고, application/json 사용할 수 없기 때문에 지키기 어렵다.
- 나 또한 이 경우 때문에 Preflight Request에 의한 Cors 에러가 난 것 같다.
- Contetn-Tyoe 헤더는 application/x-www-form-urlencoded, multipart/form-data, text/palin 중 하나를 사용해야 한다.
2. Preflight Request -> 서버에 예비 요청을 보내서 안전한지 판단한 후 요청을 보내는 방법
- 실제 리소스를 요청하기 전에 OPTIONS라는 메서드를 통해 실제 요청을 전송할지 판단한다.
- OPTIONS 메서드로 서버에 예비 요청을 보내고, 서버는 이 예비 요청에 대한 응답으로 Access-Control-Allow-Origin 헤더를 포함한 응답을 브라우저에 보낸다. 브라우저는 단순 요청과 동일하게 Access-Control-Allow-Origin 헤더를 확인해서 CORS 동작을 수행할지 판단한다.
- Preflight Request가 생긴 이유는 브라우저가 CORS를 지원하지 않는 서버에 도달하면 요청에 대한 응답을 보내지 않아 실제 요청이 수행되지 않도록 보호하기 위해 만들어졌다.
Preflight Request 조건
- PUT, DELETE, CONNECT, OPTIONS, TRACE, PATCH 방식을 사용하는 경우
- 금지된 헤더 이름을 사용하는 경우
- 요청 페이로드 유형이 text/plain, multipart/form-data, application/x-www-form-urlencoded 이 아닌 경우
- 하나 이상의 이벤트 리스너가 XMLHttpRequestUpload에 등록되어 있는 경우
해결 방법
1. method가 OPTIONS로 요청이 오는 경우를 체크
if(request.getMethod().equals("OPTIONS")){
return true;
}
2. isPreFlightRequest() 메서드 사용
if(CorsUtils.isPreFlightRequest(request)) {
return true;
}
나는 두번째 방법을 사용하여 오류를 해결했다