From 4413c5aaeab6f35a467e0cad39eaf57fe120fb92 Mon Sep 17 00:00:00 2001 From: infraurp Date: Mon, 10 Aug 2026 16:40:46 +0900 Subject: [PATCH] =?UTF-8?q?=EC=9E=A5=EA=B8=B0=20=EB=AF=B8=EC=82=AC?= =?UTF-8?q?=EC=9A=A9=20=EA=B3=84=EC=A0=95=20=ED=8C=8C=EA=B8=B0=20=EB=B0=B0?= =?UTF-8?q?=EC=B9=98=EB=A5=BC=20=EA=B4=80=EB=A6=AC=EC=9E=90(EMP)=20?= =?UTF-8?q?=EA=B3=84=EC=A0=95=20=ED=95=9C=EC=A0=95=EC=9C=BC=EB=A1=9C=20?= =?UTF-8?q?=EB=B3=80=EA=B2=BD?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 일반회원(GNR)은 탈퇴 즉시 하드 DELETE되어 휴면 전환 기반 지연 파기 대상이 아니었으므로 배치에서 제외. 관리자(개인정보취급자) 계정은 SFR-002 핵심요건 (개인정보보호위 점검 지적사항 — 휴면 대상이 일반회원에 한정되고 개인정보 취급자가 미포함)이라 로직은 그대로 유지하고 대상만 EMP로 좁혔다. 정책 화면(계정 관리 정책) 안내 문구도 관리자 계정 대상임을 명시. 스키마 변경 없음(ACCOUNT_MNG_POLICY.DELETE_PRESERVE_DAY/PURGE_USE_YN, ACCOUNT_DELETE_HISTORY 재사용). Co-Authored-By: Claude Sonnet 5 --- .../cmm/privacy/account/AccountDeleteDAO.java | 18 ++----- .../account/AccountDeleteServiceImpl.java | 33 ++++++------- .../account/AccountDeleteTargetVO.java | 2 +- .../privacy/account/AccountMngPolicyVO.java | 4 +- .../kr/or/arko/cmm/batch/BatchScheduler.java | 16 +++++-- .../PrivacyAccountDelete_SQL_Mysql.xml | 47 ++----------------- .../PrivacyAccountDelete_SQL_Oracle.xml | 41 ++-------------- .../jsp/site/mnt/sec/accountPolicy/policy.jsp | 12 +++-- 8 files changed, 48 insertions(+), 125 deletions(-) diff --git a/src/main/java/egovframework/com/cmm/privacy/account/AccountDeleteDAO.java b/src/main/java/egovframework/com/cmm/privacy/account/AccountDeleteDAO.java index 9145e4a..08060d7 100644 --- a/src/main/java/egovframework/com/cmm/privacy/account/AccountDeleteDAO.java +++ b/src/main/java/egovframework/com/cmm/privacy/account/AccountDeleteDAO.java @@ -9,30 +9,22 @@ import egovframework.com.cmm.service.impl.EgovComAbstractDAO; /** * 장기 미사용 계정 개인정보 파기 DAO (SFR-002 / 개발항목 #12). * + *

대상은 개인정보취급자(관리자, EMP) 계정 한정 — 일반회원(GNR)은 탈퇴 즉시 하드 DELETE + * 되어 이 배치의 대상이 아니다(2026-08 정리).

+ * *

statement 는 {@code PrivacyAccountDelete_SQL_Mysql.xml}(namespace {@code AccountDeleteDAO}), * sql-map-config 등록. 보존기간은 SQL 내부에서 ACCOUNT_MNG_POLICY.DELETE_PRESERVE_DAY 를 - * 직접 조회하므로 자바 측 하드코딩이 없다. 1회 처리건수 상한(200)도 SQL 측에 있다.

+ * 직접 조회하므로 자바 측 하드코딩이 없다. 1회 처리건수 상한(1,000)도 SQL 측에 있다.

*/ @Repository("accountDeleteDAO") public class AccountDeleteDAO extends EgovComAbstractDAO { - /** 파기 대상(일반회원) — 휴면확정 후 보존기간 경과분, 최대 200건. */ - @SuppressWarnings("unchecked") - public List selectExpiredGnrAccounts() { - return list("AccountDeleteDAO.selectExpiredGnrAccounts", null); - } - - /** 파기 대상(개인정보취급자) — 휴면확정 후 보존기간 경과분, 최대 200건. */ + /** 파기 대상(개인정보취급자) — 휴면확정 후 보존기간 경과분, 최대 1,000건. */ @SuppressWarnings("unchecked") public List selectExpiredEmpAccounts() { return list("AccountDeleteDAO.selectExpiredEmpAccounts", null); } - /** 일반회원 개인정보 파기(마스킹 + MBER_STTUS='D'). */ - public int anonymizeGnrMember(String mberId) { - return update("AccountDeleteDAO.anonymizeGnrMember", mberId); - } - /** 개인정보취급자 개인정보 파기(마스킹 + EMPLYR_STTUS_CODE='D'). */ public int anonymizeEmplyr(String emplyrId) { return update("AccountDeleteDAO.anonymizeEmplyr", emplyrId); diff --git a/src/main/java/egovframework/com/cmm/privacy/account/AccountDeleteServiceImpl.java b/src/main/java/egovframework/com/cmm/privacy/account/AccountDeleteServiceImpl.java index 0eef632..e693647 100644 --- a/src/main/java/egovframework/com/cmm/privacy/account/AccountDeleteServiceImpl.java +++ b/src/main/java/egovframework/com/cmm/privacy/account/AccountDeleteServiceImpl.java @@ -11,15 +11,19 @@ import org.springframework.stereotype.Service; /** * 장기 미사용 계정 개인정보 파기 서비스 구현 (SFR-002 / 개발항목 #12). * + *

대상은 개인정보취급자(관리자, EMP) 계정 한정이다. 일반회원(GNR)은 탈퇴 신청 즉시 + * {@code EgovMberManageServiceImpl.deleteMber()} 경로에서 하드 DELETE 되어 보존기간을 두고 + * 지연 파기할 대상 자체가 없으므로(2026-08 정리), 이 배치의 GNR 처리 경로를 제거했다.

+ * *

보존기간은 코드에 상수로 두지 않고 {@code ACCOUNT_MNG_POLICY.DELETE_PRESERVE_DAY} * 를 SQL 실행 시점에 조회해 적용한다(정책 화면 #13에서 변경 시 즉시 반영).

* *

파기 방식은 행 DELETE 가 아니라 PII 컬럼 마스킹 + 상태코드 'D' 전환이다. - * 게시글·활동확인·마일리지 등 다수 테이블이 MBER_ID/EMPLYR_ID 를 FK 없이 참조하고 있어 - * 행을 지우면 해당 이력이 고아가 되기 때문이며, 기존 회원탈퇴 컨벤션과도 동일하다.

+ * 게시글·활동확인·마일리지 등 다수 테이블이 EMPLYR_ID 를 FK 없이 참조하고 있어 + * 행을 지우면 해당 이력이 고아가 되기 때문이다.

* *

처리량 상한: 배치가 장기간 정지했다 재가동되는 날 백로그가 한꺼번에 처리되지 - * 않도록 1회 200건으로 제한(SQL의 LIMIT). 남은 대상은 다음 배치에서 이어 처리한다.

+ * 않도록 1회 1,000건으로 제한(SQL의 LIMIT). 남은 대상은 다음 배치에서 이어 처리한다.

* *

계정 1건 처리는 마스킹→휴면행 제거→이력적재 순이며, 개별 실패는 로깅 후 계속(fail-safe) * 하여 한 건의 오류가 배치 전체를 막지 않는다.

@@ -29,7 +33,6 @@ public class AccountDeleteServiceImpl implements AccountDeleteService { private static final Logger LOG = LoggerFactory.getLogger(AccountDeleteServiceImpl.class); - private static final String TY_GNR = "GNR"; private static final String TY_EMP = "EMP"; @Resource(name = "accountDeleteDAO") @@ -48,31 +51,25 @@ public class AccountDeleteServiceImpl implements AccountDeleteService { return 0; } - int purged = 0; - purged += purge(accountDeleteDAO.selectExpiredGnrAccounts(), TY_GNR); - purged += purge(accountDeleteDAO.selectExpiredEmpAccounts(), TY_EMP); - LOG.info("[개인정보/#12] 장기 미사용 계정 파기 배치 완료 — {}건", Integer.valueOf(purged)); + int purged = purge(accountDeleteDAO.selectExpiredEmpAccounts()); + LOG.info("[개인정보/#12] 장기 미사용 관리자 계정 파기 배치 완료 — {}건", Integer.valueOf(purged)); return purged; } - /** 유형별 파기 처리. 개별 계정 실패는 건너뛰고 계속한다. */ - private int purge(List targets, String mberTyCode) { + /** 개인정보취급자(EMP) 계정 파기 처리. 개별 계정 실패는 건너뛰고 계속한다. */ + private int purge(List targets) { if (targets == null || targets.isEmpty()) { return 0; } int cnt = 0; for (AccountDeleteTargetVO t : targets) { try { - if (TY_EMP.equals(mberTyCode)) { - accountDeleteDAO.anonymizeEmplyr(t.getMberId()); - } else { - accountDeleteDAO.anonymizeGnrMember(t.getMberId()); - } + accountDeleteDAO.anonymizeEmplyr(t.getMberId()); accountDeleteDAO.deleteDiapauseRow(t.getMberId()); AccountDeleteHistoryVO hist = new AccountDeleteHistoryVO(); hist.setMberId(t.getMberId()); - hist.setMberTyCode(mberTyCode); + hist.setMberTyCode(TY_EMP); hist.setEsntlId(t.getEsntlId()); hist.setDiapauseDt(t.getDiapauseDt()); hist.setPreserveDay(t.getPreserveDay()); @@ -80,8 +77,8 @@ public class AccountDeleteServiceImpl implements AccountDeleteService { cnt++; } catch (Exception e) { - LOG.error("[개인정보/#12] 계정 파기 실패 — 건너뜀 (id={}, ty={}): {}", - new Object[] { t.getMberId(), mberTyCode, e.getMessage() }); + LOG.error("[개인정보/#12] 관리자 계정 파기 실패 — 건너뜀 (id={}): {}", + new Object[] { t.getMberId(), e.getMessage() }); } } return cnt; diff --git a/src/main/java/egovframework/com/cmm/privacy/account/AccountDeleteTargetVO.java b/src/main/java/egovframework/com/cmm/privacy/account/AccountDeleteTargetVO.java index d4a542d..6d58d43 100644 --- a/src/main/java/egovframework/com/cmm/privacy/account/AccountDeleteTargetVO.java +++ b/src/main/java/egovframework/com/cmm/privacy/account/AccountDeleteTargetVO.java @@ -12,7 +12,7 @@ public class AccountDeleteTargetVO implements Serializable { private static final long serialVersionUID = 1L; - /** 파기 대상 계정ID(일반회원 MBER_ID / 취급자 EMPLYR_ID) */ + /** 파기 대상 계정ID(개인정보취급자 EMPLYR_ID) */ private String mberId; /** 고유식별자(ESNTL_ID) */ private String esntlId; diff --git a/src/main/java/egovframework/com/cmm/privacy/account/AccountMngPolicyVO.java b/src/main/java/egovframework/com/cmm/privacy/account/AccountMngPolicyVO.java index 29d6244..e21faa9 100644 --- a/src/main/java/egovframework/com/cmm/privacy/account/AccountMngPolicyVO.java +++ b/src/main/java/egovframework/com/cmm/privacy/account/AccountMngPolicyVO.java @@ -30,12 +30,12 @@ public class AccountMngPolicyVO implements Serializable { private int alertD2 = 7; /** 사전알림 3차(D-Day, 기본 0). */ private int alertD3 = 0; - /** 휴면/탈퇴 후 삭제 보존기간(일). */ + /** 휴면 후 삭제 보존기간(일) — 개인정보취급자(관리자, EMP) 계정 한정. 일반회원은 탈퇴 즉시 삭제되어 대상이 아니다. */ private int deletePreserveDay = 365; /** 배치 주기(DAILY/WEEKLY/MONTHLY). */ private String batchCycle = "DAILY"; /** - * 장기 미사용 계정 파기 배치 사용여부(#12). Y:사용 N:미사용. + * 장기 미사용 관리자(개인정보취급자) 계정 파기 배치 사용여부(#12). Y:사용 N:미사용. * 기본 'N' — 관리자가 정책 화면에서 명시적으로 켜야 파기가 수행된다(되돌릴 수 없는 처리). */ private String purgeUseYn = "N"; diff --git a/src/main/java/kr/or/arko/cmm/batch/BatchScheduler.java b/src/main/java/kr/or/arko/cmm/batch/BatchScheduler.java index c11ec04..5b64568 100644 --- a/src/main/java/kr/or/arko/cmm/batch/BatchScheduler.java +++ b/src/main/java/kr/or/arko/cmm/batch/BatchScheduler.java @@ -301,22 +301,28 @@ public class BatchScheduler { } /** - * [개인정보보호 #12] 장기 미사용 계정 개인정보 파기 배치. + * [개인정보보호 #12] 장기 미사용 관리자(개인정보취급자) 계정 개인정보 파기 배치. * 매일 02:30, 휴면 확정('P') 후 보존기간(ACCOUNT_MNG_POLICY.DELETE_PRESERVE_DAY 정책값)이 - * 경과한 계정의 개인정보를 파기(PII 마스킹 + 상태 'D')하고 ACCOUNT_DELETE_HISTORY 에 이력을 남긴다. - * 휴면 등록 배치(01:00)·사전알림(06:00)과 시각을 분리했다. + * 경과한 관리자 계정의 개인정보를 파기(PII 마스킹 + 상태 'D')하고 ACCOUNT_DELETE_HISTORY 에 + * 이력을 남긴다. 휴면 등록 배치(01:00)·사전알림(06:00)과 시각을 분리했다. + * + *

일반회원(GNR) 계정은 이 배치의 대상이 아니다(2026-08 정리). 탈퇴 신청 즉시 + * {@code EgovMberManageServiceImpl.deleteMber()} 경로에서 하드 DELETE 되므로 휴면 전환을 + * 거쳐 지연 파기할 대상 자체가 없다. 대상은 관리자(EMP) 계정 한정이며, 이는 SFR-002가 + * 지적한 "휴면 대상이 일반회원에 한정되고 개인정보취급자가 미포함"인 취약점을 보완하는 + * 핵심 구현 대상이라 유지한다.

* *

실제 파기 수행 여부는 정책 플래그로 제어한다. 배치는 매일 등록되어 돌지만 * {@code ACCOUNT_MNG_POLICY.PURGE_USE_YN}(기본 'N')이 'Y'가 아니면 서비스가 즉시 0건으로 * 종료한다. 켜고 끄는 것은 계정 관리 정책 화면(#13)에서 하며 재배포가 필요 없다. - * 1회 처리건수는 SQL에서 200건으로 상한(백로그 일시 폭주 방지).

+ * 1회 처리건수는 SQL에서 1,000건으로 상한(백로그 일시 폭주 방지).

* * @throws Exception 파기 오류 */ @Scheduled(cron = "0 30 2 * * ?") public void purgeExpiredAccountScheduler() throws Exception { int purged = accountDeleteService.purgeExpiredAccounts(); - log.info("[개인정보/#12] 장기 미사용 계정 파기 배치 완료 — " + purged + "건"); + log.info("[개인정보/#12] 장기 미사용 관리자 계정 파기 배치 완료 — " + purged + "건"); } /** diff --git a/src/main/resources/egovframework/sqlmap/site/mnt/biz/privacy/PrivacyAccountDelete_SQL_Mysql.xml b/src/main/resources/egovframework/sqlmap/site/mnt/biz/privacy/PrivacyAccountDelete_SQL_Mysql.xml index 6bf622f..9565815 100644 --- a/src/main/resources/egovframework/sqlmap/site/mnt/biz/privacy/PrivacyAccountDelete_SQL_Mysql.xml +++ b/src/main/resources/egovframework/sqlmap/site/mnt/biz/privacy/PrivacyAccountDelete_SQL_Mysql.xml @@ -3,9 +3,12 @@ @@ -14,22 +17,6 @@ - - - - - - - - + 대상 운영 DB가 MySQL(csv)이라 sql-map-config 미등록(다른 Privacy* Oracle 파일과 동일 관례). + 대상은 개인정보취급자(관리자, EMP) 계정 한정 — 일반회원(GNR)은 탈퇴 즉시 하드 DELETE 되어 + 이 배치의 대상이 아니므로 GNR statement 를 제거했다(2026-08). --> - - - - - - - * - 일 (휴면 전환 후 이 기간이 지나면 개인정보 파기 대상) + 일 — 개인정보취급자(관리자) 계정 대상. 휴면 전환 후 이 기간이 지나면 개인정보 파기 대상 + (일반회원은 탈퇴 시 즉시 삭제되므로 이 설정과 무관) @@ -157,7 +159,7 @@ function fn_reload() { - '사용' 선택 시 매일 02:30 배치가 위 기준일 경과 휴면계정의 개인정보를 파기합니다(1회 최대 200건). + 개인정보취급자(관리자) 계정 대상. '사용' 선택 시 매일 02:30 배치가 위 기준일 경과 휴면 관리자계정의 개인정보를 파기합니다(1회 최대 1,000건). 파기된 개인정보는 복구할 수 없습니다.