1. 요즘..
현재 진행 중인 토이프로젝트인 https://www.link-archive.com/ 는 북마크가 가능한 SNS 서비스이다. 관심 있는 URL을 저장할 수 있고 다른 사람의 URL을 둘러볼 수 있으며 북마크를 해서 가져올 수 있는 서비스이다.
요즘 구직활동 + 이슈해결 + 스터디로 블로깅을 최근에 못했다.
많고 많은 이슈 중에 오늘은 토큰을 재발급하는 이슈를 해결해서 블로그를 작성하려고 한다.
2. 정책
우선 현재 우리 서비스 정책을 간단하게 알려보자면,
AccessToken, RefreshToken을 이용 중이고 AccessToken은 JWT로 stateless 하게 보안을 확인 중이다.
AccessToken은 만료기한은 2시간, RefreshToken은 만료기한이 30일이다.
RefreshToken을 통해서 AccessToken을 재발급받을 수 있고,
AccessToken이 아직 유효한데 재발급을 요청한다면 보안이 깨졌다고 판단하여 RefreshToken, AccessToken을 만료시킨다.
그러므로 AccessToken이 만료되었을 때 재발급 요청을 보내야 한다.
3. 구현
Axios의 인터셉터를 이용해서 재발급 메서드를 구현했는데 클라이언트는 AccessToken이 만료되어 재발급을 받았단 사실을 모르게 하기 위해 뒷단에서 처리했다.
이 부분이 참 어려웠던 거 같다..!
우선, 토큰이 만료되었을 때 요청한 api요청을 큐에 담아서 잠깐 대기시켜 놓고 재발급 이후 대기시켜 놓은 api요청을 다시 실행시켜 스무스하게 이어 붙여줘야 한다.
그렇기 위해 큐를 만들어야 한다.
// 요청을 담는 함수
const addSubscriber = (callback: (token: string) => any) => {
subscribers.push(callback);
};
그러고 나서 이 큐를 실행시킬 함수를 만든다.
// 대기시킨 callback함수를 순차적으로 실행하는 함수
const onAccessTokenFetched = (token: string) => {
subscribers.forEach((callback) => callback(token));
subscribers = [];
};
이렇게 세팅해 놓고 토큰 재발급의 트리거를 설정한다.
우리 백엔드분들과 약속한 INVALID_TOKEN이라는 응답에 걸어둔다.
instance.interceptors.response.use(
(response) => response,
(error) => {
if (error.response?.data.code === 'INVALID_TOKEN') { // << 트리거
console.log('만료된 토큰입니다 재발급 하겠습니다.');
return getNewAccessToken(error);
}
return Promise.reject(error);
}
);
이제 핵심로직을 짜보면,
const getNewAccessToken = async (oError) => {
try {
const { response } = oError;
// 큐에 밀어 넣기
const retryOriginalRequest = new Promise((resolve) => {
addSubscriber((token: string) => {
response!.config.headers.Authorization = `Bearer ${token}`;
resolve(axios(response!.config));
});
});
if (!fetchingToken) {
fetchingToken = true;
const { refreshToken } = await API.getRefreshToken();
const token = getAccessToken();
const res = await API.getNewAccessToken({
refreshToken,
accessToken: token,
});
if (!isServer) {
setAccessToken(res.data.accessToken);
}
await API.setCookie({ name: 'accessToken', value: res.data.accessToken });
// 새로운 토큰을 발급받으면 요청 재시도
onAccessTokenFetched(res.data.accessToken);
}
console.log('토큰이 성공적으로 재발급 되었습니다.');
return retryOriginalRequest;
} catch (error) {
// 그래도 에러나면 쿠키&큐 비우고 로그인페이지로 이동
await API.deleteAllCookies();
subscribers = [];
window.location.href = '/login';
return Promise.reject(oError);
} finally {
fetchingToken = false;
}
};
이런 식으로 구현이 가능하다.
'토이프로젝트 > LinkArchive' 카테고리의 다른 글
횡단 관심사 분리를 위한 Axios 인스턴스와 인터셉터 설정 (0) | 2023.07.27 |
---|---|
[문제 해결] Warning: An update to Profile inside a test was not wrapped in act(...). (1) | 2023.07.18 |
Redux Provider로 감싸진 컴포넌트 테스트하기 (0) | 2023.07.15 |