세션종료 처리 스크립트 함수로 전체 통일(백엔드 포함)

창닫기 함수 보완
dev
박성영 3 months ago
parent 3eb6c17330
commit d19c88d10d

@ -378,27 +378,36 @@ public class AuthInterceptor implements HandlerInterceptor {
/**
*
*
*
* @param request HTTP
* @param response HTTP
* @param message
* @throws IOException
*/
private void redirectToLoginPageWithPopupCheck(HttpServletRequest request, HttpServletResponse response,
private void redirectToLoginPageWithPopupCheck(HttpServletRequest request, HttpServletResponse response,
String message) throws IOException {
response.setContentType("text/html; charset=UTF-8");
response.getWriter().write("<script type='text/javascript'>");
response.getWriter().write("alert('" + message + "');");
response.getWriter().write("if(window.opener) {");
response.getWriter().write(" var topWindow = window;");
response.getWriter().write(" while(topWindow.opener && !topWindow.opener.closed) {");
response.getWriter().write(" topWindow = topWindow.opener;");
response.getWriter().write(" }");
response.getWriter().write(" topWindow.location.href = '" + request.getContextPath() + loginProperties.getUrl() + "';");
response.getWriter().write(" window.close();");
response.getWriter().write("} else {");
response.getWriter().write(" location.href = '" + request.getContextPath() + loginProperties.getUrl() + "';");
response.getWriter().write("}");
response.getWriter().write("</script>");
try {
// 메시지를 request attribute로 설정
request.setAttribute("errorMessage", message);
// 401.jsp로 forward하여 handleSessionExpired() 함수 호출
request.getRequestDispatcher("/WEB-INF/views/error/401.jsp").forward(request, response);
} catch (Exception e) {
log.error("401 페이지 forward 실패, 기존 방식으로 처리", e);
// forward 실패 시 기존 방식으로 fallback
response.setContentType("text/html; charset=UTF-8");
response.getWriter().write("<script type='text/javascript'>");
response.getWriter().write("alert('" + message + "');");
response.getWriter().write("if(window.opener) {");
response.getWriter().write(" var topWindow = window;");
response.getWriter().write(" while(topWindow.opener && !topWindow.opener.closed) {");
response.getWriter().write(" topWindow = topWindow.opener;");
response.getWriter().write(" }");
response.getWriter().write(" topWindow.location.href = '" + request.getContextPath() + loginProperties.getUrl() + "';");
response.getWriter().write(" window.close();");
response.getWriter().write("} else {");
response.getWriter().write(" location.href = '" + request.getContextPath() + loginProperties.getUrl() + "';");
response.getWriter().write("}");
response.getWriter().write("</script>");
}
}
}

@ -0,0 +1,48 @@
<%@ page import="egovframework.util.SessionUtil" %>
<%@ page import="javax.websocket.Session" %>
<%@ page language="java" contentType="text/html; charset=UTF-8" pageEncoding="UTF-8"%>
<%@ taglib prefix="c" uri="http://java.sun.com/jsp/jstl/core" %>
<%@ taglib prefix="tiles" uri="http://tiles.apache.org/tags-tiles" %>
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
<html lang="ko">
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, user-scalable=no">
<!-- Cache -->
<meta http-equiv="Cache-Control" content="no-cache, no-store, must-revalidate">
<meta http-equiv="Expires" content="0">
<meta http-equiv="Pragma" content="no-cache">
<%
response.setHeader("Cache-Control","no-store");
response.setHeader("Pragma","no-cache");
response.setDateHeader("Expires",0);
if (request.getProtocol().equals("HTTP/1.1"))
response.setHeader("Cache-Control", "no-cache");
%>
<title>불법건축물관리</title>
<%--<link rel="shortcut icon" type="image/x-icon" href="<c:url value='/img/common/favicon.ico' />">--%>
<%--================== Main Scripts ======================--%>
<script type="text/javascript" src="<c:url value='/js/jquery.min.js' />"></script>
<script type="text/javascript" src="<c:url value='/js/moment.js' />"></script>
<%-- XIT CUSTOM --%>
<link rel="stylesheet" type="text/css" href="<c:url value='/xit/xit-common.css' />">
<link rel="stylesheet" type="text/css" href="<c:url value='/xit/xit-tui-grid.css' />">
<script type="text/javascript" src="<c:url value='/xit/common_util.js' />"></script>
<script type="text/javascript" src="<c:url value='/xit/xit-tui-grid.js' />"></script>
<script type="text/javascript" src="<c:url value='/xit/xit-common.js' />"></script>
<script type="text/javascript" src="<c:url value='/xit/xit-validation.js' />"></script>
</head>
<script type="text/javascript">
$(document).ready(function() {
//세션 종료
handleSessionExpired();
});
</script>
<body>
</body>
</html>

@ -1,6 +1,6 @@
/* =========================================================================
* XIT Common JavaScript Library
* 중요도 순서: 1.환경설정 2.세션/보안 3.Ajax통신 4.공통유틸 5.UI컴포넌트
* 중요도 순서: 1.환경설정 3.Ajax통신 4.공통유틸 5.UI컴포넌트
* ========================================================================= */
/* =============================================================================
@ -51,251 +51,83 @@ var loginUrl = '/login/login.do';
})();
/* =============================================================================
* 2. SESSION & SECURITY MANAGEMENT (세션 보안 관리)
* 3. AJAX COMMUNICATION (Ajax 통신 관리)
* ============================================================================= */
/**
* 세션 만료 처리 함수 - 모든 팝업을 닫고 로그인 페이지로 이동
* 중요로직: 팝업 환경에서 세션 만료 현재 팝업이 아닌 최상위 창에서 로그인 페이지로 이동
*/
function handleSessionExpired() {
try {
console.log('[세션 만료] 처리 시작');
// 중요로직: 현재 창이 팝업인지 확인
var isPopup = !!(window.opener && !window.opener.closed);
console.log('[세션 만료] 현재 창 팝업 여부:', isPopup);
if (isPopup) {
// 팝업인 경우: 최상위 부모창 찾기
var topWindow = window;
var windowChain = [];
var currentWindow = window;
// 최상위 창까지 탐색
while (currentWindow && currentWindow.opener && !currentWindow.opener.closed) {
windowChain.push(currentWindow);
currentWindow = currentWindow.opener;
topWindow = currentWindow;
// 무한루프 방지 (최대 10단계)
if (windowChain.length > 10) {
console.warn('[세션 만료] 너무 많은 팝업 체인, 탐색 중단');
break;
}
}
console.log('[세션 만료] 팝업 체인 길이:', windowChain.length);
console.log('[세션 만료] 최상위 창 찾음:', !!topWindow);
// 메시지 표시 (현재 팝업에서)
alert("세션이 종료되었습니다. 다시 로그인해주세요.");
// 최상위 창을 로그인 페이지로 이동
if (topWindow && !topWindow.closed) {
try {
topWindow.location.href = (contextPath || '') + loginUrl;
console.log('[세션 만료] 최상위 창 리다이렉트 성공');
} catch (redirectError) {
console.warn('[세션 만료] 최상위 창 리다이렉트 실패:', redirectError);
// 리다이렉트 실패 시 현재 창에서 이동
location.href = (contextPath || '') + loginUrl;
}
} else {
// 최상위 창이 닫혀있는 경우 현재 창에서 이동
location.href = (contextPath || '') + loginUrl;
console.log('[세션 만료] 현재 창에서 리다이렉트');
}
// 모든 팝업창 닫기 (약간의 지연 후)
setTimeout(function() {
try {
// 하위 팝업부터 상위 팝업 순서로 닫기
for (var i = 0; i < windowChain.length; i++) {
try {
var popupWindow = windowChain[i];
if (popupWindow && !popupWindow.closed) {
popupWindow.close();
console.log('[세션 만료] 팝업창 닫기 성공:', i + 1);
}
} catch (closeError) {
console.warn('[세션 만료] 팝업창 닫기 실패:', closeError);
}
}
console.log('[세션 만료] 모든 팝업창 닫기 완료');
} catch (e) {
console.warn('[세션 만료] 팝업창 일괄 닫기 실패:', e);
}
}, 300); // 리다이렉트 후 약간의 지연
/* jQuery Ajax 설정 */
$.ajaxSetup({
type:"POST",
dataType : "JSON",
cache: false
});
/* Ajax Error 설정 */
$(document).ajaxError( function( event, jqxhr, settings, exception ){
if( jqxhr.responseJSON ){
// 세션 만료인 경우 처리
if(jqxhr.responseJSON.errorCode === "SESSION_EXPIRED") {
handleSessionExpired()
} else if(jqxhr.responseJSON.errorCode === "MessageException") {
alert(jqxhr.responseJSON.message);
} else if(jqxhr.responseJSON.errorCode === "MESSAGE_EXCEPTION") {
alert(jqxhr.responseJSON.message);
} else {
// 일반 페이지인 경우 기존 로직 사용
console.log('[세션 만료] 일반 페이지에서 처리');
redirectToLoginWithCloseAllPopups("세션이 종료되었습니다. 다시 로그인해주세요.");
}
} catch (e) {
console.error('[세션 만료] 전체 처리 중 오류:', e);
// 모든 처리가 실패한 경우 기본 처리
try {
alert("세션이 종료되었습니다. 다시 로그인해주세요.");
location.href = (contextPath || '') + loginUrl;
} catch (finalError) {
console.error('[세션 만료] 최종 처리도 실패:', finalError);
location.reload();
alert("에러가 발생했습니다.\n\nERROR CODE : "+jqxhr.responseJSON.errorCode+"\nMESSAGE : "+jqxhr.responseJSON.message);
}
}
}
});
function handleSessionExpired() {
alert("세션이 종료되었습니다. 로그인 페이지로 이동합니다.");
/**
* 모든 팝업을 닫고 메인페이지를 로그인 페이지로 이동시키는 함수
* 중요로직: 다중 팝업 구조에서 안전하게 모든 팝업을 닫고 최상위 창만 로그인 페이지로 이동
* @param {string} message - 사용자에게 표시할 메시지 (선택사항)
* @param {boolean} showAlert - alert 표시 여부 (기본값: true)
*/
function redirectToLoginWithCloseAllPopups(message, showAlert) {
try {
// 기본값 설정
if (showAlert === undefined) showAlert = true;
if (!message) message = "로그인 페이지로 이동합니다.";
// 최상위 부모창 찾기
var topWindow = window;
while (topWindow.opener && !topWindow.opener.closed) {
topWindow = topWindow.opener;
}
// 메시지 표시
if (showAlert) {
alert(message);
// 최상위 부모는 로그인 페이지로 이동
if (topWindow && !topWindow.closed) {
topWindow.location.href = loginUrl;
}
// 중요로직: 최상위 부모창 찾기 (다중 팝업 처리)
var topWindow = window;
var currentWindow = window;
// 현재 창부터 최상위까지의 모든 창 정보 수집
var windowChain = [];
while (currentWindow) {
windowChain.push({
window: currentWindow,
isPopup: !!(currentWindow.opener && !currentWindow.opener.closed)
});
// 🔹 열린 순서대로 닫기: 최상위부터 내려오면서 닫음
var stack = [];
var current = window;
if (currentWindow.opener && !currentWindow.opener.closed) {
currentWindow = currentWindow.opener;
topWindow = currentWindow;
} else {
break;
}
// opener 체인을 최상위까지 쭉 담기
while (current.opener && !current.opener.closed) {
stack.unshift(current.opener); // 부모를 앞쪽에 넣음
current = current.opener;
}
console.log('[로그인 리다이렉트] 창 체인 정보:', windowChain.length + '개의 창 감지');
// 최상위 부모창을 로그인 페이지로 이동
if (topWindow && !topWindow.closed) {
// stack = [A, B, ...] 순서대로 들어있음
stack.forEach(function(win) {
try {
topWindow.location.href = (contextPath || '') + loginUrl;
console.log('[로그인 리다이렉트] 최상위 창 리다이렉트 성공');
} catch (redirectError) {
console.warn('[로그인 리다이렉트] 최상위 창 리다이렉트 실패:', redirectError);
// 리다이렉트 실패 시 현재 창에서 이동
location.href = (contextPath || '') + loginUrl;
if (win && !win.closed) {
win.close();
}
} catch (e) {
console.warn("창 닫기 실패:", e);
}
} else {
// 최상위 창이 닫혀있는 경우 현재 창에서 이동
location.href = (contextPath || '') + loginUrl;
console.log('[로그인 리다이렉트] 현재 창에서 리다이렉트');
}
});
// 중요로직: 메인창 외에 모든 팝업창 종료
// 마지막에 현재 창 닫기
setTimeout(function() {
try {
var popupsToClose = windowChain.filter(function(item) {
return item.isPopup;
});
console.log('[로그인 리다이렉트] 닫을 팝업 수:', popupsToClose.length);
// 수집된 모든 팝업창을 닫기 (하위창부터 상위창 순서)
for (var i = 0; i < popupsToClose.length; i++) {
try {
var popupWindow = popupsToClose[i].window;
if (popupWindow && !popupWindow.closed) {
popupWindow.close();
console.log('[로그인 리다이렉트] 팝업창 닫기 성공:', i + 1);
}
} catch (closeError) {
console.warn('[로그인 리다이렉트] 팝업창 닫기 실패:', closeError);
}
}
console.log('[로그인 리다이렉트] 모든 팝업창 닫기 완료');
window.close();
} catch (e) {
console.warn('[로그인 리다이렉트] 팝업창 일괄 닫기 실패:', e);
// 기본 방식으로 현재 창 닫기 시도
if (window.opener && !window.opener.closed) {
try {
window.close();
} catch (fallbackError) {
console.warn('[로그인 리다이렉트] 기본 방식 팝업창 닫기도 실패:', fallbackError);
}
}
console.warn("현재 창 닫기 실패:", e);
}
}, 200); // 리다이렉트 후 약간의 지연을 두어 안정성 확보
}, 100);
} catch (e) {
console.error("[로그인 리다이렉트] 전체 처리 중 오류:", e);
// 모든 처리가 실패한 경우 현재 창에서 로그인 페이지로 이동
try {
location.href = (contextPath || '') + loginUrl;
} catch (finalError) {
console.error("[로그인 리다이렉트] 최종 리다이렉트도 실패:", finalError);
// 최후의 수단으로 새로고침
location.reload();
}
console.error("세션 만료 처리 중 오류:", e);
location.href = loginUrl; // fallback
}
}
/**
* 로그아웃 함수 - 모든 팝업을 닫고 로그인 페이지로 이동
*/
function logout() {
redirectToLoginWithCloseAllPopups("로그아웃 되었습니다.");
}
/* =============================================================================
* 3. AJAX COMMUNICATION (Ajax 통신 관리)
* ============================================================================= */
/* jQuery Ajax 설정 */
$.ajaxSetup({
type:"POST",
dataType : "JSON",
cache: false
});
/* Ajax Error 설정 */
$(document).ajaxError( function( event, jqxhr, settings, exception ){
if( jqxhr.responseJSON ){
// 세션 만료인 경우 처리
if(jqxhr.responseJSON.errorCode === "SESSION_EXPIRED") {
console.log('[Ajax Error] 세션 만료 감지, URL:', settings.url);
// 중요로직: 세션 만료 시 안전한 처리를 위해 약간의 지연 후 처리
setTimeout(function() {
handleSessionExpired();
}, 100);
} else if(jqxhr.responseJSON.errorCode === "MessageException") {
alert(jqxhr.responseJSON.message);
} else if(jqxhr.responseJSON.errorCode === "MESSAGE") {
alert(jqxhr.responseJSON.message);
} else {
console.error(jqxhr);
alert("에러가 발생했습니다.\n\nERROR CODE : "+jqxhr.responseJSON.errorCode+"\nMESSAGE : "+jqxhr.responseJSON.message);
}
} else if(jqxhr.status === 401) {
// HTTP 401 Unauthorized 응답도 세션 만료로 처리
console.log('[Ajax Error] HTTP 401 감지, 세션 만료로 처리, URL:', settings.url);
setTimeout(function() {
handleSessionExpired();
}, 100);
}
});
/* Ajax Progress Block UI 설정 */
// Ajax 활성 요청 카운터 (동시 Ajax 요청 처리를 위함)
var activeAjaxCount = 0;

@ -228,27 +228,7 @@ var XitTuiGridConfig = function(){
var responseObj = JSON.parse(ev.xhr.response);
if(responseObj.errorCode === "SESSION_EXPIRED") {
// 팝업창인지 확인
if (window.opener) {
// 팝업창인 경우 세션 만료 메시지 표시
alert("세션이 종료되었습니다. 로그인 페이지로 이동합니다.");
// 최상위 부모창 찾기 (다중 팝업 처리)
var topWindow = window;
while (topWindow.opener && !topWindow.opener.closed) {
topWindow = topWindow.opener;
}
// 최상위 부모창을 로그인 페이지로 이동
topWindow.location.href = loginUrl;
// 현재 팝업창 닫기
window.close();
} else {
// 일반 페이지인 경우 메시지 표시 후 로그인 페이지로 이동
alert(responseObj.message);
location.href = loginUrl;
}
handleSessionExpired();
} else if(responseObj.errorCode === "MessageException") {
alert(responseObj.message);
} else if(responseObj.errorCode === "MESSAGE_EXCEPTION") {

Loading…
Cancel
Save