Spring Boot의 JWT 검증 흐름과 프론트 인증 문제 해결
공인전자문서센터 프로젝트를 진행하면서, 프론트엔드에서 인증과 관련된 여러 문제가 동시에 나타났어요.
처음에는 각각의 문제가 별개처럼 보였지만, 분석해 보니 모두 하나의 인증 흐름 문제로 연결되어 있었어요.
1. 문제 시작 — 잦은 401 Unauthorized 에러
일부 API에서 401 Unauthorized 에러가 반복적으로 발생했어요.
토큰이 만료된 것도 아닌데 갑자기 인증이 풀리는 문제도 있었고,
만료된 경우에도 자동 갱신이 제대로 이뤄지지 않았어요.
2. 문제 심화 — 토큰 리프레시가 중간에 멈추는 현상
토큰이 실제로 만료되었을 때는
Axios 인터셉터가 리프레시 요청을 보내긴 했지만,
갱신된 토큰으로 실패한 요청을 재시도하지 못하고 큐에 갇히는 문제가 있었어요.
즉, “토큰 갱신 → 대기 중인 요청 재전송” 이 정상적으로 이어지지 않았어요.
3. 가장 큰 문제 — Blob 응답에서 인증 오류가 완전히 무시됨
특히 심각했던 문제는 엑셀 다운로드처럼 responseType이 blob인 API였어요.
Blob 응답은 Axios가 response.data를 JSON이 아니라 이진 데이터로 다루기 때문에
서버에서 401과 errorCode를 내려줘도 프론트에서는 이를 해석할 수 없었어요.
error.response.data.errorCode가 존재하지 않음- 인터셉터 분기 자체가 작동하지 않음
- 그래서 엑셀 다운로드 화면이 멈춰버리는 현상이 발생했어요.
이 문제는 사용자가 행동을 멈춰야 하는 상황을 만들기 때문에
가장 먼저 해결해야 했어요.
4. 추가 문제 — 로그아웃 후 서버 내부 오류 발생
로그아웃을 하면 프론트에서 Authorization 헤더를 완전히 제거했는데,
이 상태로 API를 호출하면 서버에서 헤더 파싱 과정에서 Null 처리 문제를 일으켜
“서버 내부 오류가 발생했습니다.”
라는 alert이 뜨게 되었어요.
원인 분석 — 백엔드의 JWT 검증 흐름과 분기 조건을 파악했어요
문제 해결을 위해 먼저 서버에서 어떤 errorCode를 반환하는지 확인했어요.
Spring Boot의 JwtFilter를 분석해보니, 서버는 이미 아래와 같이 상태를 명확히 나누고 있었어요.
| 서버 조건 | 반환 errorCode | 의미 |
|---|---|---|
| Authorization 헤더 없음/형식 오류 | UNAUTHORIZED | 로그인 필요 |
| 토큰 만료 | TOKEN_EXPIRED | 리프레시 필요 |
| 변조/서명 오류/파싱 오류 등 | AUTHENTICATION_FAILED | 잘못된 JWT |
| 접근 제한된 사용자 | ACCESS_RESTRICTED | 강제 차단 |
즉, 백엔드는 이미 정확한 규약을 제공하고 있었지만, 프론트가 이 신호를 온전히 활용하고 있지 않았던 것이 문제의 핵심이었어요.
문제 해결 — 프론트와 백엔드의 규약을 하나로 통합했어요
1) Axios 인터셉터를 모든 인증 오류를 처리할 수 있도록 확장했어요
기존 인터셉터는 TOKEN_EXPIRED만 처리했기 때문에
다른 인증 실패(errorCode)가 오면 아무 반응도 못했어요.
그래서 아래 코드들을 모두 리프레시 대상으로 통합했어요.
shellTOKEN_EXPIRED AUTHENTICATION_FAILED INVALID_JWT (프론트에서 맵핑) UNAUTHORIZED
이제 만료·위조·형식 오류 등 모든 인증 실패 상황을 자동 회복할 수 있게 되었어요.
2) Blob 응답은 프론트에서 직접 errorCode를 보정했어요
Blob은 서버가 내려주는 JSON을 해석할 수 없기 때문에
인증 오류를 감지할 수 없다는 구조적 문제가 있었어요.
그래서 아래와 같이 401 + Blob의 조합이면
프론트가 새로운 errorCode를 부여하도록 했어요.
shellFILE_AUTH_FAILED
이 값을 기존의 인증 오류 흐름에 태워
Blob 응답도 일반 API와 완전히 동일한 방식으로 처리되도록 만들었어요.
3) 로그아웃 시 Authorization 헤더를 “완전 삭제”가 아니라 “빈 문자열”로 처리했어요
서버가 Null 헤더를 받으면 내부 오류가 발생할 수 있기 때문에,
Bearer 형태로 빈 값을 전달하도록 수정했어요.
이렇게 하니 서버는 이를
“잘못된 토큰” → 401 UNAUTHORIZED
로 정상 처리하게 되었어요.
서버 내부 오류 alert도 완전히 사라졌어요.
결과 — 인증 흐름 전반이 안정적으로 일관되게 동작하게 되었어요
- JWT 만료, 위조, 포맷 오류 등 모든 인증 실패 상황에서 자동 리프레시가 성공적으로 동작해요.
- Blob 응답(파일 다운로드)에서도 인증 로직이 정상적으로 작동해요.
- 로그아웃 후 서버 내부 오류가 더 이상 발생하지 않아요.
- 인증 관련 UX가 전체적으로 안정되고, 사용자에게 오류 화면이 노출되는 일이 크게 줄어들었어요.
- 프론트와 백엔드가 명확한 규약에 기반해 같은 기준으로 인증 상태를 해석하게 되었어요.