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, } }); // 요청 인터셉터 등록 if (client.interceptors.request.handlers.length === 0) { client.interceptors.request.use( async config => { if (store.state.isHandlingSessionError) { return Promise.reject(new axios.Cancel('Error handling in progress')); } try { await ensureValidToken(); const token = store.state.authorization; if (token) { config.headers.Authorization = token; } } catch (error) { if (!store.state.isHandlingSessionError && !window.location.pathname.includes('/login')) { store.commit('setHandlingSessionError', true); const redirect = window.location.pathname + window.location.search; sessionStorage.setItem("redirect", redirect); alert('세션이 종료되었습니다.\n로그인을 새로 해주세요.'); store.commit("setStoreReset"); window.location = '/login.page'; } return Promise.reject(error); } return config; }, error => Promise.reject(error) ); } // 응답 인터셉터 등록 if (client.interceptors.response.handlers.length === 0) { client.interceptors.response.use( response => response, async error => { if (store.state.isHandlingSessionError) { return Promise.reject(error); } const originalReq = error.config; if (error.response && error.response.status === 403) { if (!store.state.isHandlingSessionError) { store.commit('setHandlingSessionError', true); alert('접근 권한이 없습니다.'); window.history.back(); } return Promise.reject(error); } if ( error.response && error.response.status === 401 && error.response.data.message === '로그인 시간이 만료되었습니다.' && !originalReq._retry ) { if (!store.state.isHandlingSessionError) { store.commit('setHandlingSessionError', true); originalReq._retry = true; try { await refreshToken(); originalReq.headers.Authorization = store.state.authorization; if ( originalReq.headers['Content-Type'] && originalReq.headers['Content-Type'].includes('multipart/form-data') ) { return axios({ ...originalReq, headers: { ...originalReq.headers, Authorization: store.state.authorization, }, transformRequest: [(data) => data], }); } } 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); } if ( error.response && (error.response.status === 400 || error.response.status === 500) ) { if (!store.state.isHandlingSessionError) { store.commit('setHandlingSessionError', true); const redirect = window.location.pathname + window.location.search; sessionStorage.setItem('redirect', redirect); alert('세션이 종료되었습니다.\n로그인을 새로 해주세요.'); store.commit('setStoreReset'); window.location = '/login.page'; } return Promise.reject(error); } 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 }; // 두 클라이언트와 토큰 유효성 확인 함수 내보냄