
Axios Interceptor로 인한 무한 루프 해결기
프로젝트에서 JWT 인증을 구현하던 중, 토큰 재발급 과정에서 예상치 못한 무한 루프 문제가 발생했다. 겉보기엔 단순한 실수처럼 보였지만, 해결 과정에서 인터셉터의 동작 원리와 비동기 통신의 구조를 깊이 이해할 수 있었던 경험을 공유한다.
Axios란?
Axios는 브라우저와 Node.js에서 사용할 수 있는 Promise 기반 HTTP 클라이언트다.

React에서 백엔드 API와 통신할 때 가장 많이 사용되는 라이브러리 중 하나다. fetch API보다 더 간편한 문법과 다양한 기능을 제공한다.
// 기본적인 Axios 사용 예시
import axios from 'axios';
const response = await axios.get('/api/users');
console.log(response.data);
우리 프로젝트에서는 Spring Boot 백엔드와 통신하기 위해 Axios를 사용했다.
Axios Interceptor란?
Interceptor는 요청이나 응답을 가로채서 처리할 수 있는 기능이다.

쉽게 말하면, 모든 API 요청이 서버로 가기 전, 그리고 서버의 응답이 우리 코드에 도달하기 전에 중간에서 가로채서 뭔가를 할 수 있다는 것이다.
Request Interceptor
요청을 보내기 전에 실행된다. 주로 헤더에 토큰을 추가하는 용도로 사용한다.
axios.interceptors.request.use(
(config) => {
const token = localStorage.getItem('accessToken');
if (token) {
config.headers.Authorization = `Bearer ${token}`;
}
return config;
}
);
Response Interceptor
응답을 받은 후에 실행된다. 에러 처리나 토큰 재발급에 사용한다.
axios.interceptors.response.use(
(response) => response,
async (error) => {
// 401 에러 시 토큰 재발급 시도
if (error.response?.status === 401) {
// 토큰 재발급 로직
}
return Promise.reject(error);
}
);
문제 상황
프로젝트에서 JWT 인증을 구현하면서 다음과 같은 흐름을 만들려고 했다:
- 사용자가 API 요청
- 액세스 토큰 만료로 401 에러 발생
- Response Interceptor가 에러를 감지
- 리프레시 토큰으로 새 액세스 토큰 발급
- 실패했던 요청 재시도

그런데 구현 후 실행 후 콘솔을 확인 해보니 수백 개의 동일한 요청이 찍히는 무한 루프가 발생했다.
// 문제가 있던 코드
axios.interceptors.response.use(
(response) => response,
async (error) => {
if (error.response?.status === 401) {
// 같은 axios 인스턴스로 재발급 요청
const newToken = await axios.post('/api/auth/refresh', {
refreshToken: localStorage.getItem('refreshToken')
});
// ...
}
}
);
처음엔 뭔가 코드를 잘못 짠 건가? 정도로만 생각했다.
원인 분석
디버깅을 하면서 문제의 근본 원인을 파악할 수 있었다.

순환 구조의 함정
- 사용자가 API 요청 → 401 에러 발생
- Response Interceptor가 에러를 감지
- 같은 axios 인스턴스로 토큰 재발급 요청
- 재발급 요청도 인터셉터를 거침
- 재발급 요청이 실패하면 다시 401 에러
- Response Interceptor가 또 감지
- 다시 재발급 요청... (무한 반복)
문제는 인터셉터가 설정된 axios 인스턴스로 재발급 요청을 보냈다는 것이었다. 토큰 재발급 요청 자체도 인터셉터를 거치기 때문에, 이 요청이 실패하면 다시 재발급을 시도하는 순환 구조가 만들어진 것이다.
이건 단순한 코드 실수가 아니라, 인터셉터의 동작 범위를 제대로 이해하지 못해서 발생한 구조적 문제였다.
해결 방법
해결책은 의외로 명확했다. 토큰 재발급 요청만 순수 axios를 사용하는 것이다.

