Axios inteceptor token 갱신 + debounce
레거시 프로젝트에 토큰 갱신 인터셉터를 수정하다....
한페이지 내에서 호출이 여러번 들어오는데 호출 1개당 토큰리프레시 1회가 호출된다...
낭비 ...
디바운스 적용 ... Promise 객체를 써야할듯하다.
해결안 : 잘된다
// 지금 토큰 갱신 중인지
let isRefreshing = false;
// 여러개 호출 들어오는 경우 : 모두 토큰 갱신이 필요
let originalRequests = [];
apiClient.interceptors.response.use(
function (response) {
return response;
},
async error => {
console.log(
'%c interceptors.response ::: error : ',
'background: black; color: crimson',
error,
);
// axios 사용하기 때문 에러도 AxiosError 객체
if (axios.isAxiosError(error)) {
if (
error.response.status === 401 &&
error.response.data.code === AuthErrCodes.TOKEN_VERIFY_ERROR
) {
const accessToken = sessionStorage.getItem('accessToken');
const refreshToken = sessionStorage.getItem('refreshToken');
if (!refreshToken)
return Promise.reject('재 로그인이 필요합니다.[No Refresh Token]');
const originalRequest = error.config;
// 요청건이 처음 시도인지 체크
if (!originalRequest._retry) {
if (isRefreshing) {
return new Promise(function (resolve, reject) {
originalRequests.push({
url: originalRequest.url,
resolve,
reject,
});
})
.then(token => {
originalRequest.headers['Authorization'] = 'Bearer ' + token;
return axios(originalRequest);
})
.catch(err => {
return Promise.reject(err);
});
}
originalRequest._retry = true;
isRefreshing = true;
return new Promise(function (resolve, reject) {
renewAuthTokens({ accessToken, refreshToken })
.then(({ accessToken, refreshToken }) => {
sessionStorage.setItem('accessToken', accessToken);
sessionStorage.setItem('refreshToken', refreshToken);
apiClient.defaults.headers.common['Authorization'] =
'Bearer ' + accessToken;
originalRequest.headers['Authorization'] =
'Bearer ' + accessToken;
originalRequests.forEach(originalRequestPromise => {
originalRequestPromise.resolve(accessToken);
console.log('처리됨 ::: ', originalRequestPromise.url);
});
resolve(axios(originalRequest));
})
.catch(err => {
originalRequests.forEach(originalRequestPromise => {
originalRequestPromise.reject(err);
});
// 리덕스 예제
// store.dispatch(showMessage({ message: 'Expired Token' }));
reject(err);
})
.then(() => {
isRefreshing = false;
});
});
}
}
디바운스는 위에 로직하고 섞어써야할듯하다. => 조금 미뤄둔다.
originalRequest =[] 에 promise객체 써주고 originalRequest._try 체크 해주면 될듯하다.
인터셉터 외부에 변수를 정의
let timer = null;
let originalRequests = [];
apiClient.interceptors.response.use(
function (response) {
return response;
},
async error => {
console.log(
'%c인터셉터 에러 ==== ',
'background: lightgreen; color: white',
error,
);
// axios 사용하기 때문 에러도 AxiosError 객체
if (axios.isAxiosError(error)) {
if (
error.response.status === 401 &&
error.response.data.code === AuthErrCodes.TOKEN_VERIFY_ERROR
) {
const authTokens = JSON.parse(
sessionStorage.getItem('recoil-persist'),
)?.auth;
if (!authTokens.refreshToken)
return Promise.reject('refreshToken을 불러올 수 없습니다.');
// 디아운스 토큰 요청 한번에 요청 && 처리
// 0.5s이내 토큰 리프레시 들어오면 요청중이던 토큰 리프레시 스탑 후 재실행
const originalRequest = error.config;
originalRequests.push(originalRequest);
if (timer) {
clearTimeout(timer);
}
timer = setTimeout(async () => {
// token refresh 요청
const data = await renewAuthTokens(authTokens);
sessionStorage.setItem(
'tokens',
JSON.stringify({
accessToken: data.accessToken,
refreshToken: data.refreshToken,
}),
);
setAxiosApiAuthToken(data.accessToken);
// 토큰 에러 처리 후 요청 다시 진행
originalRequests.map(async originalRequest => {
originalRequest.headers[
'Authorization'
] = `Bearer ${data.accessToken}`;
//axios.reqeust(originalRequest) 쓰면 어레이 마지막 요청만 호출됨
return apiClient(originalRequest);
});
//array비우기
originalRequests.splice(0);
}, 500);
return;
}
if (error.status === 500) {
console.log('%c error 500====', 'background-color: yellow');
return alert(error.message);
}
console.log('response error', error);
return Promise.reject(error);
}
},
);