사용한 프레임워크:
axios, react-cookie
1. 왜 사용했는지?
만료시간이 다 된 토큰을 사용했을 때, 자동으로 represh token으로 access token을 재발급 받기 위해서 사용함.
토큰은 cookie에 담아서 사용하기 때문에 react-cookie를 사용함.
인터셉터(Interceptor)란? 요청(Request) 또는 응답(Response)이 애플리케이션에서 처리되기 전에 가로채어 특정 작업을 수행할 수 있도록 해주는 중간 처리 메커니즘
2. 사용 방법
2-1 먼저 cookie를 전역에서 사용하기 위해 최상위 컴포넌트에 <CookiesProvider>를 감싸준다.
// App.tsx
useAxiosInterceptor();
return (
<>
<CookiesProvider>
<Routes>
<Route element={<MainContent/>}>
<Route path={MAIN_PATH()} element={<Main/>}/>
<Route path={SIGNUP_PATH()} element={<SignUp/>}/>
<Route path={LOGIN_PATH()} element={<Login/>}/>
<Route path={TEST_PATH()} element={<Test/>}/>
<Route path={CATEGORY_PATH()} element={<Category/>}/>
</Route>
</Routes>
</CookiesProvider>
</>
);
2-2 useAxiosIntercepter.tsx 작성
import React from 'react';
import axios from 'axios';
import { useCookies } from 'react-cookie';
import {LOGIN_PATH} from "../constant";
const API_DOMAIN = process.env.REACT_APP_SERVER_DOMAIN + "/api";
// 커스텀 훅으로 인터셉터 관리
export const useAxiosInterceptor = () => {
const [cookies, setCookie, removeCookie] = useCookies(['accessToken', 'refreshToken']);
React.useEffect(() => {
// 요청 인터셉터
// const requestInterceptor = axios.interceptors.request.use(
// (config) => {
// const accessToken = cookies.accessToken;
// console.log(cookies);
// debugger;
// if (accessToken) {
// config.headers.Authorization = `Bearer ${accessToken}`;
// }
// return config;
// },
// (error) => Promise.reject(error)
// );
// 응답 인터셉터
const responseInterceptor = axios.interceptors.response.use(
(response) => response,
async (error) => {
const originalRequest = error.config;
if (error.response?.status === 401 && !originalRequest._retry && error.response?.data.code === 'EJT') {
originalRequest._retry = true;
try {
if (!cookies.refreshToken) {
console.log("refreshToken이 없습니다.");
return;
}
const refreshToken = cookies.refreshToken;
const response = await axios.post(`${API_DOMAIN}/auth/refresh`, { refreshToken });
const { accessToken, refreshToken: newRefreshToken, accessTokenExpiredMs, refreshTokenExpiredMs } = response.data;
const now = new Date().getTime();
const accessTokenExpires = new Date(now + accessTokenExpiredMs);
const refreshTokenExpires = new Date(now + refreshTokenExpiredMs);
// 새 토큰들 쿠키에 설정
setCookie('accessToken', accessToken, {
path: '/',
expires: accessTokenExpires
});
if (newRefreshToken) {
setCookie('refreshToken', newRefreshToken, {
path: '/',
expires: refreshTokenExpires
// 리프레시 토큰 만료 시간 설정
});
}
// 원래 요청 재시도
originalRequest.headers.Authorization = `Bearer ${accessToken}`;
return axios(originalRequest);
} catch (refreshError) {
// 토큰 갱신 실패 시 로그아웃 처리
removeCookie('accessToken');
removeCookie('refreshToken');
// 로그인 페이지로 리다이렉트 등 추가 처리
window.location.href = LOGIN_PATH();
return Promise.reject(refreshError);
}
}
return Promise.reject(error);
}
);
// 클린업 함수: 컴포넌트 언마운트 시 인터셉터 제거
return () => {
// axios.interceptors.request.eject(requestInterceptor);
axios.interceptors.response.eject(responseInterceptor);
};
}, [cookies.accessToken, cookies.refreshToken, setCookie, removeCookie]);
};
API 서버에서 만료된 토큰일 경우 401 응답 값과 EJT 에러 코드를 반환을 했으며, 다시 요청하지 않았을 경우에만 ${API_DOMAIN}/auth/refresh에 refreshToken을 보내서 accessToken을 요청
if (error.response?.status === 401 && !originalRequest._retry && error.response?.data.code === 'EJT') {
// 재귀요청 방지
originalRequest._retry = true
// refreshToken이 없으면 요청X
if (!cookies.refreshToken) {
console.log("refreshToken이 없습니다.");
return;
}
// accessToken이 필요한 api를 호출하니깐 원래 요청을 다시 요청할 때 header에 accessToken을 담아서 요청
originalRequest.headers.Authorization = `Bearer ${accessToken}`;
return axios(originalRequest); // 원래 요청 재시도
현재는 response 인터셉터만 필요하기 때문에 해당 내용만 정리.
request는 필요하면 나중에 추가 정리.