클라이언트 하드닝 기능 추가:
- DevTools 감지 및 화면 삭제, 우클릭 차단 기능 구현 - PortalProperty 기반 설정 관리 서비스 추가 - 클라이언트 가드 스크립트 및 토글 로직 템플릿 연동
@@ -8,6 +8,7 @@ import org.springframework.web.bind.annotation.ControllerAdvice;
|
||||
import org.springframework.web.bind.annotation.ModelAttribute;
|
||||
import com.eactive.apim.portal.config.PortalProperties;
|
||||
import com.eactive.apim.portal.apps.session.service.UserSessionService;
|
||||
import com.eactive.apim.portal.common.security.ClientGuardService;
|
||||
|
||||
@ControllerAdvice
|
||||
public class GlobalControllerAdvice {
|
||||
@@ -21,6 +22,9 @@ public class GlobalControllerAdvice {
|
||||
@Autowired
|
||||
private UserSessionService userSessionService;
|
||||
|
||||
@Autowired
|
||||
private ClientGuardService clientGuardService;
|
||||
|
||||
@ModelAttribute("breadcrumb")
|
||||
public List<Map> addBreadcrumbToModel(HttpServletRequest request) {
|
||||
String currentPath = request.getRequestURI();
|
||||
@@ -55,4 +59,22 @@ public class GlobalControllerAdvice {
|
||||
public int sessionTimeoutMinutes() {
|
||||
return userSessionService.getSessionTimeoutMinutes();
|
||||
}
|
||||
|
||||
/**
|
||||
* 클라이언트 가드 - 우클릭(contextmenu) 차단 활성화 여부.
|
||||
* PortalProperty(Portal/djb.client-guard.contextmenu.enabled)에서 조회.
|
||||
*/
|
||||
@ModelAttribute("clientGuardContextmenu")
|
||||
public boolean clientGuardContextmenu() {
|
||||
return clientGuardService.isContextmenuBlockEnabled();
|
||||
}
|
||||
|
||||
/**
|
||||
* 클라이언트 가드 - DevTools 감지 시 화면 삭제 활성화 여부.
|
||||
* PortalProperty(Portal/djb.client-guard.devtools.enabled)에서 조회.
|
||||
*/
|
||||
@ModelAttribute("clientGuardDevtools")
|
||||
public boolean clientGuardDevtools() {
|
||||
return clientGuardService.isDevtoolsGuardEnabled();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,42 @@
|
||||
package com.eactive.apim.portal.common.security;
|
||||
|
||||
import com.eactive.apim.portal.portalproperty.service.PortalPropertyService;
|
||||
import lombok.RequiredArgsConstructor;
|
||||
import org.springframework.stereotype.Service;
|
||||
|
||||
/**
|
||||
* 클라이언트측 하드닝(우클릭 차단 · DevTools 감지) 토글을 DB(PortalProperty)에서 조회한다.
|
||||
*
|
||||
* <p>두 기능은 서로 독립된 property 로 on/off 한다. group 은 기존 {@code Portal} 을 재사용하여
|
||||
* {@link PortalPropertyService#getOrCreateProperty} 의 자동 생성이 동작하도록 한다.</p>
|
||||
*
|
||||
* <ul>
|
||||
* <li>{@code djb.client-guard.contextmenu.enabled} - 우클릭(contextmenu) 차단</li>
|
||||
* <li>{@code djb.client-guard.devtools.enabled} - DevTools 감지 시 화면 삭제</li>
|
||||
* </ul>
|
||||
*/
|
||||
@Service
|
||||
@RequiredArgsConstructor
|
||||
public class ClientGuardService {
|
||||
|
||||
private static final String GROUP = "Portal";
|
||||
private static final String NAME_CONTEXTMENU = "djb.client-guard.contextmenu.enabled";
|
||||
private static final String NAME_DEVTOOLS = "djb.client-guard.devtools.enabled";
|
||||
|
||||
private final PortalPropertyService portalPropertyService;
|
||||
|
||||
/** 우클릭(contextmenu) 차단 활성화 여부 */
|
||||
public boolean isContextmenuBlockEnabled() {
|
||||
return read(NAME_CONTEXTMENU, "우클릭(contextmenu) 차단");
|
||||
}
|
||||
|
||||
/** DevTools 감지 시 화면 삭제 활성화 여부 */
|
||||
public boolean isDevtoolsGuardEnabled() {
|
||||
return read(NAME_DEVTOOLS, "DevTools 감지 시 화면 삭제");
|
||||
}
|
||||
|
||||
private boolean read(String name, String desc) {
|
||||
return Boolean.parseBoolean(
|
||||
portalPropertyService.getOrCreateProperty(GROUP, name, "false", desc).trim());
|
||||
}
|
||||
}
|
||||
|
After Width: | Height: | Size: 1.6 KiB |
|
After Width: | Height: | Size: 6.8 KiB |
|
After Width: | Height: | Size: 1.1 KiB |
|
After Width: | Height: | Size: 6.8 KiB |
|
After Width: | Height: | Size: 7.3 KiB |
|
After Width: | Height: | Size: 1.6 KiB |
|
After Width: | Height: | Size: 2.1 KiB |
|
After Width: | Height: | Size: 26 KiB |
|
After Width: | Height: | Size: 1.6 KiB |
@@ -0,0 +1,60 @@
|
||||
/**
|
||||
* client-guard.js
|
||||
*
|
||||
* 클라이언트측 하드닝. 서버가 내려준 window.__CLIENT_GUARD__ 플래그에 따라 각 기능을 독립 적용한다.
|
||||
* - contextmenu: 우클릭(컨텍스트 메뉴) 차단
|
||||
* - devtools : 창 크기 휴리스틱으로 DevTools 열림 감지 시 화면 전체 요소 영구 삭제
|
||||
*
|
||||
* 주의(한계): 창 크기 휴리스틱은 오탐/미탐이 있다(줌·북마크바·사이드패널·좁은창=오탐, undock 창=미탐).
|
||||
* JS 비활성화·breakpoint·DOM 편집으로 우회 가능하므로 실질 보안이 아니라 억제용 데코이다.
|
||||
*/
|
||||
(function () {
|
||||
'use strict';
|
||||
|
||||
var cfg = window.__CLIENT_GUARD__ || {};
|
||||
|
||||
// ── 우클릭(contextmenu) 차단 ─────────────────────────────────────────
|
||||
if (cfg.contextmenu) {
|
||||
document.addEventListener('contextmenu', function (e) {
|
||||
e.preventDefault();
|
||||
}, true);
|
||||
}
|
||||
|
||||
// ── DevTools 감지 → 화면 영구 삭제 ───────────────────────────────────
|
||||
if (cfg.devtools) {
|
||||
var THRESHOLD = 160; // 도킹된 DevTools 패널이 차지하는 최소 폭/높이(px) 추정값
|
||||
var POLL_MS = 500;
|
||||
var wiped = false;
|
||||
|
||||
function isDevtoolsOpen() {
|
||||
var widthGap = window.outerWidth - window.innerWidth;
|
||||
var heightGap = window.outerHeight - window.innerHeight;
|
||||
return widthGap > THRESHOLD || heightGap > THRESHOLD;
|
||||
}
|
||||
|
||||
function wipe() {
|
||||
if (wiped) {
|
||||
return;
|
||||
}
|
||||
wiped = true;
|
||||
// 화면 전체 요소 영구 삭제 (닫아도 복구되지 않음, 새로고침 필요)
|
||||
try {
|
||||
document.documentElement.replaceChildren();
|
||||
} catch (ignore) {
|
||||
if (document.body) {
|
||||
document.body.innerHTML = '';
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
function check() {
|
||||
if (!wiped && isDevtoolsOpen()) {
|
||||
wipe();
|
||||
}
|
||||
}
|
||||
|
||||
window.setInterval(check, POLL_MS);
|
||||
window.addEventListener('resize', check);
|
||||
check();
|
||||
}
|
||||
})();
|
||||
@@ -7,6 +7,23 @@
|
||||
<meta content="https://www.eactive.co.kr/" property="og:url"/>
|
||||
<meta charset="UTF-8"/>
|
||||
|
||||
<!-- Favicon (DJB 로고) -->
|
||||
<link rel="icon" type="image/png" sizes="32x32" th:href="@{/img/favicon/favicon-32x32.png}"/>
|
||||
<link rel="icon" type="image/png" sizes="16x16" th:href="@{/img/favicon/favicon-16x16.png}"/>
|
||||
<link rel="shortcut icon" type="image/png" th:href="@{/img/favicon/favicon.png}"/>
|
||||
<link rel="apple-touch-icon" sizes="180x180" th:href="@{/img/favicon/apple-touch-icon.png}"/>
|
||||
|
||||
<!-- 클라이언트 가드 (우클릭 차단 · DevTools 감지). PortalProperty 토글이 하나라도 켜지면 로드 -->
|
||||
<th:block th:if="${clientGuardContextmenu or clientGuardDevtools}">
|
||||
<script th:inline="javascript">
|
||||
window.__CLIENT_GUARD__ = {
|
||||
contextmenu: /*[[${clientGuardContextmenu}]]*/ false,
|
||||
devtools: /*[[${clientGuardDevtools}]]*/ false
|
||||
};
|
||||
</script>
|
||||
<script th:src="@{/js/djb/client-guard.js}"></script>
|
||||
</th:block>
|
||||
|
||||
<!-- CSRF 토큰 (세션 기반). 정적 JS/AJAX에서 토큰·헤더명을 읽어 사용한다. -->
|
||||
<meta name="_csrf" th:content="${_csrf != null ? _csrf.token : ''}"/>
|
||||
<meta name="_csrf_header" th:content="${_csrf != null ? _csrf.headerName : 'X-XSRF-TOKEN'}"/>
|
||||
|
||||