Vue3로 프로젝트를 진행하는 과정에서 AxiosInterceptors 설정에 대해 정리하고자 한다.
모든 설정은 공식문서를 기반으로 한다.
then과 catch로 처리되기 전에 axios 요청이나 응답을 가로채는 것이다.
서버에 요청을 보내기 전에 요청을 가로채서 헤더에 토큰을 넣어줄 수도 있고,
에러 응답이 왔을 때 에러를 사용자에게 띄워줄 수 있다.
이번 프로젝트에서 다음과 같이 활용할 것이다.
accessToken을 넣어 보낸다.refreshToken으로 accessToken을 재발급 받고, 재발급 받은 accessToken으로 다시 기존 요청을 보낸다.이 외 서비스 특성을 반영해 여러 세부 조건을 넣어주면 된다. 굵직한 동작은 이게 다다.
// Add a request interceptor - 요청 인터셉터
axios.interceptors.request.use(function (config) {
// Do something before request is sent - 요청 날리기 전 실행
return config;
}, function (error) {
// Do something with request error - 요청 에러시 실행
return Promise.reject(error);
});
// Add a response interceptor - 응답 인터셉터
axios.interceptors.response.use(function (response) {
// Any status code that lie within the range of 2xx cause this function to trigger
// - 200번대 응답은 여기서 처리
// Do something with response data - 응답받은 데이터 처리
return response;
}, function (error) {
// Any status codes that falls outside the range of 2xx cause this function to trigger
// - 200번대 외의 응답은 여기서 처리
// Do something with response error - 응답 에러 처리
return Promise.reject(error);
});
.env 파일을 설정하기 전이라 baseURL이 그대로 노출되어 있지만 어차피 로컬이라 상관 없이 그냥 진행한다.
인스턴스를 생성하고 인스턴스에 커스텀 설정을 할 수 있다. 나 같은 경우는 요청을 날리는 BASE URL를 적었다.
import axios from 'axios';
const axiosInstance = axios.create({
baseURL: 'http://127.0.0.1:8000/api/',
headers: {
'Content-Type': 'application/json',
},
});
export default axiosInstance;
이제 요청을 할 때마다 accessToken이 있다면 헤더에 실어서 보낼 것이다.
accessToken이 없는 경우에는 로그인을 하지 않은 유저니, 토큰이 필요한 요청에는 에러가 뜨겠고 토큰이 필요 없는 요청에는 응답이 올 것이다.axiosInstance.interceptors.request.use(
function (config) {
const { accessToken } = useAuthStore();
if (accessToken) {
config.headers.Authorization = `Bearer ${accessToken}`;
}
return config;
},
function (error) {
return Promise.reject(error);
}
);
만약 에러 코드가 unAuthorized 401이라면, 두 가지 상황이 있겠다.
accessToken이 필요한 요청에 accessToken을 보내지 않음accessToken이 만료된 상태내가 실행하고 싶은 동작은 다음과 같다.
refreshToken이 존재한다면
1-1. refreshToken을 보내 새로운 accessToken을 발급 받고 이전 요청을 다시 보낸다.
1-2.존재하는 refreshToken 역시 만료되었다면 재로그인을 요청하며 관리자 첫 페이지로 리다이렉트 한다.
refreshToken이 존재하지 않는다면
2-1. 키오스크 첫 페이지로 리다이렉트 한다.
참고로 2번은 현재 진행하는 프로젝트 서비스의 특징 때문에 그렇다. 따라서 굵직한 기능을 먼저 작성하고, (토큰 재발급 & 이전 요청 다시 보냄) 본인이 따로 처리해주고 싶은 걸 알아서 작성하면 된다.
axiosInstance.interceptors.response.use(
function (response) {
console.log(response);
return response;
},
function (error) {
console.log('ERROR >>>', error.response.data);
const originalRequest = error.config;
const { refreshToken, updateAccessToken } = useAuthStore();
// 401 에러 발생시
if (error.response.status === 401) {
// refreshToken이 있다면
if (refreshToken) {
// refreshToken으로 accessToken 발급 요청
return axios
.post('http://127.0.0.1:8000/api/accounts/token/refresh/', {
username: '로그인된 사용자 이름',
refreshToken,
})
// accessToken 발급 성공
.then((res) => {
// 새롭게 발급 받은 accessToken 저장 및 이전 요청 다시 보냄
updateAccessToken(res.data?.accessToken);
originalRequest.headers.Authorization = `Bearer ${res.data.accessToken}`;
return axiosInstance(originalRequest);
})
// refreshToken 만료, accessToken 발급 실패
.catch((error) => {
// 재로그인 요청 및 어드민 첫페이지로 리다이렉트
alert('다시 로그인해주세요.');
router.push({ name: 'admin-index' });
return Promise.reject(error);
});
// refreshToken이 없다면
} else {
// 키오스크 첫페이지로 리다이렉트
router.push({ name: 'kiosk-index' });
}
}
return Promise.reject(error);
}
);
import axios from 'axios';
import router from '@/router/index';
import { useAuthStore } from '@/stores/auth.store';
const axiosInstance = axios.create({
baseURL: 'http://127.0.0.1:8000/api/',
headers: {
'Content-Type': 'application/json',
},
});
axiosInstance.interceptors.request.use(
function (config) {
const { accessToken } = useAuthStore();
if (accessToken) {
config.headers.Authorization = `Bearer ${accessToken}`;
}
return config;
},
function (error) {
return Promise.reject(error);
}
);
axiosInstance.interceptors.response.use(
function (response) {
console.log(response);
return response;
},
function (error) {
console.log('ERROR >>>', error.response.data);
const originalRequest = error.config;
const { refreshToken, updateAccessToken } = useAuthStore();
// Unauthorized : request a new accessToken
if (error.response.status === 401) {
if (refreshToken.length) {
return axios
.post('http://127.0.0.1:8000/api/accounts/token/refresh/', {
username: '로그인된 사용자 이름',
refreshToken,
})
.then((res) => {
console.log(res);
updateAccessToken(res.data?.accessToken);
originalRequest.headers.Authorization = `Bearer ${res.data.accessToken}`;
return axiosInstance(originalRequest);
})
.catch((error) => {
alert('다시 로그인해주세요.');
router.push({ name: 'admin-index' });
return Promise.reject(error);
});
} else {
router.push({ name: 'kiosk-index' });
}
}
return Promise.reject(error);
}
);
export default axiosInstance;