diff --git a/src/main/java/com/eactive/apim/portal/common/breadcrumb/GlobalControllerAdvice.java b/src/main/java/com/eactive/apim/portal/common/breadcrumb/GlobalControllerAdvice.java index 3d353f5..7ae0475 100644 --- a/src/main/java/com/eactive/apim/portal/common/breadcrumb/GlobalControllerAdvice.java +++ b/src/main/java/com/eactive/apim/portal/common/breadcrumb/GlobalControllerAdvice.java @@ -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 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(); + } } diff --git a/src/main/java/com/eactive/apim/portal/common/security/ClientGuardService.java b/src/main/java/com/eactive/apim/portal/common/security/ClientGuardService.java new file mode 100644 index 0000000..892715b --- /dev/null +++ b/src/main/java/com/eactive/apim/portal/common/security/ClientGuardService.java @@ -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)에서 조회한다. + * + *

두 기능은 서로 독립된 property 로 on/off 한다. group 은 기존 {@code Portal} 을 재사용하여 + * {@link PortalPropertyService#getOrCreateProperty} 의 자동 생성이 동작하도록 한다.

+ * + * + */ +@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()); + } +} diff --git a/src/main/resources/static/favicon.png b/src/main/resources/static/favicon.png new file mode 100644 index 0000000..99941c9 Binary files /dev/null and b/src/main/resources/static/favicon.png differ diff --git a/src/main/resources/static/img/favicon/apple-touch-icon.png b/src/main/resources/static/img/favicon/apple-touch-icon.png new file mode 100644 index 0000000..9fdc76d Binary files /dev/null and b/src/main/resources/static/img/favicon/apple-touch-icon.png differ diff --git a/src/main/resources/static/img/favicon/favicon-16x16.png b/src/main/resources/static/img/favicon/favicon-16x16.png new file mode 100644 index 0000000..eca6bcb Binary files /dev/null and b/src/main/resources/static/img/favicon/favicon-16x16.png differ diff --git a/src/main/resources/static/img/favicon/favicon-180x180.png b/src/main/resources/static/img/favicon/favicon-180x180.png new file mode 100644 index 0000000..9fdc76d Binary files /dev/null and b/src/main/resources/static/img/favicon/favicon-180x180.png differ diff --git a/src/main/resources/static/img/favicon/favicon-192x192.png b/src/main/resources/static/img/favicon/favicon-192x192.png new file mode 100644 index 0000000..52a6efe Binary files /dev/null and b/src/main/resources/static/img/favicon/favicon-192x192.png differ diff --git a/src/main/resources/static/img/favicon/favicon-32x32.png b/src/main/resources/static/img/favicon/favicon-32x32.png new file mode 100644 index 0000000..99941c9 Binary files /dev/null and b/src/main/resources/static/img/favicon/favicon-32x32.png differ diff --git a/src/main/resources/static/img/favicon/favicon-48x48.png b/src/main/resources/static/img/favicon/favicon-48x48.png new file mode 100644 index 0000000..7a5c0b2 Binary files /dev/null and b/src/main/resources/static/img/favicon/favicon-48x48.png differ diff --git a/src/main/resources/static/img/favicon/favicon-512x512.png b/src/main/resources/static/img/favicon/favicon-512x512.png new file mode 100644 index 0000000..e255763 Binary files /dev/null and b/src/main/resources/static/img/favicon/favicon-512x512.png differ diff --git a/src/main/resources/static/img/favicon/favicon.png b/src/main/resources/static/img/favicon/favicon.png new file mode 100644 index 0000000..99941c9 Binary files /dev/null and b/src/main/resources/static/img/favicon/favicon.png differ diff --git a/src/main/resources/static/js/djb/client-guard.js b/src/main/resources/static/js/djb/client-guard.js new file mode 100644 index 0000000..eab6af6 --- /dev/null +++ b/src/main/resources/static/js/djb/client-guard.js @@ -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(); + } +})(); diff --git a/src/main/resources/templates/views/fragment/djbank/head.html b/src/main/resources/templates/views/fragment/djbank/head.html index 9b1e1d1..499ee69 100644 --- a/src/main/resources/templates/views/fragment/djbank/head.html +++ b/src/main/resources/templates/views/fragment/djbank/head.html @@ -7,6 +7,23 @@ + + + + + + + + + + + +