하관우 하관우 04-02
2025-04-02 하관우 멀티파트 수정 index.js
@ebccfe7166c40852c49019e53d3e093a2174537f
client/resources/api/index.js
--- client/resources/api/index.js
+++ client/resources/api/index.js
@@ -1,33 +1,78 @@
 import axios from 'axios';
 import store from "../../views/pages/AppStore";
 
-// 토큰 재발급 함수 (공통 함수로 분리)
-const refreshToken = async () => {
-    const res = await axios.post("/refresh/tknReissue.json", {}, {
-        headers: {
-            "Content-Type": "application/json; charset=UTF-8",
-        },
-    });
-
-    if (res.status === 200) {
-        console.log("토큰 재발급 성공! 굿~");
-        // 새로 발급 받은 AccessToken 저장
-        store.commit('setAuthorization', res.headers.authorization);
+// JWT 토큰의 만료 시간 확인 함수
+const isTokenExpired = () => {
+    try {
+        const token = store.state.authorization;
+        if (!token) return true;
         
         // JWT 토큰 디코딩
-        const base64String = store.state.authorization.split('.')[1];
+        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 mbr = JSON.parse(jsonPayload);
-        store.commit("setUserNm", mbr.userNm); // 사용자 이름 저장
-        store.commit('setRoles', mbr.roles); // 사용자 역할 저장
         
-        return true;
+        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; // 오류 발생 시 만료된 것으로 간주
     }
-    
-    throw new Error("토큰 재발급 요청 실패");
+};
+
+// 토큰 재발급 함수 (공통 함수로 분리)
+const refreshToken = async () => {
+    try {
+        const res = await axios.post("/refresh/tknReissue.json", {}, {
+            headers: {
+                "Content-Type": "application/json; charset=UTF-8",
+            },
+        });
+
+        if (res.status === 200) {
+            console.log("토큰 재발급 성공! 굿~");
+            // 새로 발급 받은 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 클라이언트 생성 함수
@@ -40,10 +85,24 @@
 
     // 요청 인터셉터
     client.interceptors.request.use(
-        config => {
-            const token = store.state.authorization; // Access Token 가져오기
-            if (token) {
-                config.headers.Authorization = token; // 토큰 추가
+        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;
         },
@@ -76,20 +135,29 @@
                 originalReq._retry = true; // 재시도 플래그 설정
                 
                 try {
-                    // 공통 토큰 재발급 함수 호출
+                    // 토큰 재발급
                     await refreshToken();
                     
-                    // 중요: Content-Type 헤더 보존
+                    // 새 토큰으로 헤더 업데이트
                     originalReq.headers.Authorization = store.state.authorization;
                     
-                    // multipart/form-data 요청의 경우 data 처리 방식이 다름
+                    // multipart/form-data 요청 처리를 위한 특별 처리
                     if (originalReq.headers['Content-Type'] && 
                         originalReq.headers['Content-Type'].includes('multipart/form-data')) {
-                        // multipart의 경우 FormData가 이미 생성되어 있으므로 그대로 사용
-                        // 특별한 처리 없이 원래 요청 재시도
+                        
+                        // 완전히 새로운 요청 생성
+                        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;
@@ -114,4 +182,4 @@
 // 멀티파트 파일 업로드를 위한 fileClient
 const fileClient = createClient('multipart/form-data');
 
-export { apiClient, fileClient }; // 두 클라이언트를 내보냄
(파일 끝에 줄바꿈 문자 없음)
+export { apiClient, fileClient, ensureValidToken }; // 두 클라이언트와 토큰 유효성 확인 함수 내보냄
(파일 끝에 줄바꿈 문자 없음)
Add a comment
List