[Spring Boot] 트러블 슈팅 - CORS Preflight request interceptor 검증 로직 예외 처리

박진우·2023년 9월 7일
1
post-thumbnail

시작하기에 앞서...

일정 관리 및 공유 오피스 대여 서비스인 meeti 프로젝트를 진행하면서 배포 후 프런트 팀원과 테스트하는 도중, 한가지 문제를 만나게 되었습니다...!🧐

원인 분석 끝에 WebConfig에 정의해두었던 HTTP Header Interceptor에서 발생하는 문제임을 발견하였습니다.

따라서, 문제를 해결하는 과정 속에서 알게된 점들을 기록해두고자 글을 작성하게 되었습니다.


문제 상황

로그인 API를 실행하면 토큰이 발급됩니다.

  • 로그인 후 JWT 토큰 발급 (Postman 실행 화면)

발급된 accessToken을 Header에 Authorization의 Value로 적용한다면 로그인 처리가 완료됩니다.

따라서 accessToken을 붙여 넣었는데...😳

  • 서버에서 발견된 에러 로그

분명 헤더에 값이 잘 들어가있음을 확인하였지만, 계속해서 Authorization Header가 비어있다는 에러가 발생하였습니다.

따라서 에러를 해결해보고자 문제를 탐색해보았습니다.


문제 원인

바로 이 친구가 원인이었습니다! (잡았다 요놈)

  • Preflight Request 확인


    요청을 한 번만 보내더라도 헤더가 비어있는 상태의 요청을 제일 먼저 보내는 것이었습니다...!

이러한 현상은 왜 일어나는 것일까요?


문제 파악

Preflight requestCORS(자원 공유 정책) 에서 실제 요청을 보내기 전에 OPTIONS HTTP Method로 요청이 허용되는지 확인하기 위해 Preflight request를 우선적으로 전달한다고 합니다.

바로 사전 점검의 의미가 강하다는 것이죠.

하지만, Preflight request가 아무리 사전 점검을 위한 요청이라 하더라도 request임은 변함없기 때문에 WebConfigInterceptor에 걸리게 되었고, 결과적으로 Authorization Header가 빈값으로 확인될 수 밖에 없던 것이었습니다. 😫

  • accessToken 검증을 위한 Interceptor

원인 파악을 하고 해당 API를 Interceptor에 추가하여 실행하였더니 동작엔 이상없었습니다.

하지만 모든 요청에 대해 Interceptor과정을 거치치 않는다면 그것 또한 문제임이 분명했습니다.

따라서, 기존 요청만을 받기 위해 Preflight request를 걸러내는 방법을 사용하고자 하였습니다.


문제 해결

  • 기존의 AuthenticationInterceptor 작성 코드 : Preflight request를 걸러내지 못함

위와 같은 상태에서 Preflight request를 걸러내는 조건을 추가하였습니다.

Preflight request를 걸러내기 위해선 HTTP Method에 OPTIONS가 있는 것을 확인하여 해당 메소드가 있다면 그 요청을 자연스럽게 넘기는 과정을 추가하면 될 것이라 판단하였습니다.

@Component
@RequiredArgsConstructor
public class AuthenticationInterceptor implements HandlerInterceptor {

    private final TokenManager tokenManager;

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

        // preflight 요청 넘기기
        if (request.getMethod().equals("OPTIONS")) {
            return true;
        }

        // 1. Authorization Header 검증
        String authorization = request.getHeader("Authorization");
        AuthorizationHeaderUtils.validateAuthorization(authorization);

        // 2. 토큰 검증
        String accessToken = authorization.split(" ")[1];
        tokenManager.validateToken(accessToken);

        // 3. 토큰 타입
        Claims tokenClaims = tokenManager.getTokenClaims(accessToken);
        String tokenType = tokenClaims.getSubject();
        if (!TokenType.isAccessToken(tokenType))
            throw new AuthenticationException(ErrorCode.NOT_ACCESS_TOKEN_TYPE);

        return true;
    }
}

코드를 수정하고나니 요청이 정상적으로 처리됨을 확인할 수 있었습니다! 👏

  • 정상 작동 확인

글을 마치며...

처음엔 많이 당황했던 문제였지만, 프런트 팀원과의 수많은 고뇌(?) 끝에 발견 후 해결할 수 있었습니다.

저 혼자만의 프로젝트였다면 더 오래걸렸을지도 모릅니다... (오늘도 잘 맞는 팀원에 대한 소중함을 느끼게 됩니다...🥹)

당장은 급한 불을 끄듯 문제를 해결하였지만, CORS에 대한 깊은 학습이 필요하다고 느꼈습니다. (많은 분들이 직면하고 고생하시는 CORS...)

다음 포스팅은 CORS에 대한 학습 후에 상세히 기록물을 남겨두고자 합니다!

다음번에 만나요 🙋‍♂️

profile
꾸준히 한 발씩 발전해나가는 모습을 기록합니다.

0개의 댓글