

250609 김혜민 로그아웃 세션 및 토큰제거 수정
@b232a0ec1f8ad6b8703e32056b53473f957a392a
--- client/views/index.js
+++ client/views/index.js
... | ... | @@ -25,9 +25,6 @@ |
25 | 25 |
const ctx = await contextPath(); |
26 | 26 |
const strgMode = await storageMode(); |
27 | 27 |
const lgnMode = await loginMode(); |
28 |
- console.log("Context Path:", ctx); |
|
29 |
- console.log("Storage Mode:", strgMode); |
|
30 |
- console.log("Login Mode:", lgnMode); |
|
31 | 28 |
|
32 | 29 |
const store = createAppStore(ctx, strgMode, lgnMode); |
33 | 30 |
|
--- client/views/pages/AppStore.js
+++ client/views/pages/AppStore.js
... | ... | @@ -123,81 +123,218 @@ |
123 | 123 |
}, |
124 | 124 |
}, |
125 | 125 |
actions: { |
126 |
- async logout({ commit }) { |
|
126 |
+ async logout({ commit, state, dispatch }) { |
|
127 |
+ console.log('로그아웃 프로세스 시작'); |
|
128 |
+ |
|
129 |
+ const ctx = state.contextPath; |
|
130 |
+ const admPath = state.path?.includes("/adm"); |
|
131 |
+ const loginMode = state.loginMode || localStorage.getItem('loginMode') || 'J'; |
|
132 |
+ |
|
127 | 133 |
try { |
128 |
- const ctx = this.state.contextPath; |
|
129 |
- const admPath = this.state.path?.includes("/adm") |
|
130 |
- const loginMode = this.state.loginMode || localStorage.getItem('loginMode') || 'J'; |
|
131 |
- |
|
132 |
- // 로그인 모드에 따른 처리 |
|
133 |
- if (loginMode === 'J') { |
|
134 |
- // JWT 방식인 경우만 서버 API 호출 |
|
135 |
- try { |
|
136 |
- const res = await logOutProc(); |
|
137 |
- if (res.data.message) { |
|
138 |
- alert(res.data.message); |
|
139 |
- } |
|
140 |
- } catch (error) { |
|
141 |
- console.log(error); |
|
142 |
- // API 에러가 발생해도 클라이언트는 정리 |
|
134 |
+ // 1. 서버 로그아웃 API 호출 (모든 모드에서 호출) |
|
135 |
+ try { |
|
136 |
+ const res = await logOutProc(); |
|
137 |
+ console.log('서버 로그아웃 응답:', res); |
|
138 |
+ |
|
139 |
+ if (res?.data?.message) { |
|
140 |
+ console.log('로그아웃 메시지:', res.data.message); |
|
143 | 141 |
} |
142 |
+ } catch (apiError) { |
|
143 |
+ // API 호출 실패해도 클라이언트 정리는 계속 진행 |
|
144 |
+ console.warn('서버 로그아웃 API 호출 실패:', apiError.message); |
|
144 | 145 |
} |
146 |
+ |
|
147 |
+ // 2. 클라이언트 완전 정리 |
|
148 |
+ await dispatch('performCompleteCleanup', { loginMode, ctx, admPath }); |
|
149 |
+ |
|
150 |
+ } catch (error) { |
|
151 |
+ console.error("로그아웃 처리 중 오류:", error); |
|
152 |
+ |
|
153 |
+ // 오류 발생해도 클라이언트 정리는 수행 |
|
154 |
+ await dispatch('performCompleteCleanup', { loginMode, ctx, admPath }); |
|
155 |
+ |
|
156 |
+ // 사용자에게는 간단한 메시지만 표시 |
|
157 |
+ console.log("로그아웃 처리가 완료되었습니다."); |
|
158 |
+ } |
|
159 |
+ }, |
|
160 |
+ |
|
161 |
+ // 완전한 클라이언트 정리 수행 |
|
162 |
+ async performCompleteCleanup({ commit, dispatch }, { loginMode, ctx, admPath }) { |
|
163 |
+ try { |
|
164 |
+ console.log('클라이언트 정리 시작'); |
|
145 | 165 |
|
146 | 166 |
// 1. 상태 초기화 |
147 | 167 |
commit("setStoreReset"); |
148 | 168 |
|
149 |
- // 2. 로컬스토리지와 세션스토리지 초기화 |
|
150 |
- localStorage.clear(); |
|
151 |
- sessionStorage.clear(); |
|
152 |
- |
|
153 |
- // 3. 모든 가능한 쿠키 삭제 (OAuth 관련 포함) |
|
154 |
- const cookiesToDelete = [ |
|
155 |
- 'refresh', |
|
156 |
- 'Authorization', |
|
157 |
- 'JSESSIONID', |
|
158 |
- 'oauth_access_token', |
|
159 |
- 'oauth_refresh_token', |
|
160 |
- 'SESSION' |
|
161 |
- ]; |
|
169 |
+ // 2. 모든 저장소 완전 정리 |
|
170 |
+ dispatch('clearAllStorages'); |
|
162 | 171 |
|
163 |
- cookiesToDelete.forEach(cookieName => { |
|
164 |
- document.cookie = `${cookieName}=; expires=Thu, 01 Jan 1970 00:00:00 UTC; path=/;`; |
|
165 |
- document.cookie = `${cookieName}=; expires=Thu, 01 Jan 1970 00:00:00 UTC; path=/; domain=${window.location.hostname};`; |
|
166 |
- }); |
|
172 |
+ // 3. 모든 쿠키 제거 |
|
173 |
+ dispatch('clearAllCookies'); |
|
167 | 174 |
|
168 |
- // 4. 로그인 페이지로 이동 |
|
169 |
- const cleanUrl = admPath ? |
|
170 |
- ctx + "/cmslogin.page" : |
|
171 |
- ctx + "/login.page"; |
|
172 |
- |
|
173 |
- window.history.replaceState({}, document.title, cleanUrl); |
|
174 |
- window.location.href = cleanUrl; |
|
175 |
- |
|
176 |
- } catch(error) { |
|
177 |
- console.error("로그아웃 처리 중 오류:", error); |
|
178 |
- |
|
179 |
- // 에러가 발생해도 클라이언트 상태는 정리 |
|
180 |
- commit("setStoreReset"); |
|
181 |
- localStorage.clear(); |
|
182 |
- sessionStorage.clear(); |
|
183 |
- |
|
184 |
- const ctx = this.state.contextPath; |
|
185 |
- const admPath = this.state.path?.includes("/adm"); |
|
186 |
- |
|
187 |
- const errorData = error.response?.data; |
|
188 |
- if (errorData?.message) { |
|
189 |
- alert(errorData.message); |
|
190 |
- } else { |
|
191 |
- console.log("로그아웃 처리 중 예상치 못한 오류 발생"); |
|
175 |
+ // 4. 세션 무효화 (세션 모드인 경우) |
|
176 |
+ if (loginMode === 'S') { |
|
177 |
+ dispatch('invalidateSession'); |
|
192 | 178 |
} |
193 | 179 |
|
194 |
- const cleanUrl = admPath ? |
|
195 |
- ctx + "/cmslogin.page" : |
|
196 |
- ctx + "/login.page"; |
|
180 |
+ // 5. 브라우저 캐시 정리 |
|
181 |
+ dispatch('clearBrowserCache'); |
|
182 |
+ |
|
183 |
+ // 6. 페이지 리다이렉트 |
|
184 |
+ const loginUrl = admPath ? |
|
185 |
+ (ctx || '') + "/cmslogin.page" : |
|
186 |
+ (ctx || '') + "/login.page"; |
|
197 | 187 |
|
198 |
- window.location.href = cleanUrl; |
|
188 |
+ console.log('로그인 페이지로 이동:', loginUrl); |
|
189 |
+ |
|
190 |
+ // 히스토리 정리 후 이동 |
|
191 |
+ window.history.replaceState(null, '', loginUrl); |
|
192 |
+ window.location.href = loginUrl; |
|
193 |
+ |
|
194 |
+ } catch (cleanupError) { |
|
195 |
+ console.error('클라이언트 정리 중 오류:', cleanupError); |
|
196 |
+ |
|
197 |
+ // 최종 수단: 강제 새로고침 |
|
198 |
+ window.location.reload(); |
|
199 | 199 |
} |
200 | 200 |
}, |
201 |
+ |
|
202 |
+ // 모든 저장소 정리 |
|
203 |
+ clearAllStorages() { |
|
204 |
+ try { |
|
205 |
+ // localStorage 완전 정리 |
|
206 |
+ const localStorageKeys = Object.keys(localStorage); |
|
207 |
+ localStorageKeys.forEach(key => { |
|
208 |
+ localStorage.removeItem(key); |
|
209 |
+ }); |
|
210 |
+ localStorage.clear(); |
|
211 |
+ |
|
212 |
+ // sessionStorage 완전 정리 |
|
213 |
+ const sessionStorageKeys = Object.keys(sessionStorage); |
|
214 |
+ sessionStorageKeys.forEach(key => { |
|
215 |
+ sessionStorage.removeItem(key); |
|
216 |
+ }); |
|
217 |
+ sessionStorage.clear(); |
|
218 |
+ |
|
219 |
+ console.log('모든 저장소 정리 완료'); |
|
220 |
+ } catch (error) { |
|
221 |
+ console.error('저장소 정리 중 오류:', error); |
|
222 |
+ } |
|
223 |
+ }, |
|
224 |
+ |
|
225 |
+ // 모든 쿠키 제거 |
|
226 |
+ clearAllCookies() { |
|
227 |
+ try { |
|
228 |
+ const cookiesToDelete = [ |
|
229 |
+ // 일반 인증 쿠키 |
|
230 |
+ 'refresh', |
|
231 |
+ 'Authorization', |
|
232 |
+ 'access_token', |
|
233 |
+ 'JSESSIONID', |
|
234 |
+ 'SESSION', |
|
235 |
+ |
|
236 |
+ // OAuth 관련 쿠키 |
|
237 |
+ 'oauth_access_token', |
|
238 |
+ 'oauth_refresh_token', |
|
239 |
+ 'oauth_state', |
|
240 |
+ 'OAUTH2_AUTHORIZATION_REQUEST', |
|
241 |
+ 'oauth2_auth_request', |
|
242 |
+ |
|
243 |
+ // 카카오 관련 |
|
244 |
+ 'kakao_login', |
|
245 |
+ '_kadu', |
|
246 |
+ '_kadub', |
|
247 |
+ '_kalt', |
|
248 |
+ |
|
249 |
+ // 네이버 관련 |
|
250 |
+ 'NID_AUT', |
|
251 |
+ 'NID_SES', |
|
252 |
+ 'NID_JKL', |
|
253 |
+ |
|
254 |
+ // 구글 관련 |
|
255 |
+ 'SACSID', |
|
256 |
+ 'APISID', |
|
257 |
+ 'SSID', |
|
258 |
+ '1P_JAR', |
|
259 |
+ |
|
260 |
+ // 기타 가능한 쿠키 |
|
261 |
+ 'remember-me', |
|
262 |
+ 'user-session', |
|
263 |
+ 'auth-token' |
|
264 |
+ ]; |
|
265 |
+ |
|
266 |
+ const domains = [ |
|
267 |
+ '', |
|
268 |
+ '.' + window.location.hostname, |
|
269 |
+ window.location.hostname, |
|
270 |
+ '.localhost', |
|
271 |
+ 'localhost' |
|
272 |
+ ]; |
|
273 |
+ |
|
274 |
+ const paths = ['/', '/oauth2', '/login', '/auth']; |
|
275 |
+ |
|
276 |
+ cookiesToDelete.forEach(cookieName => { |
|
277 |
+ paths.forEach(path => { |
|
278 |
+ domains.forEach(domain => { |
|
279 |
+ // 기본 쿠키 삭제 |
|
280 |
+ document.cookie = `${cookieName}=; expires=Thu, 01 Jan 1970 00:00:00 UTC; path=${path};`; |
|
281 |
+ |
|
282 |
+ // 도메인별 쿠키 삭제 |
|
283 |
+ if (domain) { |
|
284 |
+ document.cookie = `${cookieName}=; expires=Thu, 01 Jan 1970 00:00:00 UTC; path=${path}; domain=${domain};`; |
|
285 |
+ } |
|
286 |
+ }); |
|
287 |
+ }); |
|
288 |
+ }); |
|
289 |
+ |
|
290 |
+ console.log('모든 쿠키 제거 완료'); |
|
291 |
+ } catch (error) { |
|
292 |
+ console.error('쿠키 제거 중 오류:', error); |
|
293 |
+ } |
|
294 |
+ }, |
|
295 |
+ |
|
296 |
+ // 세션 무효화 |
|
297 |
+ invalidateSession() { |
|
298 |
+ try { |
|
299 |
+ // 세션 무효화를 위한 더미 요청 (필요한 경우) |
|
300 |
+ fetch('/mbr/sessionInvalidate', { |
|
301 |
+ method: 'POST', |
|
302 |
+ credentials: 'include' |
|
303 |
+ }).catch(() => {}); // 실패해도 무시 |
|
304 |
+ |
|
305 |
+ } catch (error) { |
|
306 |
+ console.error('세션 무효화 중 오류:', error); |
|
307 |
+ } |
|
308 |
+ }, |
|
309 |
+ |
|
310 |
+ // 브라우저 캐시 정리 |
|
311 |
+ clearBrowserCache() { |
|
312 |
+ try { |
|
313 |
+ // 캐시 API 지원 확인 및 정리 |
|
314 |
+ if ('caches' in window) { |
|
315 |
+ caches.keys().then(cacheNames => { |
|
316 |
+ cacheNames.forEach(cacheName => { |
|
317 |
+ caches.delete(cacheName); |
|
318 |
+ }); |
|
319 |
+ }).catch(() => {}); |
|
320 |
+ } |
|
321 |
+ |
|
322 |
+ // IndexedDB 정리 (가능한 경우) |
|
323 |
+ if ('indexedDB' in window) { |
|
324 |
+ // 일반적인 DB 이름들 정리 시도 |
|
325 |
+ const commonDBNames = ['auth_db', 'user_db', 'session_db']; |
|
326 |
+ commonDBNames.forEach(dbName => { |
|
327 |
+ try { |
|
328 |
+ indexedDB.deleteDatabase(dbName); |
|
329 |
+ } catch (e) {} |
|
330 |
+ }); |
|
331 |
+ } |
|
332 |
+ |
|
333 |
+ } catch (error) { |
|
334 |
+ console.error('브라우저 캐시 정리 중 오류:', error); |
|
335 |
+ } |
|
336 |
+ }, |
|
337 |
+ |
|
201 | 338 |
setUserType({ commit }, userType) { |
202 | 339 |
commit("setUserType", userType); |
203 | 340 |
}, |
--- client/views/pages/login/Login.vue
+++ client/views/pages/login/Login.vue
... | ... | @@ -155,9 +155,7 @@ |
155 | 155 |
}, |
156 | 156 |
|
157 | 157 |
async loginSuccessProc(res) { |
158 |
- console.log('login success :: res? = ' , res); |
|
159 | 158 |
const loginType = res.headers['loginmode']; |
160 |
- console.log('loginType : ', loginType); |
|
161 | 159 |
if (loginType === 'J') { |
162 | 160 |
this.handleJWTLogin(res); |
163 | 161 |
} else if (loginType === 'S') { |
... | ... | @@ -269,7 +267,6 @@ |
269 | 267 |
await this.handleLoginSuccess(); |
270 | 268 |
|
271 | 269 |
} catch (error) { |
272 |
- console.error("OAuth2 처리 실패:", error); |
|
273 | 270 |
this.handleOAuthError('processing_error', error.message); |
274 | 271 |
} |
275 | 272 |
}, |
... | ... | @@ -287,8 +284,6 @@ |
287 | 284 |
}, |
288 | 285 |
|
289 | 286 |
async handleOAuthJWT() { |
290 |
- console.log("JWT 모드 OAuth2 처리 시작"); |
|
291 |
- |
|
292 | 287 |
try { |
293 | 288 |
const token = localStorage.getItem('authorization') |
294 | 289 |
|| this.getCookie('refresh') |
... | ... | @@ -344,7 +339,6 @@ |
344 | 339 |
this.setAuthInfo('S', null, { ...userInfo, roles }); |
345 | 340 |
|
346 | 341 |
} catch (error) { |
347 |
- console.error("세션 모드 처리 실패:", error); |
|
348 | 342 |
throw error; |
349 | 343 |
} |
350 | 344 |
}, |
Add a comment
Delete comment
Once you delete this comment, you won't be able to recover it. Are you sure you want to delete this comment?