클라이언트는 Axios Interceptors를 이용하여 모든 요청을 가로채 AccessToken을 Header에 추가합니다. AccessToken이 만료된다면, Cookie에 저장된 RefreshToken을 이용하여 새로운 AccessToken을 발급 받습니다.
이러한 토큰 관리 시스템에서 메인 페이지 접속 시 다음과 같은 문제가 발생했습니다. 메인 페이지 로드 시, 유저 정보, 메인 퀘스트 정보, 서브 퀘스트 정보를 비동기로 요청합니다. AccessToken이 만료된 경우 각 요청마다 401에러가 발생합니다. 그 결과 RefreshToken을 이용한 새로운 AccessToken 재발급 요청이 3번 중복해서 발생하게 되었습니다.
참고: Axios는 비동기로 요청을 보내고, 서버에서 먼저 처리된 순서대로 응답을 받습니다.
저는 대기열 방식을 통해 토큰 재발급 요청 문제를 해결했습니다. 이 방식은 다음과 같이 작동합니다.
// 토큰 갱신중인지 확인하기 위한 변수
let isTokenRefreshing = false;
// 대기열
let refreshSubscribers: ((accessToken: string) => void)[] = [];
const onTokenRefreshed = (accessToken: string) => {
refreshSubscribers.forEach((callback) => callback(accessToken));
refreshSubscribers = [];
};
const addRefreshSubscriber = (callback: (accessToken: string) => void) => {
refreshSubscribers.push(callback);
};
...
axiosInstance.interceptors.response.use(
(response) => {
return response;
},
async (error) => {
const originalRequest = error.config;
if (error.response?.status === 401) {
if (!isTokenRefreshing) {
// 가장 먼저 들어온 401에러일 경우, true로 변경
isTokenRefreshing = true;
try {
// 비동기로 토큰 재발급 요청
const { accessToken } = await refreshToken();
setToken(accessToken);
originalRequest.headers.Authorization = `Bearer ${accessToken}`;
// 갱신된 토큰을 콜백에 전달
onTokenRefreshed(accessToken);
isTokenRefreshing = false;
return axiosInstance(originalRequest);
} catch (error) {
isTokenRefreshing = false;
refreshSubscribers = [];
removeToken();
await logout();
return Promise.reject(error);
}
}
// 토큰 갱신중이라면, 콜백을 대기열에 추가
const retryOriginalRequest = new Promise((resolve) => {
addRefreshSubscriber((accessToken) => {
originalRequest.headers.Authorization = `Bearer ${accessToken}`;
resolve(axiosInstance(originalRequest));
});
});
return retryOriginalRequest;
}
return Promise.reject(error);
}
);
return axiosInstance;
};
axios interceptors와 refresh token을 활용한 jwt 토큰 관리
Axios Jwt RefreshToken 중복요청