import axios from 'axios'; import store from "../../views/pages/AppStore"; // Vuex 스토어 임포트 // JWT 토큰 디코더 function decodeToken(token) { try { const base64String = token.split('.')[1]; const base64 = base64String.replace(/-/g, '+').replace(/_/g, '/'); const jsonPayload = decodeURIComponent(atob(base64).split('').map(c => '%' + ('00' + c.charCodeAt(0).toString(16)).slice(-2) ).join('')); return JSON.parse(jsonPayload); } catch (e) { console.error("Invalid token", e); return null; } } // 모든 클라이언트에 적용될 요청 인터셉터 const requestInterceptor = config => { // skipAuthRefresh는 요청 시점에 config에 직접 추가하여 토큰 재발급 로직을 건너뛰게 할 때 사용 if (store.state.authorization) { config.headers.Authorization = store.state.authorization; } return config; }; // 모든 클라이언트에 적용될 응답 인터셉터 const responseInterceptor = async error => { const originalReq = error.config; if (!error.response) return Promise.reject(error); // 로그인 요청 (skipAuthRefresh 플래그 확인) if (originalReq?.skipAuthRefresh) { return Promise.reject(error); } // 권한 없음 if (error.response.status === 403 && error.response.data.message === '접근 권한이 없습니다.') { alert('접근 권한이 없습니다.'); // 사용자에게 메시지 알림 window.history.back(); // 뒤로 가기 return Promise.reject(error); } // 리프레시 토큰 요청 자체는 재시도하지 않음 if (originalReq.url.includes('/refresh/tknReissue.json')) { return Promise.reject(error); } // 토큰 만료 시 한 번만 재시도 (401 에러) if (error.response.status === 401 && !originalReq._retry) { originalReq._retry = true; // 재시도 플래그 설정 try { // 리프레시 토큰으로 새 토큰 발급 요청 const res = await axios.post('/refresh/tknReissue.json', {}); const newToken = res.headers.authorization; // Vuex 스토어에 새 토큰 저장 store.commit('setAuthorization', newToken); originalReq.headers.Authorization = store.state.authorization; // 재시도 요청의 헤더 업데이트 // 유저 정보 다시 디코딩하여 스토어에 저장 (새 토큰 기준으로) const user = decodeToken(newToken); store.commit("setUserInfo", { userNm: user.userNm, loginId: user.loginId, userId: user.userId, roles: Array.isArray(user.roles) ? user.roles.map(r => r.authority) : [], }); // 실패했던 원본 요청 재시도 return axios(originalReq); // axios 인스턴스에 원래 요청 그대로 전달하여 재시도 } catch (refreshError) { // 리프레시 실패 시 (세션 만료 등) 로그인 페이지로 강제 이동 sessionStorage.setItem("redirect", window.location.pathname + window.location.search); alert('세션이 종료되었습니다.\n로그인을 새로 해주세요.'); store.commit("setStoreReset"); localStorage.clear(); sessionStorage.clear(); window.location.href = '/login.page'; return Promise.reject(refreshError); } } // 그 외 모든 에러는 그대로 reject return Promise.reject(error); }; const createConfiguredClient = (contentType) => { const client = axios.create({ baseURL: '/', // 모든 API 요청에 기본으로 붙을 URL headers: { 'Content-Type': contentType, }, }); // 요청 및 응답 인터셉터 적용 client.interceptors.request.use(requestInterceptor, error => Promise.reject(error)); client.interceptors.response.use(response => response, responseInterceptor); return client; }; // JSON 데이터 요청을 위한 클라이언트 const apiClient = createConfiguredClient('application/json; charset=UTF-8'); // 멀티파트(파일 업로드) 요청을 위한 클라이언트 const fileClient = createConfiguredClient('multipart/form-data'); // 모듈 외부로 내보내기 export { apiClient, fileClient };