[Axios] 토큰 재발급 중복 요청 문제 해결

Oneik·2024년 9월 27일
0
post-thumbnail

문제 상황

클라이언트는 Axios Interceptors를 이용하여 모든 요청을 가로채 AccessToken을 Header에 추가합니다. AccessToken이 만료된다면, Cookie에 저장된 RefreshToken을 이용하여 새로운 AccessToken을 발급 받습니다.

이러한 토큰 관리 시스템에서 메인 페이지 접속 시 다음과 같은 문제가 발생했습니다. 메인 페이지 로드 시, 유저 정보, 메인 퀘스트 정보, 서브 퀘스트 정보를 비동기로 요청합니다. AccessToken이 만료된 경우 각 요청마다 401에러가 발생합니다. 그 결과 RefreshToken을 이용한 새로운 AccessToken 재발급 요청이 3번 중복해서 발생하게 되었습니다.

참고: Axios는 비동기로 요청을 보내고, 서버에서 먼저 처리된 순서대로 응답을 받습니다.

해결 방법: 대기열 방식

저는 대기열 방식을 통해 토큰 재발급 요청 문제를 해결했습니다. 이 방식은 다음과 같이 작동합니다.

  1. 가장 먼저 401에러 응답을 받은 경우에만 토큰 재발급을 요청합니다.
  2. 이후의 401에러 콜백들은 토큰 재발급 요청을 보내지 않고 대기열에 등록합니다.
  3. 새 토큰을 받으면, 대기열에 등록된 모든 콜백에 새 토큰을 전달합니다.
  4. 새 토큰을 받은 각 요청은 갱신된 토큰으로 원래의 요청을 재시도합니다.
// 토큰 갱신중인지 확인하기 위한 변수
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 중복요청

profile
초보 개발자의 블로그입니다

0개의 댓글

관련 채용 정보