핵심 아이디어
인터셉터가 설정된 httpClient 대신, 순수 axios로 토큰 재발급 요청을 보내면 인터셉터를 거치지 않는다.
// httpClient: 인터셉터가 설정된 인스턴스
const httpClient = axios.create({
baseURL: baseURL,
timeout: 10000,
withCredentials: true,
});
// 인터셉터 설정
httpClient.interceptors.response.use(
(response) => response,
async (error) => {
if (error.response?.status === 401 && !originalRequest._retry) {
try {
// 순수 axios로 재발급 요청 (인터셉터 없음!)
const { data } = await axios.post(
`${baseURL}/auth/refresh`,
{},
{ withCredentials: true }
);
const newAccessToken = data.accessToken;
storage.setToken(newAccessToken);
// 실패했던 요청 재시도
originalRequest.headers.Authorization = `Bearer ${newAccessToken}`;
return httpClient(originalRequest);
} catch (refreshError) {
// 재발급 실패 시 로그아웃
storage.removeToken();
window.location.href = '/';
return Promise.reject(refreshError);
}
}
return Promise.reject(error);
}
);
동시 요청 처리
여러 API가 동시에 401 에러를 받으면 어떻게 될까? 토큰 재발급이 여러 번 실행될 수 있다. 이를 방지하기 위해 갱신 대기열을 구현했다.
let isRefreshing = false; // 토큰 갱신 중인지 여부
let refreshSubscribers = []; // 대기 중인 요청들
const onRefreshed = (accessToken) => {
// 갱신 완료 후 대기 중인 요청들 재시도
refreshSubscribers.forEach((callback) => callback(accessToken));
refreshSubscribers = [];
};
const addRefreshSubscriber = (callback) => {
refreshSubscribers.push(callback);
};
// 401 에러 처리
if (error.response?.status === 401) {
if (isRefreshing) {
// 이미 갱신 중이면 대기열에 추가
return new Promise((resolve) => {
addRefreshSubscriber((token) => {
originalRequest.headers.Authorization = `Bearer ${token}`;
resolve(httpClient(originalRequest));
});
});
}
isRefreshing = true;
// 토큰 재발급 로직...
isRefreshing = false;
}
로그인 요청 예외 처리
로그인 실패 시에도 401이 발생하는데, 이 경우는 토큰 재발급을 시도하면 안 된다.
// 로그인 요청은 토큰 갱신 시도하지 않음
if (originalRequest.url === "/auth/login") {
return Promise.reject(error);
}
해결 후 흐름
이제 정상적인 흐름이 만들어졌다.

- 사용자가 httpClient로 API 요청
- 액세스 토큰 만료로 401 에러
- Response Interceptor 감지
- 순수 axios로 토큰 재발급 요청 (인터셉터 없음!)
- 새 토큰 저장 및 Redux 상태 업데이트
- 실패했던 요청 재시도
- 정상 응답
토큰 재발급 요청은 axios.post()를 직접 호출하기 때문에 httpClient의 인터셉터를 거치지 않는다.
배운 점
단순한 코드 실수처럼 보였지만, 이 문제를 해결하면서 많은 것을 배울 수 있었다.
1. 인터셉터의 동작 원리
인터셉터는 해당 axios 인스턴스의 모든 요청과 응답을 가로챈다는 것을 몸소 체험했다. 예외 없이 모든 것을 가로채기 때문에, 재발급 요청 같은 특수한 경우는 별도로 처리해야 한다.
2. 책임의 분리
하나의 httpClient로 모든 것을 처리하려고 하지 말고, 상황에 따라 적절한 방법을 선택하는 것이 중요하다. 인터셉터가 필요 없는 특수한 경우(토큰 재발급)는 순수 axios를 사용하는 것처럼 말이다. 단순히 버그를 막는 것뿐만 아니라, 코드의 의도를 명확하게 만들어준다.
3. 문제 해결 과정
처음에는 AI의 도움을 받아 해결책을 찾았지만, 단순히 코드만 복사하지 않고 왜 이렇게 해야 하는지를 이해하려고 노력했다. 덕분에 인터셉터의 동작 원리를 확실히 내 것으로 만들 수 있었다.
정리
JWT 인증 구현은 이제 많은 프로젝트에서 기본적으로 사용하는 기능이다. 이 과정에서 토큰 재발급은 거의 필수적으로 구현하게 되는데, Axios Interceptor를 사용하다 보면 나와 같은 무한 루프 문제를 한 번쯤은 겪게 될 것이다.
이 경험을 통해 비동기 통신과 인터셉터의 구조를 더 깊이 이해할 수 있었다
참고 자료
- Axios 공식 문서 - Interceptors
- JWT 인증 구현 가이드
'Archive > Java 풀스택 아카데미' 카테고리의 다른 글
| [TIL] 23. 12월 CORS(Cross-Origin Resource Sharing) (0) | 2025.12.21 |
|---|---|
| [TIL] 21. 12월 S3란? (1) | 2025.12.08 |
| [TIL] 20. 12월 Redis란? (0) | 2025.12.02 |
| [TIL] 19. 11월 JPA란 (0) | 2025.11.25 |
| [TIL] 18. 11월 JWT (0) | 2025.11.18 |