fix: 로그아웃 후 자동 재로그인 버그 수정

쿠키를 단일 진실 출처로 만들어 서브도메인 간 로그아웃 불일치 해결:
- login.html: logout=1 파라미터 시 localStorage+쿠키 전부 정리 후 토큰 체크 스킵
- 각 시스템 logout 함수에 &logout=1 추가 (6개 파일)
- 각 시스템 initAuth에 쿠키 우선 검증 추가 (7개 파일)

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
Hyungi Ahn
2026-03-13 09:00:19 +09:00
parent 3d6cedf667
commit 1006e8479e
10 changed files with 100 additions and 8 deletions

View File

@@ -201,8 +201,20 @@
}
}
// logout=1 파라미터가 있으면 모든 인증 데이터 정리
var isLogout = new URLSearchParams(location.search).get('logout') === '1';
if (isLogout) {
ssoCookie.remove('sso_token');
ssoCookie.remove('sso_user');
ssoCookie.remove('sso_refresh_token');
['sso_token','sso_user','sso_refresh_token','token','user','access_token',
'currentUser','current_user','userInfo','userPageAccess'].forEach(function(k) {
localStorage.removeItem(k);
});
}
// 이미 로그인 되어있으면 포털로 (쿠키 또는 localStorage 체크 + 만료 확인)
var existingToken = ssoCookie.get('sso_token') || localStorage.getItem('sso_token');
var existingToken = isLogout ? null : (ssoCookie.get('sso_token') || localStorage.getItem('sso_token'));
if (existingToken && existingToken !== 'undefined' && existingToken !== 'null') {
if (isTokenValid(existingToken)) {
// 쿠키 재설정 (localStorage에만 있고 쿠키가 없는 경우 대비)

View File

@@ -51,7 +51,7 @@
['sso_token','sso_user','sso_refresh_token','token','user','access_token','currentUser','current_user','userInfo','userPageAccess'].forEach(function(k) {
localStorage.removeItem(k);
});
window.location.href = this.getLoginUrl();
window.location.href = this.getLoginUrl(window.location.href) + '&logout=1';
},
/**

View File

@@ -154,7 +154,7 @@ if ('caches' in window) {
// 401 Unauthorized 처리
if (response.status === 401) {
window.clearSSOAuth();
window.location.href = window.getLoginUrl();
window.location.href = window.getLoginUrl() + '&logout=1';
throw new Error('인증이 만료되었습니다.');
}

View File

@@ -120,8 +120,24 @@ async function checkPageAccess(pageKey) {
}
}
// 쿠키 직접 읽기 (api-base.js의 cookieGet은 IIFE 내부 함수이므로 접근 불가)
function _authCookieGet(name) {
var match = document.cookie.match(new RegExp('(?:^|; )' + name + '=([^;]*)'));
return match ? decodeURIComponent(match[1]) : null;
}
// 즉시 실행 함수로 스코프를 보호하고 로직을 실행
(async function() {
// 쿠키 우선 검증: 쿠키 없고 localStorage에만 토큰이 있으면 정리
var cookieToken = _authCookieGet('sso_token');
var localToken = localStorage.getItem('sso_token');
if (!cookieToken && localToken) {
['sso_token','sso_user','sso_refresh_token','token','user','access_token',
'currentUser','current_user','userInfo','userPageAccess'].forEach(function(k) { localStorage.removeItem(k); });
window.location.href = window.getLoginUrl ? window.getLoginUrl() : '/login';
return;
}
if (!isLoggedIn()) {
clearAuthData(); // 만약을 위해 한번 더 정리
window.location.href = window.getLoginUrl ? window.getLoginUrl() : '/login';

View File

@@ -19,6 +19,12 @@
window.location.href = window.getLoginUrl ? window.getLoginUrl() : '/login';
}
// ===== 쿠키 직접 읽기 (api-base.js의 cookieGet은 IIFE 내부이므로) =====
function cookieGet(name) {
var match = document.cookie.match(new RegExp('(?:^|; )' + name + '=([^;]*)'));
return match ? decodeURIComponent(match[1]) : null;
}
// ===== 인증 함수 (api-base.js의 전역 헬퍼 활용) =====
function isLoggedIn() {
var token = window.getSSOToken ? window.getSSOToken() : localStorage.getItem('sso_token');
@@ -40,6 +46,16 @@
// ===== 메인 초기화 =====
async function init() {
// 쿠키 우선 검증: 쿠키 없고 localStorage에만 토큰이 있으면 정리
var cookieToken = cookieGet('sso_token');
var localToken = localStorage.getItem('sso_token');
if (!cookieToken && localToken) {
['sso_token','sso_user','sso_refresh_token','token','user','access_token',
'currentUser','current_user','userInfo','userPageAccess'].forEach(function(k) { localStorage.removeItem(k); });
safeRedirectToLogin();
return;
}
// 1. 인증 확인
if (!isLoggedIn()) {
clearAuthData();

View File

@@ -46,8 +46,17 @@ class App {
* 인증 확인
*/
async checkAuth() {
// 쿠키 우선 검증: 쿠키 없고 localStorage에만 토큰이 있으면 정리
const cookieToken = this._cookieGet('sso_token');
const localToken = localStorage.getItem('sso_token');
if (!cookieToken && localToken) {
['sso_token','sso_user','sso_refresh_token','token','user','access_token',
'currentUser','current_user','userInfo','userPageAccess'].forEach(k => localStorage.removeItem(k));
throw new Error('쿠키 없음 - 로그아웃 상태');
}
// SSO 쿠키 우선, localStorage 폴백
const token = this._cookieGet('sso_token') || localStorage.getItem('sso_token');
const token = cookieToken || localToken;
if (!token) {
throw new Error('토큰 없음');
}

View File

@@ -97,6 +97,15 @@ class AuthManager {
* 저장소에서 사용자 정보 복원 (SSO 쿠키 + localStorage)
*/
restoreUserFromStorage() {
// 쿠키 우선 검증: 쿠키 없고 localStorage에만 토큰이 있으면 정리
const cookieToken = this._cookieGet('sso_token');
const localToken = localStorage.getItem('sso_token');
if (!cookieToken && localToken) {
['sso_token','sso_user','sso_refresh_token','token','user','access_token',
'currentUser','current_user','userInfo','userPageAccess'].forEach(k => localStorage.removeItem(k));
return;
}
const token = this._getToken();
const user = this._getUser();
@@ -240,7 +249,7 @@ class AuthManager {
logout() {
this.clearAuth();
this.notifyListeners('logout');
window.location.href = this._getLoginUrl();
window.location.href = this._getLoginUrl() + '&logout=1';
}
/**

View File

@@ -79,7 +79,7 @@ function doLogout() {
if (!confirm('로그아웃?')) return;
_cookieRemove('sso_token'); _cookieRemove('sso_user'); _cookieRemove('sso_refresh_token');
['sso_token','sso_user','sso_refresh_token','token','user','access_token','currentUser','current_user','userInfo','userPageAccess'].forEach(k => localStorage.removeItem(k));
location.href = getLoginUrl();
location.href = getLoginUrl() + '&logout=1';
}
/* ===== Navbar ===== */
@@ -106,6 +106,16 @@ let currentUser = null;
/* ===== Init ===== */
function initAuth() {
// 쿠키 우선 검증: 쿠키 없고 localStorage에만 토큰이 있으면 정리
const cookieToken = _cookieGet('sso_token');
const localToken = localStorage.getItem('sso_token');
if (!cookieToken && localToken) {
['sso_token','sso_user','sso_refresh_token','token','user','access_token',
'currentUser','current_user','userInfo','userPageAccess'].forEach(k => localStorage.removeItem(k));
_safeRedirect();
return false;
}
const token = getToken();
if (!token) { _safeRedirect(); return false; }
const decoded = decodeToken(token);

View File

@@ -76,7 +76,7 @@ function doLogout() {
if (!confirm('로그아웃?')) return;
_cookieRemove('sso_token'); _cookieRemove('sso_user'); _cookieRemove('sso_refresh_token');
['sso_token','sso_user','sso_refresh_token','token','user','access_token','currentUser','current_user','userInfo','userPageAccess'].forEach(k => localStorage.removeItem(k));
location.href = getLoginUrl();
location.href = getLoginUrl() + '&logout=1';
}
/* ===== Navbar ===== */
@@ -100,6 +100,16 @@ let currentUser = null;
/* ===== Init ===== */
function initAuth() {
// 쿠키 우선 검증: 쿠키 없고 localStorage에만 토큰이 있으면 정리
const cookieToken = _cookieGet('sso_token');
const localToken = localStorage.getItem('sso_token');
if (!cookieToken && localToken) {
['sso_token','sso_user','sso_refresh_token','token','user','access_token',
'currentUser','current_user','userInfo','userPageAccess'].forEach(k => localStorage.removeItem(k));
_safeRedirect();
return false;
}
const token = getToken();
if (!token) { _safeRedirect(); return false; }
const decoded = decodeToken(token);

View File

@@ -75,7 +75,7 @@ function doLogout() {
if (!confirm('로그아웃?')) return;
_cookieRemove('sso_token'); _cookieRemove('sso_user'); _cookieRemove('sso_refresh_token');
['sso_token','sso_user','sso_refresh_token','token','user','access_token','currentUser','current_user','userInfo','userPageAccess'].forEach(k => localStorage.removeItem(k));
location.href = getLoginUrl();
location.href = getLoginUrl() + '&logout=1';
}
/* ===== State ===== */
@@ -83,6 +83,16 @@ let currentUser = null;
/* ===== Init ===== */
async function init() {
// 쿠키 우선 검증: 쿠키 없고 localStorage에만 토큰이 있으면 정리
const cookieToken = _cookieGet('sso_token');
const localToken = localStorage.getItem('sso_token');
if (!cookieToken && localToken) {
['sso_token','sso_user','sso_refresh_token','token','user','access_token',
'currentUser','current_user','userInfo','userPageAccess'].forEach(k => localStorage.removeItem(k));
_safeRedirect();
return;
}
const token = getToken();
if (!token) { _safeRedirect(); return; }
const decoded = decodeToken(token);