📌 개선 이유
백엔드와 통신시 인증이 필요한 경우 아래와 같이 헤더에 토큰을 넣어서 메서드를 요청해야 한다.
매 요청 작업마다 작성하면서도 '이걸 이렇게 다 쓰는 게 맞나?' 'spring의 인터셉터 같은 게 있지않을까?' 하고 생각했었다.하지만 무지했었기 때문에 일단 일일이 다 작성했던 상태였다.그리고 이제 리팩토링을 해볼까 하는 시점에서 편리한 방법이 있는지 검색해보았고, Axios Interceptor 를 알게 되었다.
📌 Axios Interceptor 란?
Axios Interceptor는 Axios에서 HTTP 요청과 응답을 가로채고, 이를 처리하거나 변형할 수 있는 기능을 제공한다. 이 기능은 요청을 보내기 전이나 응답을 받은 후에 특정 작업을 수행해야 할 때 매우 유용하고, 주로 다음과 같은 상황일 때 사용된다.
- 공통 헤더 추가: 모든 요청에 공통적으로 사용되는 헤더(예: 인증 토큰)를 추가할 때.
- 로딩 스피너 표시: 요청이 진행 중일 때 로딩 스피너를 표시하고, 응답을 받은 후 스피너를 숨길 때.
- 에러 처리: 응답에서 에러가 발생했을 때 공통적인 에러 처리 로직을 적용할 때.
- 응답 데이터 변형: 서버에서 받은 데이터를 클라이언트에서 사용할 수 있는 형태로 변형할 때.
즉, 나의 경우
인증이 필요한 모든 axios 요청에서 직접 헤더를 넣는 귀찮은 반복 작업을 axios interceptor를 이용하여 해결할 수 있다 !!
📌 AxiosInstance 생성
1. 의존성 및 초기 설정
import axios from 'axios';
import { useCookies } from 'react-cookie';
import { useNavigate } from 'react-router-dom';
import { useEffect, useState } from 'react';
2. Axios 인스턴스 설정
const AxiosInstance = () => {
const [cookies, setCookies, removeCookies] = useCookies(['accessToken']);
const navigate = useNavigate();
const [isRefreshing, setIsRefreshing] = useState(false); // 현재 토큰 갱신 요청이 진행 중인지 여부
const [refreshSubscribers, setRefreshSubscribers] = useState([]); // 토큰 갱신이 완료된 후 다시 시도해야 할 요청들을 저장
3. Axios 인스턴스 생성
useEffect(() => {
console.log('AxiosInstance - 쿠키에서 읽은 accessToken:', cookies.accessToken);
}, [cookies]);
const axiosInstance = axios.create({
headers: { "Content-Type": "application/json" }, // 기본 헤더 설정
baseURL: 'http://localhost:8082/', // 기본 URL 설정
withCredentials: true // 크로스 도메인 요청에 쿠키를 포함
});
4. 토큰 갱신 및 요청 구독 함수
const subscribeTokenRefresh = (cb) => {
setRefreshSubscribers((prevSubscribers) => [...prevSubscribers, cb]);
};
const onRefreshed = (newAccessToken) => {
refreshSubscribers.forEach((cb) => cb(newAccessToken));
setRefreshSubscribers([]);
};
5. 액세스 토큰 갱신 함수
const refreshAccessToken = async () => {
try {
const response = await axios.post('http://localhost:8082/api/v1/auth/n/refreshToken', {
accessToken: cookies.accessToken
}, {
withCredentials: true,
});
const newAccessToken = response.data.accessToken;
setCookies('accessToken', newAccessToken, { path: '/' });
return newAccessToken;
} catch (error) {
removeCookies('accessToken', { path: '/' });
navigate('/login');
throw error;
}
};
6. 요청 인터셉터
axiosInstance.interceptors.request.use(
(config) => {
if (!config.url.includes('/api/v1/auth/n/login') && cookies.accessToken) {
config.headers.Authorization = `Bearer ${cookies.accessToken}`;
}
return config;
},
(error) => Promise.reject(error)
);
7. 응답 인터셉터
axiosInstance.interceptors.response.use(
(response) => response,
async (error) => {
const statusCode = error.response?.status;
const originalRequest = error.config;
if (statusCode === 401 && !originalRequest._retry) {
if (isRefreshing) {
return new Promise((resolve) => {
subscribeTokenRefresh((newAccessToken) => {
originalRequest.headers.Authorization = `Bearer ${newAccessToken}`;
resolve(axiosInstance(originalRequest));
});
});
}
originalRequest._retry = true;
setIsRefreshing(true);
try {
const newAccessToken = await refreshAccessToken();
onRefreshed(newAccessToken);
originalRequest.headers.Authorization = `Bearer ${newAccessToken}`;
return axiosInstance(originalRequest);
} catch (refreshError) {
return Promise.reject(refreshError);
} finally {
setIsRefreshing(false);
}
}
return Promise.reject(error);
}
);
8. Axios 인스턴스 반환
return axiosInstance;
};
export default AxiosInstance;
Axios 인터셉터를 사용하여 자동으로 액세스 토큰을 갱신하고, 갱신된 토큰으로 실패한 요청을 재시도할 수 있도록 한다.
이를 통해 사용자는 로그인을 다시 하지 않아도 자동으로 토큰이 갱신되고 요청이 처리되는 경험을 할 수 있게 된다.
📌 AxiosInstance 적용
컴포넌트에서 Axios 인스턴스 사용하면 된다.
그리고 일일이 헤더에 토큰을 넣어줬던 부분을 삭제했다!
import { useAxios } from '../AxiosContext'; // Axios 인스턴스 가져오기
const axios = useAxios(); // Axios 인스턴스 사용
예시)
📌 결론
이번 작업에서는 Axios 인스턴스를 생성하고, 이를 통해 HTTP 요청 및 응답을 관리하는 방식을 구현했다. 특히, 액세스 토큰을 갱신하고 이를 재사용하는 로직을 포함하여, 인증 시스템을 보다 효율적으로 관리할 수 있게 되었다.
만약 이 방법을 진작에 알았다면 처음부터 일일이 헤더를 작성하지 않았을 텐데...😓 하고 잠시 자책했지만, 그런 비효율적인 경험이 오히려 도움이 되었다. 번거로움과 필요성을 직접 체감했기 때문에 스스로 찾아보고, 적용해보면서 얻은 것이 중요하다고 생각한다.
그리고 나는 이러한 유지보수성과 자동화가 개선되는 부분에서 재미와 쾌감을 느끼게 되는 것 같다..!
'Project 댕린이집' 카테고리의 다른 글
[Refactoring] Redis를 사용해 Refresh Token 관리해보자 (0) | 2024.07.25 |
---|---|
[소셜로그인] 카카오 로그인 (0) | 2024.07.03 |
[알림장] 작성 / 조회 (0) | 2024.07.01 |
[게시판] 댓글 (0) | 2024.06.29 |
[게시판] 좋아요 (0) | 2024.06.28 |