import axios from 'axios'; import store from "../../views/pages/AppStore"; // JWT 토큰의 만료 시간 확인 함수 const isTokenExpired = () => { try { const token = store.state.authorization; if (!token) return true; // JWT 토큰 디코딩 const base64String = token.split('.')[1]; const base64 = base64String.replace(/-/g, '+').replace(/_/g, '/'); const jsonPayload = decodeURIComponent(atob(base64).split('').map(c => { return '%' + ('00' + c.charCodeAt(0).toString(16)).slice(-2); }).join('')); const payload = JSON.parse(jsonPayload); // exp는 토큰 만료 시간(Unix timestamp) const expTime = payload.exp * 1000; // 밀리초 단위로 변환 const currentTime = new Date().getTime(); // 토큰 만료 5분 전부터는 미리 갱신 return currentTime > (expTime - 5 * 60 * 1000); } catch (e) { console.error("토큰 검증 오류:", e); return true; // 오류 발생 시 만료된 것으로 간주 } }; // 토큰 재발급 함수 (공통 함수로 분리) const refreshToken = async () => { try { const res = await axios.post("/refresh/tknReissue.json", {}, { headers: { "Content-Type": "application/json; charset=UTF-8", }, }); if (res.status === 200) { // 새로 발급 받은 AccessToken 저장 store.commit('setAuthorization', res.headers.authorization); // JWT 토큰 디코딩 const base64String = store.state.authorization.split('.')[1]; const base64 = base64String.replace(/-/g, '+').replace(/_/g, '/'); const jsonPayload = decodeURIComponent(atob(base64).split('').map(c => { return '%' + ('00' + c.charCodeAt(0).toString(16)).slice(-2); }).join('')); const mbr = JSON.parse(jsonPayload); store.commit("setUserNm", mbr.userNm); // 사용자 이름 저장 store.commit('setRoles', mbr.roles); // 사용자 역할 저장 return true; } throw new Error("토큰 재발급 요청 실패"); } catch (error) { console.error("토큰 재발급 중 오류:", error); throw error; } }; // 토큰 유효성 확인 및 필요시 갱신하는 함수 const ensureValidToken = async () => { if (isTokenExpired()) { try { await refreshToken(); return true; } catch (error) { console.error("토큰 갱신 실패:", error); throw error; } } return true; }; // Axios 클라이언트 생성 함수 const createClient = (contentType) => { const client = axios.create({ headers: { 'Content-Type': contentType, } }); // 요청 인터셉터 client.interceptors.request.use( async config => { // 요청 전에 토큰 만료 확인 및 필요시 갱신 try { await ensureValidToken(); const token = store.state.authorization; if (token) { config.headers.Authorization = token; } } catch (error) { // 토큰 갱신 실패 시 로그인 페이지로 이동 if (!window.location.pathname.includes('/login')) { const redirect = window.location.pathname + window.location.search; sessionStorage.setItem("redirect", redirect); alert('세션이 종료되었습니다.\n로그인을 새로 해주세요.'); store.commit("setStoreReset"); window.location = '/login.page'; } throw error; } return config; }, error => { return Promise.reject(error); } ); // 응답 인터셉터 client.interceptors.response.use( response => { return response; }, async error => { const originalReq = error.config; // 403 에러 처리 if (error.response && error.response.status === 403) { alert('접근 권한이 없습니다.'); window.history.back(); return Promise.reject(error); } // 401 에러 처리 (토큰 만료) if (error.response && error.response.status === 401 && error.response.data.message === '로그인 시간이 만료되었습니다.' && !originalReq._retry) { originalReq._retry = true; // 재시도 플래그 설정 try { // 토큰 재발급 await refreshToken(); // 새 토큰으로 헤더 업데이트 originalReq.headers.Authorization = store.state.authorization; // multipart/form-data 요청 처리를 위한 특별 처리 if (originalReq.headers['Content-Type'] && originalReq.headers['Content-Type'].includes('multipart/form-data')) { // 완전히 새로운 요청 생성 return axios({ ...originalReq, headers: { ...originalReq.headers, Authorization: store.state.authorization }, // FormData가 변경되지 않도록 원본 데이터 유지 transformRequest: [(data) => data] }); } // 일반 요청은 기존 client 사용하여 재시도 return client(originalReq); } catch (refreshError) { const redirect = window.location.pathname + window.location.search; sessionStorage.setItem("redirect", redirect); alert('세션이 종료되었습니다.\n로그인을 새로 해주세요.'); store.commit("setStoreReset"); window.location = '/login.page'; // 로그인 페이지로 리다이렉트 return Promise.reject(refreshError); } } return Promise.reject(error); } ); return client; }; // JSON 요청을 위한 apiClient const apiClient = createClient('application/json; charset=UTF-8'); // 멀티파트 파일 업로드를 위한 fileClient const fileClient = createClient('multipart/form-data'); export { apiClient, fileClient, ensureValidToken }; // 두 클라이언트와 토큰 유효성 확인 함수 내보냄