#13/#9 정책 조회·수정, #11 장기 미사용 계정 현황 조회, #10 휴면 사전알림 대상 조회.
+ * statement는 {@code PrivacyAccountMng_SQL_Mysql.xml}(namespace {@code AccountMngDAO}),
+ * sql-map-config 등록.
+ */
+@Repository("accountMngDAO")
+public class AccountMngDAO extends EgovComAbstractDAO {
+
+ /** #13/#9 정책 단일행 조회. 없으면 null(호출부 기본값 fallback). */
+ public AccountMngPolicyVO selectPolicy() {
+ return (AccountMngPolicyVO) selectByPk("AccountMngDAO.selectPolicy", null);
+ }
+
+ /** #13 정책 수정. */
+ public int updatePolicy(AccountMngPolicyVO vo) {
+ return update("AccountMngDAO.updatePolicy", vo);
+ }
+
+ /** #11 장기 미사용 계정 현황 목록. */
+ @SuppressWarnings("unchecked")
+ public List selectDormantStatusList(DormantAccountStatusVO searchVO) {
+ return list("AccountMngDAO.selectDormantStatusList", searchVO);
+ }
+
+ /** #11 현황 전체 건수(페이징). */
+ public int selectDormantStatusListCount(DormantAccountStatusVO searchVO) {
+ Object cnt = selectByPk("AccountMngDAO.selectDormantStatusListCount", searchVO);
+ return cnt == null ? 0 : ((Integer) cnt).intValue();
+ }
+
+ /** #10 휴면 D-N일 전 사전알림 대상 조회. param: alertBefore(Integer). */
+ @SuppressWarnings("unchecked")
+ public List selectDormantAlertTargets(int alertBefore) {
+ HashMap param = new HashMap();
+ param.put("alertBefore", Integer.valueOf(alertBefore));
+ return list("AccountMngDAO.selectDormantAlertTargets", param);
+ }
+}
diff --git a/src/main/java/egovframework/com/cmm/privacy/account/AccountMngPolicyService.java b/src/main/java/egovframework/com/cmm/privacy/account/AccountMngPolicyService.java
new file mode 100644
index 0000000..d482fd3
--- /dev/null
+++ b/src/main/java/egovframework/com/cmm/privacy/account/AccountMngPolicyService.java
@@ -0,0 +1,13 @@
+package egovframework.com.cmm.privacy.account;
+
+/**
+ * 계정 관리 정책 서비스 (#13 정책 설정 / #9 휴면 기준일수 정책값화).
+ */
+public interface AccountMngPolicyService {
+
+ /** 현재 정책 조회(미설정 시 기본값 VO 반환). */
+ AccountMngPolicyVO selectPolicy() throws Exception;
+
+ /** 정책 수정. */
+ void updatePolicy(AccountMngPolicyVO vo) throws Exception;
+}
diff --git a/src/main/java/egovframework/com/cmm/privacy/account/AccountMngPolicyServiceImpl.java b/src/main/java/egovframework/com/cmm/privacy/account/AccountMngPolicyServiceImpl.java
new file mode 100644
index 0000000..da45b16
--- /dev/null
+++ b/src/main/java/egovframework/com/cmm/privacy/account/AccountMngPolicyServiceImpl.java
@@ -0,0 +1,31 @@
+package egovframework.com.cmm.privacy.account;
+
+import javax.annotation.Resource;
+
+import org.springframework.stereotype.Service;
+
+/**
+ * 계정 관리 정책 서비스 구현 (#13/#9).
+ *
+ *
#9의 휴면 기준일수는 sqlmap 서브쿼리(ACCOUNT_MNG_POLICY.DORMANT_DAY, 미설정 시 365)로
+ * 직접 적용되며, 이 서비스는 관리자 설정 화면(#13)의 조회/수정을 담당한다.
+ * 정책 미설정(테이블 empty) 시 기본값 VO를 반환해 화면·배치가 안전하게 동작한다.
+ */
+@Service("accountMngPolicyService")
+public class AccountMngPolicyServiceImpl implements AccountMngPolicyService {
+
+ @Resource(name = "accountMngDAO")
+ private AccountMngDAO accountMngDAO;
+
+ @Override
+ public AccountMngPolicyVO selectPolicy() throws Exception {
+ AccountMngPolicyVO vo = accountMngDAO.selectPolicy();
+ // 정책 미설정 시 기본값(휴면 365일 등) — DDL 시드가 없어도 화면/배치 안전.
+ return vo != null ? vo : new AccountMngPolicyVO();
+ }
+
+ @Override
+ public void updatePolicy(AccountMngPolicyVO vo) throws Exception {
+ accountMngDAO.updatePolicy(vo);
+ }
+}
diff --git a/src/main/java/egovframework/com/cmm/privacy/account/AccountMngPolicyVO.java b/src/main/java/egovframework/com/cmm/privacy/account/AccountMngPolicyVO.java
new file mode 100644
index 0000000..cbda2d9
--- /dev/null
+++ b/src/main/java/egovframework/com/cmm/privacy/account/AccountMngPolicyVO.java
@@ -0,0 +1,62 @@
+package egovframework.com.cmm.privacy.account;
+
+import java.io.Serializable;
+
+/**
+ * 계정 관리 정책 VO — ACCOUNT_MNG_POLICY(단일행) 매핑.
+ *
+ *
+ */
+public interface DormantAlertService {
+
+ /**
+ * 정책(ALERT_D1/D2/D3)에 해당하는 휴면 사전알림 대상을 조회해 본인·관리자에게 발송한다.
+ * (배치에서 호출; 개별 발송 실패는 로깅 후 계속 — fail-safe)
+ *
+ * @return 발송 건수
+ */
+ int sendDormantPreAlerts() throws Exception;
+}
diff --git a/src/main/java/egovframework/com/cmm/privacy/account/DormantAlertServiceImpl.java b/src/main/java/egovframework/com/cmm/privacy/account/DormantAlertServiceImpl.java
new file mode 100644
index 0000000..b32a9ad
--- /dev/null
+++ b/src/main/java/egovframework/com/cmm/privacy/account/DormantAlertServiceImpl.java
@@ -0,0 +1,112 @@
+package egovframework.com.cmm.privacy.account;
+
+import java.util.ArrayList;
+import java.util.List;
+
+import javax.annotation.Resource;
+
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+import org.springframework.stereotype.Service;
+
+import egovframework.com.cop.ems.service.EgovSndngMailRegistService;
+import egovframework.com.cop.ems.service.SndngMailVO;
+
+/**
+ * 휴면 전 사전 알림 서비스 구현 (#10, D-30/D-7/D-Day + 관리자 알림).
+ *
+ *
정책(ACCOUNT_MNG_POLICY)의 ALERT_D1/D2/D3(기본 30/7/0)일 전 대상을 조회해
+ * 본인과 관리자(ADMIN_EMAIL)에게 사전 알림 메일을 발송한다. 기존 휴면 배치의
+ * 유예/확정 본인 메일(EgovLoginServiceImpl.processDiapauseList)과 독립적으로 동작하며,
+ * 발송 실패가 배치 본류를 막지 않도록 전 구간 fail-safe.
PERSONAL_DATA_ACCESS_LOG 표준 컬럼만 SELECT(별도 탐지 테이블 없음, 위·변조 방지).
+ * statement는 이미 등록된 {@code PrivacyAccessLog_SQL_Mysql.xml}
+ * (namespace {@code PersonalDataAccessLogDAO})에 정의 — 신규 sqlmap 등록 불요.
+ *
+ * @author arko-privacy-auditlog (Fork B - 6차)
+ * @since 2026-07-21
+ */
+@Repository("abnormalAccessDetectionDAO")
+public class AbnormalAccessDetectionDAO extends EgovComAbstractDAO {
+
+ /** ① 비인가접근시도(처리결과 F) 총건수. */
+ public int countUnauthorized(Map param) {
+ Object o = selectByPk("PersonalDataAccessLogDAO.detectUnauthorized", param);
+ return o == null ? 0 : ((Integer) o).intValue();
+ }
+
+ /** ② 대량조회 의심 접속자 목록({conectId, cnt}). */
+ @SuppressWarnings("unchecked")
+ public List
+ *
+ *
운영 안전(관리자 잠금 방지): 이 컨트롤러는 취급자를 잠그지 않는다.
+ * 잠금 회피 판정은 {@link SecondFactorInterceptor}가 수행한다(수단 미설정 사용자는 통과).
+ * 본 컨트롤러는 인증수단이 준비된 사용자에게 검증 UI만 제공한다.
+ *
+ *
+ * << 개정이력(Modification Information) >>
+ * 수정일 수정자 수정내용
+ * ---------- ------ ---------------------------
+ * 2026.07.22 URP 최초 생성 (개인정보보호 SFR-003 / #52 — 2차 인증 화면·검증·OTP 등록)
+ *
+ */
+@Controller
+public class SecondFactorController {
+
+ protected static final Log LOG = LogFactory.getLog(SecondFactorController.class);
+
+ /** 2차 인증 실패 누적 세션 키 — 과도한 시도 억제(계정 잠금과 별개, 이 세션 한정). */
+ private static final String ATTR_SF_ATTEMPT = "SF_ATTEMPT";
+ /** 세션당 2차 인증 최대 시도 횟수. */
+ private static final int MAX_ATTEMPT = 5;
+ /** 검증 성공 후 기본 이동 목적지. */
+ private static final String DEFAULT_TARGET = "/mnt/main.do";
+ /** 미인증 시 이동할 로그인 화면. */
+ private static final String LOGIN_URL = "/uat/uia/egovLoginUsr.do";
+
+ /** 취급자별 인증수단 분기 리졸버(빈: authMethodResolver). */
+ @Resource(name = "authMethodResolver")
+ private AuthMethodResolver authMethodResolver;
+
+ /** OTP 인증 서비스(등록 경로에서 비밀키 발급/검증에 직접 사용). */
+ @Resource(name = "otpAuthService")
+ private OtpAuthService otpAuthService;
+
+ // ────────────────────────────────────────────────────────── 2차 인증 화면
+
+ /**
+ * 2차 인증 입력 화면(GET). 로그인 사용자에게 배정된 인증수단을 판정해 안내한다.
+ */
+ @RequestMapping(value = "/uat/uia/secondFactor.do")
+ public String secondFactorView(HttpServletRequest request, ModelMap model,
+ @RequestParam(value = "returnUrl", required = false) String returnUrl) throws Exception {
+
+ LoginVO user = loginUser(request);
+ if (user == null) {
+ return "redirect:" + LOGIN_URL;
+ }
+
+ model.addAttribute("returnUrl", sanitizeTarget(returnUrl));
+
+ AuthMethodService svc = null;
+ try {
+ svc = authMethodResolver.resolve(user.getUniqId());
+ } catch (Exception e) {
+ // 사용 가능한 수단이 하나도 없음 → 등록 유도(잠그지 않음).
+ LOG.info("[개인정보/#52] 2차 인증수단 미가용 — 등록 유도: user=" + user.getId() + ", cause=" + e.toString());
+ }
+
+ if (svc == null || !safeAvailable(svc, user.getUniqId())) {
+ model.addAttribute("needEnroll", Boolean.TRUE);
+ model.addAttribute("methodType", "NONE");
+ return "egovframework/com/uat/uia/EgovSecondFactor";
+ }
+
+ String methodType = methodTypeOf(svc);
+ model.addAttribute("methodType", methodType);
+
+ // SMS 수단이면 화면 진입 시 인증번호 발송(구현체 준비 후 동작). 실패해도 화면은 표시.
+ if ("SMS".equals(methodType)) {
+ try {
+ svc.issue(user.getUniqId());
+ model.addAttribute("maskedPhone", MaskingUtil.maskPhone(user.getMbtlnum()));
+ } catch (Exception e) {
+ LOG.warn("[개인정보/#52] SMS 인증번호 발송 미가용(구현 대기): " + e.toString());
+ model.addAttribute("issueFail", Boolean.TRUE);
+ }
+ }
+
+ return "egovframework/com/uat/uia/EgovSecondFactor";
+ }
+
+ /**
+ * 2차 인증 코드 검증(POST). 성공 시 세션 재발급 + 완료표식 세팅 후 목적지 이동.
+ */
+ @RequestMapping(value = "/uat/uia/secondFactorConfirm.do")
+ public String secondFactorConfirm(HttpServletRequest request, ModelMap model,
+ @RequestParam(value = "authCode", required = false) String authCode,
+ @RequestParam(value = "returnUrl", required = false) String returnUrl) throws Exception {
+
+ LoginVO user = loginUser(request);
+ if (user == null) {
+ return "redirect:" + LOGIN_URL;
+ }
+
+ String target = sanitizeTarget(returnUrl);
+ model.addAttribute("returnUrl", target);
+
+ // 세션당 과도한 시도 억제
+ HttpSession session = request.getSession();
+ int attempt = attemptCount(session);
+ if (attempt >= MAX_ATTEMPT) {
+ model.addAttribute("methodType", "LOCKED");
+ model.addAttribute("errorMsg", "인증 시도 횟수를 초과했습니다. 다시 로그인해 주세요.");
+ return "egovframework/com/uat/uia/EgovSecondFactor";
+ }
+
+ AuthMethodService svc = null;
+ try {
+ svc = authMethodResolver.resolve(user.getUniqId());
+ } catch (Exception e) {
+ LOG.info("[개인정보/#52] 검증 단계 수단 미가용 — 등록 유도: user=" + user.getId());
+ }
+ if (svc == null || !safeAvailable(svc, user.getUniqId())) {
+ model.addAttribute("needEnroll", Boolean.TRUE);
+ model.addAttribute("methodType", "NONE");
+ return "egovframework/com/uat/uia/EgovSecondFactor";
+ }
+
+ String methodType = methodTypeOf(svc);
+ model.addAttribute("methodType", methodType);
+
+ boolean ok = false;
+ try {
+ ok = svc.verify(user.getUniqId(), authCode);
+ } catch (Exception e) {
+ LOG.warn("[개인정보/#52] 2차 인증 검증 오류: " + e.toString());
+ ok = false;
+ }
+
+ if (ok) {
+ // 성공 — 세션 재발급(세션 고정 차단, #16) + 완료표식 세팅. attempt 카운터는 세션과 함께 폐기됨.
+ SessionRegenerator.onSecondFactorSuccess(request);
+ LOG.info("[개인정보/#14] 2차 인증 성공 — 세션 재발급, 목적지 이동: user=" + user.getId() + ", target=" + target);
+ return "redirect:" + target;
+ }
+
+ // 실패 — 시도횟수 증가 후 화면 재표시(계정열거/수단 노출 없이 일관 메시지)
+ session.setAttribute(ATTR_SF_ATTEMPT, Integer.valueOf(attempt + 1));
+ model.addAttribute("errorMsg", "인증코드가 올바르지 않습니다. 다시 시도해 주세요.");
+ return "egovframework/com/uat/uia/EgovSecondFactor";
+ }
+
+ // ────────────────────────────────────────────────────────── OTP 최소 등록(enrollment)
+
+ /**
+ * OTP 등록 화면(GET). 신규 비밀키를 발급·암호화 저장하고 프로비저닝 정보(secret/otpauth URI)를 표시한다.
+ *
+ *
주의: 매 진입 시 새 비밀키가 발급되므로, 표시된 secret으로 인증앱을 설정한 뒤
+ * 반드시 하단 최초검증까지 완료해야 한다. (기본 저장소 {@code InMemoryOtpSecretStore}는
+ * 애플리케이션 재기동 시 유실되므로 운영에서는 DB 저장소 구현체로 교체한다.)
+ */
+ @RequestMapping(value = "/uat/uia/secondFactorEnroll.do")
+ public String enrollView(HttpServletRequest request, ModelMap model) throws Exception {
+ LoginVO user = loginUser(request);
+ if (user == null) {
+ return "redirect:" + LOGIN_URL;
+ }
+
+ String otpauthUri = otpAuthService.provision(user.getUniqId());
+ model.addAttribute("otpauthUri", otpauthUri);
+ model.addAttribute("otpSecret", extractSecret(otpauthUri)); // 수동 입력용(QR 보조) Base32 secret
+ model.addAttribute("accountLabel", user.getId());
+ // otpauth URI 를 스캔 가능한 QR(자립형 data URI PNG)로 렌더 — 외부 리소스/네트워크 없음(ZXing 서버측 생성).
+ try {
+ model.addAttribute("otpQrDataUri", buildQrDataUri(otpauthUri, 220));
+ } catch (Exception e) {
+ // QR 생성 실패해도 수동 입력(secret)로 등록 가능하도록 화면은 계속 표시.
+ LOG.warn("[개인정보/#18] OTP QR 생성 실패 — 수동 입력으로 대체: " + e.toString());
+ }
+ return "egovframework/com/uat/uia/EgovSecondFactorEnroll";
+ }
+
+ /**
+ * OTP 등록 최초검증(POST). 발급한 비밀키로 첫 코드를 검증한다.
+ * 성공 시 곧바로 2차 인증 완료로 간주하고 세션을 재발급한 뒤 취급자 영역으로 이동한다.
+ */
+ @RequestMapping(value = "/uat/uia/secondFactorEnrollConfirm.do")
+ public String enrollConfirm(HttpServletRequest request, ModelMap model,
+ @RequestParam(value = "authCode", required = false) String authCode) throws Exception {
+
+ LoginVO user = loginUser(request);
+ if (user == null) {
+ return "redirect:" + LOGIN_URL;
+ }
+
+ boolean ok = false;
+ try {
+ ok = otpAuthService.verify(user.getUniqId(), authCode);
+ } catch (Exception e) {
+ LOG.warn("[개인정보/#18] OTP 등록 검증 오류: " + e.toString());
+ ok = false;
+ }
+
+ if (ok) {
+ SessionRegenerator.onSecondFactorSuccess(request);
+ LOG.info("[개인정보/#18] OTP 등록·최초검증 완료 — 세션 재발급: user=" + user.getId());
+ return "redirect:" + DEFAULT_TARGET;
+ }
+
+ model.addAttribute("enrollError", "인증코드가 올바르지 않습니다. 인증앱의 현재 코드로 다시 시도하거나 재발급하세요.");
+ return "egovframework/com/uat/uia/EgovSecondFactorEnroll";
+ }
+
+ // ────────────────────────────────────────────────────────── 내부 유틸
+
+ private LoginVO loginUser(HttpServletRequest request) {
+ HttpSession session = request.getSession(false);
+ if (session == null) {
+ return null;
+ }
+ Object o = session.getAttribute("loginVO");
+ return (o instanceof LoginVO) ? (LoginVO) o : null;
+ }
+
+ private int attemptCount(HttpSession session) {
+ Object o = session.getAttribute(ATTR_SF_ATTEMPT);
+ return (o instanceof Integer) ? ((Integer) o).intValue() : 0;
+ }
+
+ /** {@code isAvailable} 호출 시 구현체(스텁)가 던지는 예외를 흡수해 false로 취급. */
+ private boolean safeAvailable(AuthMethodService svc, String uniqId) {
+ try {
+ return svc.isAvailable(uniqId);
+ } catch (Exception e) {
+ return false;
+ }
+ }
+
+ private String methodTypeOf(AuthMethodService svc) {
+ if (svc instanceof OtpAuthService) {
+ return "OTP";
+ }
+ if (svc instanceof SmsAuthService) {
+ return "SMS";
+ }
+ if (svc instanceof CertAuthService) {
+ return "CERT";
+ }
+ return "OTP";
+ }
+
+ /**
+ * 목적지 URL 화이트리스트 검증 — open-redirect 방지.
+ * 취급자 영역({@code /mnt/}) 내부 경로만 허용하고, 그 외/이상값은 기본 목적지로 대체한다.
+ */
+ private String sanitizeTarget(String returnUrl) {
+ if (returnUrl == null) {
+ return DEFAULT_TARGET;
+ }
+ String t = returnUrl.trim();
+ if (t.length() == 0) {
+ return DEFAULT_TARGET;
+ }
+ // 절대 URL/프로토콜 상대(//host)/역참조 차단 — 반드시 "/mnt/"로 시작하는 앱 내부 경로만 허용
+ if (!t.startsWith("/mnt/") || t.startsWith("//") || t.indexOf('\\') >= 0
+ || t.indexOf("://") >= 0 || t.indexOf("..") >= 0) {
+ return DEFAULT_TARGET;
+ }
+ return t;
+ }
+
+ /**
+ * otpauth URI를 스캔 가능한 QR 코드로 인코딩해 자립형 data URI(PNG, Base64)로 반환한다.
+ *
외부 리소스/네트워크 없이 서버측(ZXing, 기존 의존성)에서 생성한다 — 폐쇄망 전제.
+ * 반환값은 {@code data:image/png;base64,...} 형식으로 {@code }에 그대로 바인딩된다.
+ */
+ private String buildQrDataUri(String content, int size) throws Exception {
+ QRCodeWriter writer = new QRCodeWriter();
+ java.util.Map hints = new java.util.HashMap();
+ hints.put(EncodeHintType.ERROR_CORRECTION, ErrorCorrectionLevel.M);
+ hints.put(EncodeHintType.MARGIN, Integer.valueOf(1));
+ hints.put(EncodeHintType.CHARACTER_SET, "UTF-8");
+ BitMatrix matrix = writer.encode(content, BarcodeFormat.QR_CODE, size, size, hints);
+ ByteArrayOutputStream baos = new ByteArrayOutputStream();
+ MatrixToImageWriter.writeToStream(matrix, "png", baos);
+ String b64 = new String(Base64.encodeBase64(baos.toByteArray()), "US-ASCII");
+ return "data:image/png;base64," + b64;
+ }
+
+ /** otpauth URI에서 secret 파라미터(Base32)만 추출한다(수동 입력 안내용). */
+ private String extractSecret(String otpauthUri) {
+ if (otpauthUri == null) {
+ return "";
+ }
+ int i = otpauthUri.indexOf("secret=");
+ if (i < 0) {
+ return "";
+ }
+ int start = i + "secret=".length();
+ int amp = otpauthUri.indexOf('&', start);
+ return (amp < 0) ? otpauthUri.substring(start) : otpauthUri.substring(start, amp);
+ }
+}
diff --git a/src/main/java/egovframework/com/uss/umt/service/impl/EgovAccountUnlockManageServiceImpl.java b/src/main/java/egovframework/com/uss/umt/service/impl/EgovAccountUnlockManageServiceImpl.java
index 79965a4..051d209 100644
--- a/src/main/java/egovframework/com/uss/umt/service/impl/EgovAccountUnlockManageServiceImpl.java
+++ b/src/main/java/egovframework/com/uss/umt/service/impl/EgovAccountUnlockManageServiceImpl.java
@@ -3,10 +3,17 @@ package egovframework.com.uss.umt.service.impl;
import java.util.List;
import javax.annotation.Resource;
+import javax.servlet.http.HttpServletRequest;
import org.springframework.stereotype.Service;
+import org.springframework.web.context.request.RequestContextHolder;
+import org.springframework.web.context.request.ServletRequestAttributes;
import egovframework.com.cmm.LoginVO;
+import egovframework.com.cmm.privacy.auditlog.ClientIpResolver;
+import egovframework.com.cmm.privacy.lock.AccountUnlockHistoryDAO;
+import egovframework.com.cmm.privacy.lock.AccountUnlockHistoryVO;
+import egovframework.com.cmm.util.EgovUserDetailsHelper;
import egovframework.com.uat.uia.service.impl.LoginDAO;
import egovframework.com.uss.umt.service.DiapauseManageVO;
import egovframework.com.uss.umt.service.EgovAccountUnlockManageService;
@@ -15,17 +22,21 @@ import egovframework.rte.fdl.cmmn.AbstractServiceImpl;
@Service("egovAccountUnlockManageService")
public class EgovAccountUnlockManageServiceImpl extends AbstractServiceImpl implements EgovAccountUnlockManageService{
-
+
/** diapauseManageDAO */
@Resource(name="diapauseManageDAO")
- private DiapauseManageDAO diapauseManageDAO;
-
+ private DiapauseManageDAO diapauseManageDAO;
+
//@Resource(name = "loginService")
- //protected EgovLoginService egovLoginService;
-
+ //protected EgovLoginService egovLoginService;
+
@Resource(name="loginDAO")
private LoginDAO loginDAO;
+ /** 개인정보보호(SER-001 / #24) 계정 잠금 해제 이력 DAO */
+ @Resource(name="accountUnlockHistoryDAO")
+ private AccountUnlockHistoryDAO accountUnlockHistoryDAO;
+
@Override
public List selectUserLockCheckList(UserDefaultVO userSearchVO) {
return diapauseManageDAO.selectUserLockCheckList(userSearchVO);
@@ -40,41 +51,88 @@ public class EgovAccountUnlockManageServiceImpl extends AbstractServiceImpl imp
public void updateUnlock(String user_id, String user_gubun) {
String [] sUser_id = user_id.split(",");
String [] sUser_gubun = user_gubun.split(",");
-
+
DiapauseManageVO vo = new DiapauseManageVO();
LoginVO loginVo = new LoginVO();
- for (int i=0; i※ 스케줄 활성화 주의: 본 프로젝트는 {@code }이
+ * context-common.xml/egov-com-servlet.xml 양쪽 모두 주석 처리되어 있어 현재
+ * {@code @Scheduled}가 동작하지 않는다(배치는 OS crontab 운영). 활성화 시 기존 휴면/메일 등
+ * 휴면 배치가 함께 기동되므로, 활성화 여부·경합은 운영과 협의해 결정한다.
+ *
+ * @throws Exception 파기 오류
+ */
+ @Scheduled(cron = "0 0 4 1 * ?")
+ public void purgeAuthorChangeHistoryScheduler() throws Exception {
+ int purged = privacyBatchService.purgeExpiredAuthorChangeHistory();
+ log.info("[개인정보/#5] 권한변경이력 파기 배치 완료 — " + purged + "건");
+ }
+
+ /**
+ * [개인정보보호 #40] 개인정보 접속기록(PERSONAL_DATA_ACCESS_LOG) 해시체인 무결성 검증 배치.
+ * 매월 1일 03:00, 적재 순서대로 재계산해 위·변조를 탐지하고 불일치 시 즉시 관리자 통보.
+ * (스케줄 활성화 주의는 {@link #purgeAuthorChangeHistoryScheduler()} 참조.)
+ *
+ * @throws Exception 검증 오류
+ */
+ @Scheduled(cron = "0 0 3 1 * ?")
+ public void verifyAccessLogHashChainScheduler() throws Exception {
+ HashChainVerifyResultVO result = privacyBatchService.verifyAccessLogHashChain();
+ log.info("[개인정보/#40] 접속기록 무결성 검증 배치 완료 — 총 " + result.getTotalCount()
+ + "건, 결과=" + (result.isOk() ? "정상" : "위반(LOG_ID=" + result.getMismatchLogId() + ")"));
+ }
+
+ /**
+ * [개인정보보호 #37] 개인정보 접속기록(PERSONAL_DATA_ACCESS_LOG) 보관정책 파기 배치.
+ * 매월 1일 02:00(저부하 시간대), 보관기간(730일) 경과 파티션을 DROP(개별 DELETE 미사용)하고
+ * 미래 월 파티션을 선행 증설한다. COMTNSYSLOG 7일 삭제정책은 이 테이블에 적용하지 않는다(#38).
+ * (스케줄 활성화 주의는 {@link #purgeAuthorChangeHistoryScheduler()} 참조 — 현재 전역 비활성.)
+ *
+ * @throws Exception 파기/증설 오류
+ */
+ @Scheduled(cron = "0 0 2 1 * ?")
+ public void purgeAccessLogPartitionScheduler() throws Exception {
+ int dropped = privacyBatchService.purgeExpiredAccessLogPartitions();
+ log.info("[개인정보/#37] 접속기록 파티션 파기/증설 배치 완료 — 파기 " + dropped + "개 파티션");
+ }
+
+ /**
+ * [개인정보보호 #43] 개인정보 접속기록 정기점검 리포트 생성 배치.
+ * 매월 1일 05:00(기존 파기 02:00/무결성 03:00/이력파기 04:00 배치와 시각 분리).
+ * 전월분 접속기록을 집계(총건수·실패·무결성#40·취급자별 처리현황)해 PDAL_INSPECTION_REPORT 적재.
+ *
+ *
※ 스케줄 활성화 주의: 전역 {@code } 비활성 상태라
+ * 현재 {@code @Scheduled}는 동작하지 않는다(OS crontab 운영). 활성화·경합은 운영과 협의.
+ *
+ * @throws Exception 집계/적재 오류
+ */
+ @Scheduled(cron = "0 0 5 1 * ?")
+ public void generateInspectionReportScheduler() throws Exception {
+ InspectionReportVO report = privacyBatchService.generateMonthlyInspectionReport();
+ log.info("[개인정보/#43] 정기점검 리포트 생성 배치 완료 — " + report.getRptYm()
+ + " 총 " + report.getTotalLogCnt() + "건, 무결성=" + report.getIntegrityResult());
+ }
+
+ /**
+ * [개인정보보호 #10] 휴면 전 사전 알림 배치 — D-30/D-7/D-Day 3단계 + 관리자 알림.
+ * 매일 06:00, 정책(ACCOUNT_MNG_POLICY)의 ALERT_D1/D2/D3 시점에 해당하는 휴면 예정
+ * 계정을 본인·관리자에게 사전 통지한다. 발송 실패는 fail-safe(로깅 후 계속).
+ *
+ *
※ 전역 {@code } 비활성 상태라 현재 @Scheduled 는 동작하지
+ * 않는다(OS crontab 운영). 활성화·경합은 운영과 협의(핸드오프).
+ *
+ * @throws Exception 알림 처리 오류
+ */
+ @Scheduled(cron = "0 0 6 * * ?")
+ public void dormantPreAlertScheduler() throws Exception {
+ int sent = dormantAlertService.sendDormantPreAlerts();
+ log.info("[개인정보/#10] 휴면 사전알림 배치 완료 — " + sent + "건 발송");
+ }
+
}
\ No newline at end of file
diff --git a/src/main/resources/egovframework/spring/com/context-egovuserdetailshelper.xml b/src/main/resources/egovframework/spring/com/context-egovuserdetailshelper.xml
index 9072897..e187388 100644
--- a/src/main/resources/egovframework/spring/com/context-egovuserdetailshelper.xml
+++ b/src/main/resources/egovframework/spring/com/context-egovuserdetailshelper.xml
@@ -32,8 +32,11 @@
-
-
+
+
+
+
+
diff --git a/src/main/resources/egovframework/spring/com/context-syslogaop.xml b/src/main/resources/egovframework/spring/com/context-syslogaop.xml
index 651340f..2b09386 100644
--- a/src/main/resources/egovframework/spring/com/context-syslogaop.xml
+++ b/src/main/resources/egovframework/spring/com/context-syslogaop.xml
@@ -24,4 +24,39 @@
-->
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/src/main/resources/egovframework/sqlmap/com/uat/uia/EgovLoginUsr_SQL_Mysql.xml b/src/main/resources/egovframework/sqlmap/com/uat/uia/EgovLoginUsr_SQL_Mysql.xml
index 4bc609a..0c2aab6 100644
--- a/src/main/resources/egovframework/sqlmap/com/uat/uia/EgovLoginUsr_SQL_Mysql.xml
+++ b/src/main/resources/egovframework/sqlmap/com/uat/uia/EgovLoginUsr_SQL_Mysql.xml
@@ -185,6 +185,119 @@
]]>
+
+
+
+
+
+
+
+
+
+
+
+
+
+ (SELECT IFNULL(MAX(DORMANT_DAY),365) FROM ACCOUNT_MNG_POLICY) /* No.9 정책값화(미설정 시 365) */
+ AND a.MBER_ID NOT IN (SELECT MBER_ID FROM COMTNDIAPAUSEMBER)
+ AND a.MBER_STTUS = 'P'
+ ]]>
+
+
+
+
+ (SELECT IFNULL(MAX(DORMANT_DAY),365) FROM ACCOUNT_MNG_POLICY) /* No.9 정책값화(미설정 시 365) */
+ AND e.EMPLYR_ID NOT IN (SELECT MBER_ID FROM COMTNDIAPAUSEMBER)
+ AND e.EMPLYR_STTUS_CODE = 'P'
+ ]]>
+
+
+
+
+
+
+
+
+
+
+ UPDATE COMTNDIAPAUSEMBER SET DIAPAUSE_STAT = #diapause_stat# WHERE MBER_ID = #id#
+
+
+
+
+
+
+
+ UPDATE COMTNDIAPAUSEMBER
+ SET TOKEN = #token#
+ , TOKEN_EXPDT = DATE_ADD(NOW(), INTERVAL 10 MINUTE)
+ WHERE MBER_ID = #mber_id#
+ AND ESNTL_ID = #esntl_id#
+
+
365
+ WHERE NVL(SYSDATE - b.CREAT_DT,SYSDATE - a.SBSCRB_DE) > (SELECT NVL(MAX(DORMANT_DAY),365) FROM ACCOUNT_MNG_POLICY) /* No.9 정책값화 */
AND a.MBER_ID NOT IN (SELECT MBER_ID FROM COMTNDIAPAUSEMBER)
AND a.MBER_STTUS = 'P'
]]>
+
+
+
+ (SELECT NVL(MAX(DORMANT_DAY),365) FROM ACCOUNT_MNG_POLICY) /* No.9 정책값화 */
+ AND e.EMPLYR_ID NOT IN (SELECT MBER_ID FROM COMTNDIAPAUSEMBER)
+ AND e.EMPLYR_STTUS_CODE = 'P'
+ ]]>
+
+
+
+
= DIAPAUSE_DT)
+ WHERE A.MBER_TY_CODE = 'GNR'
+ AND (DIAPAUSE_STAT IS NULL OR (DIAPAUSE_STAT = 'S' AND SYSDATE >= DIAPAUSE_DT))
diff --git a/src/main/resources/egovframework/sqlmap/config/mysql/sql-map-config-mysql-0-site.xml b/src/main/resources/egovframework/sqlmap/config/mysql/sql-map-config-mysql-0-site.xml
index a77b364..a2f6df5 100644
--- a/src/main/resources/egovframework/sqlmap/config/mysql/sql-map-config-mysql-0-site.xml
+++ b/src/main/resources/egovframework/sqlmap/config/mysql/sql-map-config-mysql-0-site.xml
@@ -99,4 +99,34 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/src/main/resources/egovframework/sqlmap/site/mnt/biz/privacy/PrivacyAccessLogIntegrity_SQL_Mysql.xml b/src/main/resources/egovframework/sqlmap/site/mnt/biz/privacy/PrivacyAccessLogIntegrity_SQL_Mysql.xml
new file mode 100644
index 0000000..1c49341
--- /dev/null
+++ b/src/main/resources/egovframework/sqlmap/site/mnt/biz/privacy/PrivacyAccessLogIntegrity_SQL_Mysql.xml
@@ -0,0 +1,34 @@
+
+
+
+
+
+
+
+
+
diff --git a/src/main/resources/egovframework/sqlmap/site/mnt/biz/privacy/PrivacyAccessLogIntegrity_SQL_Oracle.xml b/src/main/resources/egovframework/sqlmap/site/mnt/biz/privacy/PrivacyAccessLogIntegrity_SQL_Oracle.xml
new file mode 100644
index 0000000..4a8131c
--- /dev/null
+++ b/src/main/resources/egovframework/sqlmap/site/mnt/biz/privacy/PrivacyAccessLogIntegrity_SQL_Oracle.xml
@@ -0,0 +1,32 @@
+
+
+
+
+
+
+
+
+
diff --git a/src/main/resources/egovframework/sqlmap/site/mnt/biz/privacy/PrivacyAccessLog_SQL_Mysql.xml b/src/main/resources/egovframework/sqlmap/site/mnt/biz/privacy/PrivacyAccessLog_SQL_Mysql.xml
new file mode 100644
index 0000000..919b0ae
--- /dev/null
+++ b/src/main/resources/egovframework/sqlmap/site/mnt/biz/privacy/PrivacyAccessLog_SQL_Mysql.xml
@@ -0,0 +1,358 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ ALTER TABLE PERSONAL_DATA_ACCESS_LOG DROP PARTITION $value$
+
+
+
+
+ ALTER TABLE PERSONAL_DATA_ACCESS_LOG
+ ADD PARTITION (PARTITION $partitionName$ VALUES LESS THAN ('$boundaryDt$'))
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/src/main/resources/egovframework/sqlmap/site/mnt/biz/privacy/PrivacyAccessLog_SQL_Oracle.xml b/src/main/resources/egovframework/sqlmap/site/mnt/biz/privacy/PrivacyAccessLog_SQL_Oracle.xml
new file mode 100644
index 0000000..94842e7
--- /dev/null
+++ b/src/main/resources/egovframework/sqlmap/site/mnt/biz/privacy/PrivacyAccessLog_SQL_Oracle.xml
@@ -0,0 +1,299 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ ALTER TABLE PERSONAL_DATA_ACCESS_LOG DROP PARTITION $value$
+
+
+
+ ALTER TABLE PERSONAL_DATA_ACCESS_LOG
+ ADD PARTITION $partitionName$ VALUES LESS THAN (TO_DATE('$boundaryDt$', 'YYYY-MM-DD'))
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/src/main/resources/egovframework/sqlmap/site/mnt/biz/privacy/PrivacyAccountMng_SQL_Mysql.xml b/src/main/resources/egovframework/sqlmap/site/mnt/biz/privacy/PrivacyAccountMng_SQL_Mysql.xml
new file mode 100644
index 0000000..e40bd44
--- /dev/null
+++ b/src/main/resources/egovframework/sqlmap/site/mnt/biz/privacy/PrivacyAccountMng_SQL_Mysql.xml
@@ -0,0 +1,111 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/src/main/resources/egovframework/sqlmap/site/mnt/biz/privacy/PrivacyAccountMng_SQL_Oracle.xml b/src/main/resources/egovframework/sqlmap/site/mnt/biz/privacy/PrivacyAccountMng_SQL_Oracle.xml
new file mode 100644
index 0000000..832df1a
--- /dev/null
+++ b/src/main/resources/egovframework/sqlmap/site/mnt/biz/privacy/PrivacyAccountMng_SQL_Oracle.xml
@@ -0,0 +1,88 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/src/main/resources/egovframework/sqlmap/site/mnt/biz/privacy/PrivacyAccountUnlockHistory_SQL_Mysql.xml b/src/main/resources/egovframework/sqlmap/site/mnt/biz/privacy/PrivacyAccountUnlockHistory_SQL_Mysql.xml
new file mode 100644
index 0000000..177d80a
--- /dev/null
+++ b/src/main/resources/egovframework/sqlmap/site/mnt/biz/privacy/PrivacyAccountUnlockHistory_SQL_Mysql.xml
@@ -0,0 +1,53 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/src/main/resources/egovframework/sqlmap/site/mnt/biz/privacy/PrivacyAccountUnlockHistory_SQL_Oracle.xml b/src/main/resources/egovframework/sqlmap/site/mnt/biz/privacy/PrivacyAccountUnlockHistory_SQL_Oracle.xml
new file mode 100644
index 0000000..cf467a6
--- /dev/null
+++ b/src/main/resources/egovframework/sqlmap/site/mnt/biz/privacy/PrivacyAccountUnlockHistory_SQL_Oracle.xml
@@ -0,0 +1,54 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/src/main/resources/egovframework/sqlmap/site/mnt/biz/privacy/PrivacyAuthorHistoryPurge_SQL_Mysql.xml b/src/main/resources/egovframework/sqlmap/site/mnt/biz/privacy/PrivacyAuthorHistoryPurge_SQL_Mysql.xml
new file mode 100644
index 0000000..ba425e9
--- /dev/null
+++ b/src/main/resources/egovframework/sqlmap/site/mnt/biz/privacy/PrivacyAuthorHistoryPurge_SQL_Mysql.xml
@@ -0,0 +1,19 @@
+
+
+
+
+
+
+
+
+
+
+
diff --git a/src/main/resources/egovframework/sqlmap/site/mnt/biz/privacy/PrivacyAuthorHistoryPurge_SQL_Oracle.xml b/src/main/resources/egovframework/sqlmap/site/mnt/biz/privacy/PrivacyAuthorHistoryPurge_SQL_Oracle.xml
new file mode 100644
index 0000000..497a74f
--- /dev/null
+++ b/src/main/resources/egovframework/sqlmap/site/mnt/biz/privacy/PrivacyAuthorHistoryPurge_SQL_Oracle.xml
@@ -0,0 +1,17 @@
+
+
+
+
+
+
+
+
+
+
+
diff --git a/src/main/resources/egovframework/sqlmap/site/mnt/biz/privacy/PrivacyAuthorWorkflow_SQL_Mysql.xml b/src/main/resources/egovframework/sqlmap/site/mnt/biz/privacy/PrivacyAuthorWorkflow_SQL_Mysql.xml
new file mode 100644
index 0000000..a97303f
--- /dev/null
+++ b/src/main/resources/egovframework/sqlmap/site/mnt/biz/privacy/PrivacyAuthorWorkflow_SQL_Mysql.xml
@@ -0,0 +1,166 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/src/main/resources/egovframework/sqlmap/site/mnt/biz/privacy/PrivacyAuthorWorkflow_SQL_Oracle.xml b/src/main/resources/egovframework/sqlmap/site/mnt/biz/privacy/PrivacyAuthorWorkflow_SQL_Oracle.xml
new file mode 100644
index 0000000..4017a81
--- /dev/null
+++ b/src/main/resources/egovframework/sqlmap/site/mnt/biz/privacy/PrivacyAuthorWorkflow_SQL_Oracle.xml
@@ -0,0 +1,162 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/src/main/resources/egovframework/sqlmap/site/mnt/biz/privacy/PrivacyAuthority_SQL_Mysql.xml b/src/main/resources/egovframework/sqlmap/site/mnt/biz/privacy/PrivacyAuthority_SQL_Mysql.xml
new file mode 100644
index 0000000..e526198
--- /dev/null
+++ b/src/main/resources/egovframework/sqlmap/site/mnt/biz/privacy/PrivacyAuthority_SQL_Mysql.xml
@@ -0,0 +1,22 @@
+
+
+
+
+
+
+
+
+
diff --git a/src/main/resources/egovframework/sqlmap/site/mnt/biz/privacy/PrivacyAuthority_SQL_Oracle.xml b/src/main/resources/egovframework/sqlmap/site/mnt/biz/privacy/PrivacyAuthority_SQL_Oracle.xml
new file mode 100644
index 0000000..50876ab
--- /dev/null
+++ b/src/main/resources/egovframework/sqlmap/site/mnt/biz/privacy/PrivacyAuthority_SQL_Oracle.xml
@@ -0,0 +1,19 @@
+
+
+
+
+
+
+
+
+
diff --git a/src/main/resources/egovframework/sqlmap/site/mnt/biz/privacy/PrivacyJobMapping_SQL_Mysql.xml b/src/main/resources/egovframework/sqlmap/site/mnt/biz/privacy/PrivacyJobMapping_SQL_Mysql.xml
new file mode 100644
index 0000000..ea3185c
--- /dev/null
+++ b/src/main/resources/egovframework/sqlmap/site/mnt/biz/privacy/PrivacyJobMapping_SQL_Mysql.xml
@@ -0,0 +1,29 @@
+
+
+
+
+
+
+
+
+
diff --git a/src/main/resources/egovframework/sqlmap/site/mnt/biz/privacy/PrivacyJobMapping_SQL_Oracle.xml b/src/main/resources/egovframework/sqlmap/site/mnt/biz/privacy/PrivacyJobMapping_SQL_Oracle.xml
new file mode 100644
index 0000000..e771490
--- /dev/null
+++ b/src/main/resources/egovframework/sqlmap/site/mnt/biz/privacy/PrivacyJobMapping_SQL_Oracle.xml
@@ -0,0 +1,28 @@
+
+
+
+
+
+
+
+
+
diff --git a/src/main/resources/egovframework/sqlmap/site/mnt/biz/privacy/PrivacyMenuAuth_SQL_Mysql.xml b/src/main/resources/egovframework/sqlmap/site/mnt/biz/privacy/PrivacyMenuAuth_SQL_Mysql.xml
new file mode 100644
index 0000000..5e7d89c
--- /dev/null
+++ b/src/main/resources/egovframework/sqlmap/site/mnt/biz/privacy/PrivacyMenuAuth_SQL_Mysql.xml
@@ -0,0 +1,27 @@
+
+
+
+
+
+
+
+
+
diff --git a/src/main/resources/egovframework/sqlmap/site/mnt/biz/privacy/PrivacyMenuAuth_SQL_Oracle.xml b/src/main/resources/egovframework/sqlmap/site/mnt/biz/privacy/PrivacyMenuAuth_SQL_Oracle.xml
new file mode 100644
index 0000000..91edfcd
--- /dev/null
+++ b/src/main/resources/egovframework/sqlmap/site/mnt/biz/privacy/PrivacyMenuAuth_SQL_Oracle.xml
@@ -0,0 +1,22 @@
+
+
+
+
+
+
+
+
+
diff --git a/src/main/resources/egovframework/sqlmap/site/mnt/biz/privacy/PrivacyOtpDevice_SQL_Mysql.xml b/src/main/resources/egovframework/sqlmap/site/mnt/biz/privacy/PrivacyOtpDevice_SQL_Mysql.xml
new file mode 100644
index 0000000..a5c682b
--- /dev/null
+++ b/src/main/resources/egovframework/sqlmap/site/mnt/biz/privacy/PrivacyOtpDevice_SQL_Mysql.xml
@@ -0,0 +1,50 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/src/main/resources/egovframework/sqlmap/site/mnt/biz/privacy/PrivacyOtpDevice_SQL_Oracle.xml b/src/main/resources/egovframework/sqlmap/site/mnt/biz/privacy/PrivacyOtpDevice_SQL_Oracle.xml
new file mode 100644
index 0000000..8c68204
--- /dev/null
+++ b/src/main/resources/egovframework/sqlmap/site/mnt/biz/privacy/PrivacyOtpDevice_SQL_Oracle.xml
@@ -0,0 +1,53 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/src/main/webapp/WEB-INF/config/egovframework/springmvc/egov-com-servlet.xml b/src/main/webapp/WEB-INF/config/egovframework/springmvc/egov-com-servlet.xml
index b036377..0cf783e 100644
--- a/src/main/webapp/WEB-INF/config/egovframework/springmvc/egov-com-servlet.xml
+++ b/src/main/webapp/WEB-INF/config/egovframework/springmvc/egov-com-servlet.xml
@@ -64,11 +64,22 @@
-
+
+
+
+
+
+
@@ -94,6 +105,107 @@
+
+
+
+
+
+
+
+
+
+ \A/mnt/(main|left|top|right|footer)\.do.*\Z
+ \A/mnt/(include)/.*\.do.*\Z
+ \A/uat/uia/.*\Z
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ \A/mnt/(main|left|top|right|footer)\.do.*\Z
+ \A/mnt/(include)/.*\.do.*\Z
+ \A/uat/uia/.*\Z
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ ROLE_ADMIN
+ ROLE_ADMINS
+
+
+
+
+ 30
diff --git a/src/script/mysql/ddl/privacy.account_mng_policy_create_mysql.sql b/src/script/mysql/ddl/privacy.account_mng_policy_create_mysql.sql
new file mode 100644
index 0000000..8aa95ec
--- /dev/null
+++ b/src/script/mysql/ddl/privacy.account_mng_policy_create_mysql.sql
@@ -0,0 +1,34 @@
+-- =====================================================================
+-- 계정 관리 정책 : ACCOUNT_MNG_POLICY (단일행 정책 테이블)
+-- 문화품앗이 개인정보보호 기능개선 사업 (SFR-002, 장기 미사용 계정 관리)
+-- 개발항목 #13(정책 설정) + #9(휴면 기준일수 정책값화) - 담당: arko-privacy-authctrl (Fork A)
+-- 작성일: 2026-07-21 DB: MySQL 8 / MariaDB
+-- ---------------------------------------------------------------------
+-- [용도] 휴면 기준일수·사전알림 시점·삭제 보존기간·배치주기 등 계정 관리 정책을
+-- 하드코딩이 아닌 관리자 설정값으로 운영하기 위한 단일행 정책 테이블.
+-- [#9] 휴면 select(EgovLoginUsr_SQL_Mysql.xml)의 365 하드코딩을
+-- (SELECT DORMANT_DAY FROM ACCOUNT_MNG_POLICY) 로 대체(미설정 시 365 fallback).
+-- [#10] ALERT_D1/D2/D3(기본 30/7/0) = 휴면예정 D-30/D-7/D-Day 사전알림 시점.
+-- ADMIN_EMAIL = 사전알림 관리자 수신 주소.
+-- =====================================================================
+
+CREATE TABLE IF NOT EXISTS ACCOUNT_MNG_POLICY (
+ POLICY_ID INT NOT NULL DEFAULT 1 COMMENT '정책 식별자(단일행=1)',
+ DORMANT_DAY INT NOT NULL DEFAULT 365 COMMENT '휴면 전환 기준 미접속 일수(#9)',
+ ALERT_D1 INT NOT NULL DEFAULT 30 COMMENT '사전알림 1차(휴면 D-일수)',
+ ALERT_D2 INT NOT NULL DEFAULT 7 COMMENT '사전알림 2차(휴면 D-일수)',
+ ALERT_D3 INT NOT NULL DEFAULT 0 COMMENT '사전알림 3차(D-Day, 0=당일)',
+ DELETE_PRESERVE_DAY INT NOT NULL DEFAULT 365 COMMENT '휴면/탈퇴 후 삭제 보존기간(일)',
+ BATCH_CYCLE VARCHAR(20) NOT NULL DEFAULT 'DAILY' COMMENT '배치 주기(DAILY/WEEKLY/MONTHLY)',
+ ADMIN_EMAIL VARCHAR(200) NULL COMMENT '사전알림 관리자 수신 이메일',
+ UPD_ID VARCHAR(20) NULL COMMENT '최종 수정자',
+ UPD_DT DATETIME NULL COMMENT '최종 수정일시',
+ PRIMARY KEY (POLICY_ID)
+) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4
+ COMMENT='계정 관리 정책(개인정보보호 SFR-002, 단일행)';
+
+-- 기본 정책 시드(없을 때만). 기준 365일·알림 D-30/7/0·보존 365일.
+INSERT INTO ACCOUNT_MNG_POLICY (POLICY_ID, DORMANT_DAY, ALERT_D1, ALERT_D2, ALERT_D3, DELETE_PRESERVE_DAY, BATCH_CYCLE, UPD_ID, UPD_DT)
+SELECT 1, 365, 30, 7, 0, 365, 'DAILY', 'SYSTEM', NOW()
+ FROM DUAL
+ WHERE NOT EXISTS (SELECT 1 FROM ACCOUNT_MNG_POLICY WHERE POLICY_ID = 1);
diff --git a/src/script/mysql/ddl/privacy.account_unlock_history_create_mysql.sql b/src/script/mysql/ddl/privacy.account_unlock_history_create_mysql.sql
new file mode 100644
index 0000000..b5ada4b
--- /dev/null
+++ b/src/script/mysql/ddl/privacy.account_unlock_history_create_mysql.sql
@@ -0,0 +1,38 @@
+-- =====================================================================
+-- 계정 잠금 해제 이력 : ACCOUNT_UNLOCK_HISTORY
+-- 문화품앗이 개인정보보호 기능개선 사업 (SER-001, 계정 잠금 정책 정비)
+-- 개발항목 #24 - 담당: arko-privacy-authctrl (Fork A)
+-- 작성일: 2026-07-21 DB: MySQL 8 / MariaDB
+-- ---------------------------------------------------------------------
+-- [목적] 잠긴 계정 해제를 DB 직접조작이 아니라 관리자 화면(EgovAccountUnlockManage)
+-- 에서 처리하고, 해제자·해제일시·대상·사유를 이력으로 남긴다.
+-- [무결성] 애플리케이션 계층에 UPDATE/DELETE statement 미정의(INSERT/SELECT 한정).
+-- =====================================================================
+
+CREATE TABLE ACCOUNT_UNLOCK_HISTORY (
+ UNLOCK_HIST_ID BIGINT NOT NULL AUTO_INCREMENT COMMENT '해제 이력 일련번호(PK)',
+ TRGET_ID VARCHAR(50) NULL COMMENT '해제 대상 계정 ID(전체해제 시 NULL 가능)',
+ TRGET_SE VARCHAR(10) NULL COMMENT '대상 회원구분 GNR/GRO/ENT/USR',
+ UNLOCK_TY CHAR(1) NOT NULL DEFAULT 'I' COMMENT '해제 유형 I:개별 A:전체',
+ UNLOCK_USER_ID VARCHAR(50) NULL COMMENT '해제 처리자(관리자) ID',
+ UNLOCK_RESN VARCHAR(500) NULL COMMENT '해제 사유',
+ UNLOCK_IP VARCHAR(45) NULL COMMENT '해제자 접속 IP(IPv6 대응)',
+ UNLOCK_DT DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP COMMENT '해제 일시',
+ PRIMARY KEY (UNLOCK_HIST_ID),
+ KEY IX_AUH_TRGET (TRGET_ID, UNLOCK_DT),
+ KEY IX_AUH_USER (UNLOCK_USER_ID, UNLOCK_DT),
+ KEY IX_AUH_DT (UNLOCK_DT)
+) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4
+ COMMENT='계정 잠금 해제 이력(개인정보보호 SER-001 #24)';
+
+
+-- =====================================================================
+-- 계정 잠금 임계값 10회 → 5회 (#24)
+-- 임계값은 하드코딩이 아니라 LOGIN_LOCK_COUNT.LOCK_COUNT(단일행) 데이터에서 조회됨
+-- (loginDAO.selectFailTotalCount). 아래 UPDATE 로 운영 정책값을 5회로 조정한다.
+-- ※ 소스(EgovLoginController)에는 값 부재 시 기본 5회 fallback 이 반영되어 있음.
+-- =====================================================================
+UPDATE LOGIN_LOCK_COUNT SET LOCK_COUNT = 5;
+-- 행이 없을 경우 대비(최초 세팅):
+-- INSERT INTO LOGIN_LOCK_COUNT (LOCK_COUNT) SELECT 5 FROM DUAL
+-- WHERE NOT EXISTS (SELECT 1 FROM LOGIN_LOCK_COUNT);
diff --git a/src/script/mysql/ddl/privacy.author_change_history_create_mysql.sql b/src/script/mysql/ddl/privacy.author_change_history_create_mysql.sql
new file mode 100644
index 0000000..3c49e77
--- /dev/null
+++ b/src/script/mysql/ddl/privacy.author_change_history_create_mysql.sql
@@ -0,0 +1,37 @@
+-- =====================================================================
+-- 권한 변경 이력 테이블 : AUTHOR_CHANGE_HISTORY
+-- 문화품앗이 개인정보보호 기능개선 사업 (SFR-001, 역할별 차등 접근/권한 변경 추적)
+-- 개발항목 #4 - 담당: arko-privacy-authctrl (Fork A → 오케스트레이터 완료)
+-- 작성일: 2026-07-21 DB: MySQL 8 / MariaDB
+-- ---------------------------------------------------------------------
+-- [목적] 권한 신설·변경·회수(#1 getAuthorities 대상 COMTNEMPLYRSCRTYESTBS 등)의
+-- 모든 변경을 변경자·변경일시·변경전/후 권한과 함께 이력으로 적재한다.
+-- 권한 신청·승인 워크플로(#3)의 승인 반영 시점에 권한반영과 단일 트랜잭션으로 적재.
+--
+-- [보관·파기] 3년(1,095일) 이상 보관 전제. 월 1회 @Scheduled 파기 배치(#5)가
+-- 보관기간(1,095일) 경과 이력만 선별 파기한다. 개별 삭제 API 미제공.
+-- 다운로드 시 사유 입력 필수 + 다운로드 이력 적재(#6)는 별도 처리.
+--
+-- [무결성] 이력 위·변조 억제를 위해 변경전/후 권한을 함께 보관(사후 대사 가능).
+-- 애플리케이션 계층에 UPDATE statement 미정의(INSERT/SELECT/파기 DELETE 한정).
+-- =====================================================================
+
+CREATE TABLE AUTHOR_CHANGE_HISTORY (
+ HIST_ID BIGINT NOT NULL AUTO_INCREMENT COMMENT '이력 일련번호(PK)',
+ TRGET_UNIQ_ID VARCHAR(20) NOT NULL COMMENT '변경 대상자 고유식별자(ESNTL_ID)',
+ TRGET_ID VARCHAR(20) NULL COMMENT '변경 대상자 계정ID(참고용)',
+ CHG_SE_CODE CHAR(1) NOT NULL COMMENT '변경구분 G:부여(grant) R:회수(revoke) M:변경(modify)',
+ BEFORE_AUTHOR VARCHAR(1000) NULL COMMENT '변경 전 권한(코드 CSV 또는 스냅샷)',
+ AFTER_AUTHOR VARCHAR(1000) NULL COMMENT '변경 후 권한(코드 CSV 또는 스냅샷)',
+ CHG_RESN VARCHAR(500) NULL COMMENT '변경 사유(신청/승인 워크플로 연계 #3)',
+ APROVL_ID VARCHAR(20) NULL COMMENT '승인자 계정ID(승인 워크플로 #3)',
+ CHANGER_UNIQ_ID VARCHAR(20) NOT NULL COMMENT '변경자(처리자) 고유식별자',
+ CHANGER_ID VARCHAR(20) NULL COMMENT '변경자 계정ID(참고용)',
+ CHANGER_IP VARCHAR(45) NULL COMMENT '변경자 접속 IP(IPv6 대응)',
+ CHG_DT DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP COMMENT '변경 일시',
+ PRIMARY KEY (HIST_ID),
+ KEY IX_ACH_TRGET (TRGET_UNIQ_ID, CHG_DT),
+ KEY IX_ACH_CHANGER (CHANGER_UNIQ_ID, CHG_DT),
+ KEY IX_ACH_CHGDT (CHG_DT)
+) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4
+ COMMENT='권한 변경 이력(개인정보보호 SFR-001, 3년 이상 보관)';
diff --git a/src/script/mysql/ddl/privacy.author_workflow_create_mysql.sql b/src/script/mysql/ddl/privacy.author_workflow_create_mysql.sql
new file mode 100644
index 0000000..486e44b
--- /dev/null
+++ b/src/script/mysql/ddl/privacy.author_workflow_create_mysql.sql
@@ -0,0 +1,47 @@
+-- =====================================================================
+-- 권한 신청·승인 워크플로 + 권한이력 다운로드 이력
+-- 문화품앗이 개인정보보호 기능개선 사업 (SFR-001, 권한 신설·변경·회수 승인통제)
+-- 개발항목 #3(신청·승인 워크플로) / #6(권한이력 조회·다운로드) - 담당: arko-privacy-authctrl (Fork A 6차)
+-- 작성일: 2026-07-21 DB: MySQL 8 / MariaDB
+-- ---------------------------------------------------------------------
+-- [#3] AUTHOR_CHANGE_REQUEST : 권한 변경 신청→승인/반려 워크플로.
+-- 승인(A) 시 권한 반영(COMTNEMPLYRSCRTYESTBS)과 이력 적재(AUTHOR_CHANGE_HISTORY)를
+-- 단일 트랜잭션으로 처리한다(egovframework.com..*Impl AOP tx advisor).
+-- [#6] AUTHOR_HIST_DOWNLOAD_HISTORY : 권한이력 다운로드 증빙(사유 필수).
+-- =====================================================================
+
+CREATE TABLE AUTHOR_CHANGE_REQUEST (
+ REQUEST_ID BIGINT NOT NULL AUTO_INCREMENT COMMENT '신청 일련번호(PK)',
+ TRGET_UNIQ_ID VARCHAR(20) NOT NULL COMMENT '권한변경 대상자 고유식별자(ESNTL_ID)',
+ TRGET_ID VARCHAR(20) NULL COMMENT '권한변경 대상자 계정ID(참고용)',
+ REQUEST_SE CHAR(1) NOT NULL COMMENT '신청구분 N:신설(부여) C:변경 R:회수',
+ BEFORE_AUTHOR VARCHAR(1000) NULL COMMENT '신청 시점 현재 권한(스냅샷)',
+ AFTER_AUTHOR VARCHAR(1000) NULL COMMENT '신청 목표 권한(코드/CSV)',
+ REQUEST_RESN VARCHAR(500) NOT NULL COMMENT '신청 사유(필수)',
+ REQSTER_ID VARCHAR(20) NOT NULL COMMENT '신청자 계정ID',
+ REQSTER_UNIQ_ID VARCHAR(20) NULL COMMENT '신청자 고유식별자',
+ REQUEST_DT DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP COMMENT '신청 일시',
+ CONFM_STTUS CHAR(1) NOT NULL DEFAULT 'R' COMMENT '처리상태 R:신청 A:승인 J:반려',
+ CONFM_ID VARCHAR(20) NULL COMMENT '승인/반려 처리자 계정ID',
+ CONFM_UNIQ_ID VARCHAR(20) NULL COMMENT '승인/반려 처리자 고유식별자',
+ CONFM_DT DATETIME NULL COMMENT '승인/반려 일시',
+ CONFM_RESN VARCHAR(500) NULL COMMENT '반려 사유',
+ PRIMARY KEY (REQUEST_ID),
+ KEY IX_ACR_TRGET (TRGET_UNIQ_ID, REQUEST_DT),
+ KEY IX_ACR_STTUS (CONFM_STTUS, REQUEST_DT)
+) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4
+ COMMENT='권한 변경 신청·승인 워크플로(개인정보보호 SFR-001 #3)';
+
+CREATE TABLE AUTHOR_HIST_DOWNLOAD_HISTORY (
+ DWLD_ID BIGINT NOT NULL AUTO_INCREMENT COMMENT '다운로드 일련번호(PK)',
+ DOWNLOADER_ID VARCHAR(20) NOT NULL COMMENT '다운로드자 계정ID',
+ DOWNLOADER_UNIQ_ID VARCHAR(20) NULL COMMENT '다운로드자 고유식별자',
+ DOWNLOADER_IP VARCHAR(45) NULL COMMENT '다운로드자 접속 IP(IPv6 대응)',
+ DWLD_RESN VARCHAR(500) NOT NULL COMMENT '다운로드 사유(필수·미입력 차단)',
+ SEARCH_COND VARCHAR(1000) NULL COMMENT '다운로드 시 검색조건 스냅샷',
+ DWLD_CNT INT NOT NULL DEFAULT 0 COMMENT '다운로드 건수',
+ DWLD_DT DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP COMMENT '다운로드 일시',
+ PRIMARY KEY (DWLD_ID),
+ KEY IX_AHD_DWLD (DOWNLOADER_ID, DWLD_DT)
+) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4
+ COMMENT='권한이력 다운로드 증빙 이력(개인정보보호 SFR-001 #6)';
diff --git a/src/script/mysql/ddl/privacy.diapause_emplyr_extend_mysql.sql b/src/script/mysql/ddl/privacy.diapause_emplyr_extend_mysql.sql
new file mode 100644
index 0000000..be142c2
--- /dev/null
+++ b/src/script/mysql/ddl/privacy.diapause_emplyr_extend_mysql.sql
@@ -0,0 +1,25 @@
+-- =====================================================================
+-- 휴면(장기 미사용) 대상 확장 : COMTNDIAPAUSEMBER 회원유형 구분 컬럼 추가
+-- 문화품앗이 개인정보보호 기능개선 사업 (SFR-002, 장기 미사용 계정 관리)
+-- 개발항목 #7 - 담당: arko-privacy-authctrl (Fork A)
+-- 작성일: 2026-07-21 DB: MySQL 8 / MariaDB
+-- ---------------------------------------------------------------------
+-- [배경] 기존 휴면 대상은 일반회원(COMTNGNRLMBER)뿐이며, 휴면 테이블
+-- COMTNDIAPAUSEMBER 에는 회원유형 구분 컬럼이 없다. 개인정보취급자
+-- (COMTNEMPLYRINFO)를 휴면 대상에 포함(#7)하면서 일반회원 휴면/해제
+-- 흐름과 혼선되지 않도록 회원유형 구분 컬럼을 신설한다.
+--
+-- [안전] DEFAULT 'GNR'(일반회원)로 신설 → 기존 적재 행은 자동 'GNR' 처리되어
+-- 기존 selectDiapauseList(일반회원 join COMTNGNRLMBER) 흐름 무영향.
+-- 취급자 휴면 행은 'EMP'로 적재하여 별도 select/notify 경로로 처리한다.
+--
+-- [멱등] 재실행 안전을 위해 컬럼 존재 시 무시(수동 확인 후 실행).
+-- =====================================================================
+
+ALTER TABLE COMTNDIAPAUSEMBER
+ ADD COLUMN MBER_TY_CODE CHAR(3) NOT NULL DEFAULT 'GNR'
+ COMMENT '회원유형 GNR:일반회원 EMP:개인정보취급자';
+
+-- 조회 성능(취급자/일반 분리 조회) 보조 인덱스
+ALTER TABLE COMTNDIAPAUSEMBER
+ ADD INDEX IX_DIAPAUSE_MBERTY (MBER_TY_CODE, DIAPAUSE_STAT);
diff --git a/src/script/mysql/ddl/privacy.download_history_create_mysql.sql b/src/script/mysql/ddl/privacy.download_history_create_mysql.sql
new file mode 100644
index 0000000..6ce6aa8
--- /dev/null
+++ b/src/script/mysql/ddl/privacy.download_history_create_mysql.sql
@@ -0,0 +1,30 @@
+-- =====================================================================
+-- 개인정보 접속기록 다운로드 이력 : PDAL_DOWNLOAD_HISTORY
+-- 문화품앗이 개인정보보호 기능개선 사업 (SFR-004 / SER-002)
+-- 개발항목 #42 - 담당: arko-privacy-auditlog (Fork B)
+-- 작성일: 2026-07-21 DB: MySQL 8 / MariaDB
+-- ---------------------------------------------------------------------
+-- [목적] 개인정보 접속기록(PERSONAL_DATA_ACCESS_LOG) 조회결과 다운로드 시,
+-- 다운로드 사유(필수)·다운로드자·일시·IP·건수를 이력으로 남긴다.
+-- 사유 미입력 시 다운로드는 서비스 계층에서 차단(본 테이블 미적재).
+-- [원칙] 개인정보 본문 미적재 — 다운로드 검색조건 요약(SEARCH_COND)만 보관.
+-- 이 테이블은 INSERT/SELECT 만 수행(위·변조 방지, UPDATE/DELETE 미정의).
+-- =====================================================================
+
+CREATE TABLE PDAL_DOWNLOAD_HISTORY (
+ DL_ID BIGINT NOT NULL AUTO_INCREMENT COMMENT '다운로드 이력 일련번호(PK)',
+ DOWNLOADER_ID VARCHAR(20) NOT NULL COMMENT '다운로드자 계정ID',
+ DOWNLOAD_DT DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP COMMENT '다운로드 일시',
+ DOWNLOADER_IP VARCHAR(45) NULL COMMENT '다운로드자 접속 IP(IPv6 대응)',
+ DL_REASON VARCHAR(500) NOT NULL COMMENT '다운로드 사유(필수 — 미입력 시 차단)',
+ SEARCH_COND VARCHAR(1000) NULL COMMENT '다운로드 검색조건 요약(개인정보 본문 미포함)',
+ RESULT_CNT INT NOT NULL DEFAULT 0 COMMENT '다운로드 건수',
+ REG_DT DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP COMMENT '등록 일시',
+ PRIMARY KEY (DL_ID),
+ KEY IX_PDDH_DT (DOWNLOAD_DT),
+ KEY IX_PDDH_USER (DOWNLOADER_ID, DOWNLOAD_DT)
+) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4
+ COMMENT='개인정보 접속기록 다운로드 이력(#42, SER-002)';
+
+-- [Oracle] 시퀀스 필요:
+-- CREATE SEQUENCE PDAL_DOWNLOAD_HISTORY_SEQ START WITH 1 INCREMENT BY 1 NOCACHE;
diff --git a/src/script/mysql/ddl/privacy.inspection_report_create_mysql.sql b/src/script/mysql/ddl/privacy.inspection_report_create_mysql.sql
new file mode 100644
index 0000000..2ae17c1
--- /dev/null
+++ b/src/script/mysql/ddl/privacy.inspection_report_create_mysql.sql
@@ -0,0 +1,37 @@
+-- =====================================================================
+-- 개인정보 접속기록 정기점검 리포트 : PDAL_INSPECTION_REPORT
+-- 문화품앗이 개인정보보호 기능개선 사업 (SFR-004 / SER-002)
+-- 개발항목 #43 - 담당: arko-privacy-auditlog (Fork B)
+-- 작성일: 2026-07-21 DB: MySQL 8 / MariaDB
+-- ---------------------------------------------------------------------
+-- [목적] 월 1회 정기점검 배치가 전월분 접속기록을 집계해 1행으로 적재한다.
+-- "월 1회 이상 점검 내역"을 화면 출력으로 제출(개인정보보호위 점검 대응).
+--
+-- [집계원] PERSONAL_DATA_ACCESS_LOG(#30) + 해시체인 무결성 검증(#40) 재사용.
+-- [자리표시] 이상징후 5유형(#35)·다운로드 이력(#42)은 미구현 → 컬럼만 두고 배치가 0/요약만 적재.
+-- [화면입력] INSPECTOR_ID/INSPECT_RESULT/ACTN_MATTER 는 배치가 아닌 점검자가 화면(#48)에서 입력.
+-- =====================================================================
+
+CREATE TABLE PDAL_INSPECTION_REPORT (
+ RPT_ID BIGINT NOT NULL AUTO_INCREMENT COMMENT '리포트 일련번호(PK)',
+ RPT_YM CHAR(6) NOT NULL COMMENT '점검 대상 연월(전월, yyyyMM)',
+ PERIOD_FROM DATETIME NOT NULL COMMENT '집계 시작일시',
+ PERIOD_TO DATETIME NOT NULL COMMENT '집계 종료일시',
+ TOTAL_LOG_CNT INT NOT NULL DEFAULT 0 COMMENT '총 접속기록 건수',
+ FAIL_CNT INT NOT NULL DEFAULT 0 COMMENT '처리결과 F(실패/차단) 건수 = 이상징후 일부',
+ ABNORMAL_SUMMARY VARCHAR(2000) NULL COMMENT '이상징후 5유형 요약(#35 연계 전 자리표시)',
+ INTEGRITY_RESULT VARCHAR(20) NOT NULL DEFAULT 'OK' COMMENT '해시체인 무결성: OK / VIOLATION',
+ INTEGRITY_MISMATCH_ID BIGINT NULL COMMENT '최초 불일치 LOG_ID(위반 시)',
+ INTEGRITY_TOTAL_CNT INT NOT NULL DEFAULT 0 COMMENT '무결성 검증 대상 건수',
+ HANDLER_SUMMARY VARCHAR(4000) NULL COMMENT '취급자별 처리현황 요약',
+ DOWNLOAD_CNT INT NOT NULL DEFAULT 0 COMMENT '다운로드 이력 건수(#42 연계 전 0)',
+ INSPECTOR_ID VARCHAR(20) NULL COMMENT '점검자 계정ID(화면 입력, #44)',
+ INSPECT_RESULT VARCHAR(500) NULL COMMENT '점검결과(화면 입력, #44)',
+ ACTN_MATTER VARCHAR(1000) NULL COMMENT '조치사항(화면 입력, #44)',
+ INSPECT_DT DATETIME NULL COMMENT '점검일시(점검결과 입력 시각, #44 감사증빙)',
+ REG_DT DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP COMMENT '리포트 생성일시',
+ PRIMARY KEY (RPT_ID),
+ UNIQUE KEY UX_PIR_YM (RPT_YM),
+ KEY IX_PIR_REGDT (REG_DT)
+) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4
+ COMMENT='개인정보 접속기록 정기점검 리포트(#43, 월 1회)';
diff --git a/src/script/mysql/ddl/privacy.job_mapping_create_mysql.sql b/src/script/mysql/ddl/privacy.job_mapping_create_mysql.sql
new file mode 100644
index 0000000..5a0b6f0
--- /dev/null
+++ b/src/script/mysql/ddl/privacy.job_mapping_create_mysql.sql
@@ -0,0 +1,53 @@
+-- =====================================================================
+-- 개인정보 처리 업무유형 매핑 테이블 : PDAL_JOB_MAPPING
+-- 문화품앗이 개인정보보호 기능개선 사업 (SFR-004 / SER-002)
+-- 개발항목 #32 - 담당: arko-privacy-auditlog (Fork B)
+-- 작성일: 2026-07-21 DB: MySQL 8 / MariaDB
+-- ---------------------------------------------------------------------
+-- [목적] 접속기록 수집(#31 PersonalDataLogAspect)이 가로챈 서비스 메서드를
+-- 업무코드(JOB_SE_CODE)·업무명(JOB_SE_NM)으로 매핑한다.
+-- AOP pointcut 범위(개인정보 처리 서비스 패키지) 안에서 신규 화면이 추가되면
+-- 이 테이블에 행만 등록하면 수집되며, Aspect/pointcut 소스 수정이 불요하다.
+--
+-- [매칭 규칙] Aspect가 (대상클래스 단순명, 메서드명)으로 조회.
+-- - TARGET_CLASS : 서비스 impl 단순 클래스명(예: EgovMberManageServiceImpl) 또는 FQN
+-- - METHOD_PTTRN : 메서드명 접두 매칭(예: 'select','insertMber') 또는 '*'(전체)
+-- - DML_GUBUN : 지정 시 우선, NULL이면 메서드명 접두로 유도(insert*→I/update*→U/delete*→D/그외 S)
+-- - SORT_ORDR : 다중 매칭 시 낮은 값 우선(구체 규칙을 위로)
+--
+-- ※ URL 패턴 기반 수집이 필요하면(서블릿 필터형) 동일 테이블에 URL_PTTRN 컬럼을 추가해 확장.
+-- =====================================================================
+
+CREATE TABLE PDAL_JOB_MAPPING (
+ MAPPING_ID BIGINT NOT NULL AUTO_INCREMENT COMMENT '매핑 일련번호(PK)',
+ TARGET_CLASS VARCHAR(200) NOT NULL COMMENT '대상 서비스 impl 단순 클래스명 또는 FQN',
+ METHOD_PTTRN VARCHAR(100) NOT NULL DEFAULT '*' COMMENT '메서드명 접두 매칭(또는 * 전체)',
+ JOB_SE_CODE VARCHAR(30) NOT NULL COMMENT '수행업무 유형 코드',
+ JOB_SE_NM VARCHAR(100) NOT NULL COMMENT '수행업무 유형(화면·기능명)',
+ DML_GUBUN CHAR(1) NULL COMMENT '처리구분 강제값 S/I/U/D (NULL=메서드명 유도)',
+ SORT_ORDR INT NOT NULL DEFAULT 100 COMMENT '매칭 우선순위(낮을수록 우선)',
+ USE_AT CHAR(1) NOT NULL DEFAULT 'Y' COMMENT '사용여부 Y/N',
+ RM VARCHAR(300) NULL COMMENT '비고',
+ PRIMARY KEY (MAPPING_ID),
+ KEY IX_PJM_LOOKUP (USE_AT, TARGET_CLASS, SORT_ORDR)
+) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4
+ COMMENT='개인정보 처리 업무유형 매핑(접속기록 #31/#32)';
+
+-- ---------------------------------------------------------------------
+-- 초기 매핑 시드 : 현행 개인정보 처리 서비스(#31 pointcut 대상과 일치)
+-- 신규 개인정보 화면 추가 시 아래 형식으로 행만 추가하면 수집된다.
+-- ---------------------------------------------------------------------
+INSERT INTO PDAL_JOB_MAPPING (TARGET_CLASS, METHOD_PTTRN, JOB_SE_CODE, JOB_SE_NM, DML_GUBUN, SORT_ORDR, RM) VALUES
+ ('EgovMberManageServiceImpl', 'select', 'MBER_VIEW', '회원관리-조회', 'S', 10, '회원 개인정보 조회'),
+ ('EgovMberManageServiceImpl', 'insert', 'MBER_REG', '회원관리-등록', 'I', 10, NULL),
+ ('EgovMberManageServiceImpl', 'update', 'MBER_MOD', '회원관리-수정', 'U', 10, NULL),
+ ('EgovMberManageServiceImpl', 'delete', 'MBER_DEL', '회원관리-삭제', 'D', 10, NULL),
+ ('EgovMberManageServiceImpl', '*', 'MBER_ETC', '회원관리-기타', NULL, 90, '미분류 폴백'),
+ ('EgovEntrprsManageServiceImpl', 'select', 'ENTRPRS_VIEW', '업체관리-조회', 'S', 10, '업체 담당자 개인정보 조회'),
+ ('EgovEntrprsManageServiceImpl', 'insert', 'ENTRPRS_REG', '업체관리-등록', 'I', 10, NULL),
+ ('EgovEntrprsManageServiceImpl', 'update', 'ENTRPRS_MOD', '업체관리-수정', 'U', 10, NULL),
+ ('EgovEntrprsManageServiceImpl', 'delete', 'ENTRPRS_DEL', '업체관리-삭제', 'D', 10, NULL),
+ ('EgovEntrprsManageServiceImpl', '*', 'ENTRPRS_ETC', '업체관리-기타', NULL, 90, '미분류 폴백'),
+ ('MberSsnServiceImpl', '*', 'MBER_SSN', '회원 고유식별정보 처리', NULL, 20, '주민등록번호 등 고유식별정보'),
+ ('BizMberMergeServiceImpl', '*', 'MBER_MERGE', '회원 통합/이관', NULL, 30, NULL),
+ ('BizTalentMberManageServiceImpl', '*', 'TALENT_MBER', '재능나눔 회원관리', NULL, 30, NULL);
diff --git a/src/script/mysql/ddl/privacy.otp_device_create_mysql.sql b/src/script/mysql/ddl/privacy.otp_device_create_mysql.sql
new file mode 100644
index 0000000..51142f6
--- /dev/null
+++ b/src/script/mysql/ddl/privacy.otp_device_create_mysql.sql
@@ -0,0 +1,24 @@
+-- =====================================================================
+-- OTP(2단계 인증) 비밀키 영구 저장 테이블
+-- 문화품앗이 개인정보보호 기능개선 사업 (SFR-003 / IAR-001, 추가 인증수단 1차 OTP)
+-- 개발항목 No.18 후속 - 담당: arko-privacy-authctrl
+-- 작성일: 2026-07-22 DB: MySQL 5.7/8 / MariaDB
+-- ---------------------------------------------------------------------
+-- [배경] OtpSecretStore 기본 구현이 InMemory(재기동 시 유실)라 이를 대체하는
+-- DB 영속 저장소(DaoOtpSecretStore)가 사용하는 테이블.
+-- [보안] SECRET_ENC 는 OtpSecretCipher(AES-GCM) 암호문(Base64)만 저장한다.
+-- 평문 비밀키/OTP 코드는 저장하지 않는다(SER-003).
+-- [키] UNIQ_ID = 개인정보취급자 고유식별자(ESNTL_ID), 취급자당 1건(PK).
+-- =====================================================================
+
+CREATE TABLE IF NOT EXISTS OTP_DEVICE (
+ UNIQ_ID VARCHAR(20) NOT NULL COMMENT '개인정보취급자 고유식별자(ESNTL_ID, PK)',
+ SECRET_ENC VARCHAR(255) NOT NULL COMMENT 'OTP 비밀키 AES-GCM 암호문(Base64) - 평문 금지',
+ REG_DT DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP COMMENT '등록(발급) 일시',
+ LAST_VERIFY_DT DATETIME NULL COMMENT '최종 검증 성공 일시(참고용)',
+ USE_CNT INT NOT NULL DEFAULT 0 COMMENT '검증 성공 누적 횟수(참고용)',
+ DEVICE_LABEL VARCHAR(100) NULL COMMENT '기기/계정 라벨(참고용)',
+ USE_YN CHAR(1) NOT NULL DEFAULT 'Y' COMMENT '사용여부 Y:사용 N:해지',
+ PRIMARY KEY (UNIQ_ID)
+) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4
+ COMMENT='OTP 2단계 인증 비밀키 영구 저장(개인정보보호 SFR-003 No.18)';
diff --git a/src/script/mysql/ddl/privacy.pdal_create_mysql.sql b/src/script/mysql/ddl/privacy.pdal_create_mysql.sql
new file mode 100644
index 0000000..bf487d7
--- /dev/null
+++ b/src/script/mysql/ddl/privacy.pdal_create_mysql.sql
@@ -0,0 +1,79 @@
+-- =====================================================================
+-- 개인정보 접속기록 표준 테이블 : PERSONAL_DATA_ACCESS_LOG
+-- 문화품앗이 개인정보보호 기능개선 사업 (SFR-004 / DBR-001 / SER-002)
+-- 개발항목 #30 - 담당: arko-privacy-auditlog (Fork B)
+-- 작성일: 2026-07-21 DB: MySQL 8 / MariaDB
+-- ---------------------------------------------------------------------
+-- [설계 원칙] 신규 스키마 설계가 아니라 기존 표준 PERSONAL_DATA_DML_HISTORY 를
+-- 표준 접속기록 테이블로 "승격" + 보관/무결성 컬럼 확장이다.
+--
+-- [기존 PERSONAL_DATA_DML_HISTORY → PERSONAL_DATA_ACCESS_LOG 컬럼 매핑]
+-- CHG_USER_ID → CONECT_ID (① 사용자 식별자)
+-- CREATE_DATE → CONECT_DT (② 접속일시, SYSDATE())
+-- CHG_CONECT_IP → CONECT_IP (③ 접속지 IP) * VARCHAR(45) IPv6 대응
+-- UI_GUBUN → JOB_SE_CODE/NM (④ 수행업무 유형) * 업무유형 매핑 테이블로 코드/명 분리
+-- ESNTL_ID → TRGET_ID (⑤ 처리대상 식별자)
+-- DML_GUBUN → DML_GUBUN (처리구분 S/I/U/D 그대로 계승)
+-- CHG_USER_SE → USER_SE (처리자 구분)
+-- [신규 확장 컬럼]
+-- TRGET_CN - 처리대상 건수/범위 (⑤ 보강)
+-- PROCESS_RESULT - 처리결과 S:성공 F:실패/차단 (비정상접근 탐지 5유형 적재 겸용)
+-- HASH_VAL - 위·변조 방지 해시 (SHA-256, 개발항목 #39)
+-- PREV_HASH_VAL - 직전 레코드 해시 (해시체인)
+--
+-- [보관·파기] 730일 보관 전제. 월 단위 RANGE COLUMNS 파티셔닝으로
+-- 파기 배치가 만료 파티션을 ALTER TABLE ... DROP PARTITION 으로 제거
+-- (개별 DELETE 미사용). 애플리케이션 계층에 UPDATE/DELETE statement 미정의.
+-- [분리 원칙] COMTNSYSLOG 의 7일 삭제정책은 본 테이블에 절대 적용하지 않는다.
+-- =====================================================================
+
+CREATE TABLE PERSONAL_DATA_ACCESS_LOG
+(
+ LOG_ID BIGINT NOT NULL AUTO_INCREMENT COMMENT '로그 일련번호',
+ CONECT_ID VARCHAR(20) NOT NULL COMMENT '① 사용자 식별자(ID)',
+ CONECT_DT DATETIME NOT NULL COMMENT '② 접속일시(파티션 키)',
+ CONECT_IP VARCHAR(45) NOT NULL COMMENT '③ 접속지 정보(IP, IPv6 대응)',
+ JOB_SE_CODE VARCHAR(30) NOT NULL COMMENT '④ 수행업무 유형 코드',
+ JOB_SE_NM VARCHAR(100) NOT NULL COMMENT '④ 수행업무 유형 명(화면/기능명)',
+ TRGET_ID VARCHAR(50) NULL COMMENT '⑤ 처리대상 식별자',
+ TRGET_CN VARCHAR(200) NULL COMMENT '⑤ 처리대상 건수/범위',
+ DML_GUBUN CHAR(1) NOT NULL COMMENT '처리구분 S:조회 I:등록 U:수정 D:삭제',
+ PROCESS_RESULT CHAR(1) NOT NULL DEFAULT 'S' COMMENT '처리결과 S:성공 F:실패/차단',
+ USER_SE VARCHAR(10) NULL COMMENT '처리자 구분',
+ HASH_VAL VARCHAR(64) NOT NULL COMMENT '위·변조 방지 해시(SHA-256 Hex)',
+ PREV_HASH_VAL VARCHAR(64) NULL COMMENT '직전 레코드 해시(해시체인)',
+ PRIMARY KEY (LOG_ID, CONECT_DT),
+ KEY IDX_PDAL_01 (CONECT_ID, CONECT_DT), -- 접속자 ID 검색 (현행 CONECT_MTHD LIKE 결함 해소)
+ KEY IDX_PDAL_02 (CONECT_DT), -- 기간 검색 / 파티션 프루닝
+ KEY IDX_PDAL_03 (TRGET_ID, CONECT_DT), -- 처리대상 추적
+ KEY IDX_PDAL_04 (JOB_SE_CODE, CONECT_DT) -- 업무유형/이상탐지유형 집계
+)
+ENGINE=InnoDB DEFAULT CHARSET=utf8
+COMMENT='개인정보 접속기록 표준 테이블(PERSONAL_DATA_DML_HISTORY 승격, 730일 보관)'
+PARTITION BY RANGE COLUMNS(CONECT_DT)
+(
+ -- 파티션 키(CONECT_DT)를 PK에 포함해야 RANGE COLUMNS 파티셔닝 성립 (MySQL/MariaDB 제약)
+ -- 월 단위 사전 생성. 유지보수 배치가 매월 다음 달 파티션을 ADD PARTITION 으로 선행 생성하고,
+ -- 파기 배치(월 1회)가 보관기간(기본 730일) 경과 파티션을 DROP PARTITION 으로 제거한다.
+ -- ※ MAXVALUE 파티션은 두지 않는다 — 만료 데이터가 catch-all 에 갇혀 DROP 불가해지는 것을 방지.
+ PARTITION p202607 VALUES LESS THAN ('2026-08-01'),
+ PARTITION p202608 VALUES LESS THAN ('2026-09-01'),
+ PARTITION p202609 VALUES LESS THAN ('2026-10-01'),
+ PARTITION p202610 VALUES LESS THAN ('2026-11-01'),
+ PARTITION p202611 VALUES LESS THAN ('2026-12-01'),
+ PARTITION p202612 VALUES LESS THAN ('2027-01-01'),
+ PARTITION p202701 VALUES LESS THAN ('2027-02-01'),
+ PARTITION p202702 VALUES LESS THAN ('2027-03-01')
+)
+;
+
+-- ---------------------------------------------------------------------
+-- [유지보수 참고] 월별 파티션 추가/삭제 예시 (배치가 동적으로 수행)
+--
+-- -- 다음 달 파티션 선행 생성
+-- ALTER TABLE PERSONAL_DATA_ACCESS_LOG
+-- ADD PARTITION (PARTITION p202703 VALUES LESS THAN ('2027-04-01'));
+--
+-- -- 보관기간(730일) 경과 파티션 파기
+-- ALTER TABLE PERSONAL_DATA_ACCESS_LOG DROP PARTITION p202607;
+-- ---------------------------------------------------------------------
diff --git a/src/script/mysql/ddl/privacy.pdal_purge_history_create_mysql.sql b/src/script/mysql/ddl/privacy.pdal_purge_history_create_mysql.sql
new file mode 100644
index 0000000..cb430ef
--- /dev/null
+++ b/src/script/mysql/ddl/privacy.pdal_purge_history_create_mysql.sql
@@ -0,0 +1,28 @@
+-- =====================================================================
+-- 개인정보 접속기록 파기·증설 이력 : PDAL_PURGE_HISTORY
+-- 문화품앗이 개인정보보호 기능개선 사업 (SFR-004 / DBR-001)
+-- 개발항목 #37 - 담당: arko-privacy-auditlog (Fork B)
+-- 작성일: 2026-07-21 DB: MySQL 8 / MariaDB
+-- ---------------------------------------------------------------------
+-- [목적] PERSONAL_DATA_ACCESS_LOG 의 보관정책(730일) 파기 배치가 수행한
+-- 파티션 DROP(파기)·ADD(선행 증설) 내역을 증빙으로 기록한다.
+-- 개인정보 접속기록의 유일한 삭제 경로가 "파티션 DROP"임을 감사에서 입증하는 근거.
+--
+-- [원칙] 접속기록 본문(PERSONAL_DATA_ACCESS_LOG)에는 UPDATE/DELETE 를 정의하지 않으며,
+-- 파기는 개별 DELETE 가 아니라 파티션 단위 DROP 으로만 수행한다(개발항목 #37).
+-- =====================================================================
+
+CREATE TABLE PDAL_PURGE_HISTORY
+(
+ PURGE_ID BIGINT NOT NULL AUTO_INCREMENT COMMENT '이력 일련번호',
+ PARTITION_NAME VARCHAR(64) NOT NULL COMMENT '대상 파티션명(예: p202607)',
+ BOUNDARY_DT VARCHAR(30) NULL COMMENT '파티션 상한 경계값(LESS THAN, 예: 2026-08-01)',
+ PURGE_TYPE VARCHAR(10) NOT NULL COMMENT '유형 DROP:파기 ADD:선행증설',
+ PURGE_DT DATETIME NOT NULL COMMENT '수행 일시',
+ NOTE VARCHAR(300) NULL COMMENT '비고(보관기간·사유)',
+ PRIMARY KEY (PURGE_ID),
+ KEY IDX_PDAL_PURGE_01 (PURGE_DT),
+ KEY IDX_PDAL_PURGE_02 (PARTITION_NAME)
+)
+ENGINE=InnoDB DEFAULT CHARSET=utf8
+COMMENT='개인정보 접속기록 파티션 파기·증설 이력(개발항목 #37, 파기 증빙)';
diff --git a/src/script/mysql/privacy.install_mysql.sql b/src/script/mysql/privacy.install_mysql.sql
new file mode 100644
index 0000000..ff0fe4a
--- /dev/null
+++ b/src/script/mysql/privacy.install_mysql.sql
@@ -0,0 +1,509 @@
+-- #####################################################################
+-- arko_renual 설치용 통합 SQL (개인정보보호 기능개선 사업 · arko 대비 변동분)
+-- 대상 DB : MySQL 5.7 / MariaDB (스키마 csv)
+-- 구성 : [PART 1] DDL(테이블 생성·변경) → [PART 2] DML(기준/시드 데이터)
+-- 실행순서: 반드시 PART 1 → PART 2 순서로 실행(테이블 생성 후 시드 적재)
+-- 주의 : 신규 설치용. 기존 테이블이 있는 환경 재실행 시 CREATE 중복 오류 가능
+-- (IF NOT EXISTS 미지정 테이블). 그런 환경은 PART 2(DML)만 실행하세요.
+-- #####################################################################
+
+
+-- =====================================================================
+-- PART 1. DDL (테이블 생성 / 컬럼 확장) ※ 시드 INSERT는 PART 2로 분리
+-- =====================================================================
+
+-- ─────────────────────────────────────────────────────────────
+-- [privacy.account_mng_policy_create_mysql.sql]
+-- ─────────────────────────────────────────────────────────────
+-- =====================================================================
+-- 계정 관리 정책 : ACCOUNT_MNG_POLICY (단일행 정책 테이블)
+-- 문화품앗이 개인정보보호 기능개선 사업 (SFR-002, 장기 미사용 계정 관리)
+-- 개발항목 #13(정책 설정) + #9(휴면 기준일수 정책값화) - 담당: arko-privacy-authctrl (Fork A)
+-- 작성일: 2026-07-21 DB: MySQL 8 / MariaDB
+-- ---------------------------------------------------------------------
+-- [용도] 휴면 기준일수·사전알림 시점·삭제 보존기간·배치주기 등 계정 관리 정책을
+-- 하드코딩이 아닌 관리자 설정값으로 운영하기 위한 단일행 정책 테이블.
+-- [#9] 휴면 select(EgovLoginUsr_SQL_Mysql.xml)의 365 하드코딩을
+-- (SELECT DORMANT_DAY FROM ACCOUNT_MNG_POLICY) 로 대체(미설정 시 365 fallback).
+-- [#10] ALERT_D1/D2/D3(기본 30/7/0) = 휴면예정 D-30/D-7/D-Day 사전알림 시점.
+-- ADMIN_EMAIL = 사전알림 관리자 수신 주소.
+-- =====================================================================
+
+CREATE TABLE IF NOT EXISTS ACCOUNT_MNG_POLICY (
+ POLICY_ID INT NOT NULL DEFAULT 1 COMMENT '정책 식별자(단일행=1)',
+ DORMANT_DAY INT NOT NULL DEFAULT 365 COMMENT '휴면 전환 기준 미접속 일수(#9)',
+ ALERT_D1 INT NOT NULL DEFAULT 30 COMMENT '사전알림 1차(휴면 D-일수)',
+ ALERT_D2 INT NOT NULL DEFAULT 7 COMMENT '사전알림 2차(휴면 D-일수)',
+ ALERT_D3 INT NOT NULL DEFAULT 0 COMMENT '사전알림 3차(D-Day, 0=당일)',
+ DELETE_PRESERVE_DAY INT NOT NULL DEFAULT 365 COMMENT '휴면/탈퇴 후 삭제 보존기간(일)',
+ BATCH_CYCLE VARCHAR(20) NOT NULL DEFAULT 'DAILY' COMMENT '배치 주기(DAILY/WEEKLY/MONTHLY)',
+ ADMIN_EMAIL VARCHAR(200) NULL COMMENT '사전알림 관리자 수신 이메일',
+ UPD_ID VARCHAR(20) NULL COMMENT '최종 수정자',
+ UPD_DT DATETIME NULL COMMENT '최종 수정일시',
+ PRIMARY KEY (POLICY_ID)
+) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4
+ COMMENT='계정 관리 정책(개인정보보호 SFR-002, 단일행)';
+
+-- 기본 정책 시드(없을 때만). 기준 365일·알림 D-30/7/0·보존 365일.
+
+-- ─────────────────────────────────────────────────────────────
+-- [privacy.account_unlock_history_create_mysql.sql]
+-- ─────────────────────────────────────────────────────────────
+-- =====================================================================
+-- 계정 잠금 해제 이력 : ACCOUNT_UNLOCK_HISTORY
+-- 문화품앗이 개인정보보호 기능개선 사업 (SER-001, 계정 잠금 정책 정비)
+-- 개발항목 #24 - 담당: arko-privacy-authctrl (Fork A)
+-- 작성일: 2026-07-21 DB: MySQL 8 / MariaDB
+-- ---------------------------------------------------------------------
+-- [목적] 잠긴 계정 해제를 DB 직접조작이 아니라 관리자 화면(EgovAccountUnlockManage)
+-- 에서 처리하고, 해제자·해제일시·대상·사유를 이력으로 남긴다.
+-- [무결성] 애플리케이션 계층에 UPDATE/DELETE statement 미정의(INSERT/SELECT 한정).
+-- =====================================================================
+
+CREATE TABLE ACCOUNT_UNLOCK_HISTORY (
+ UNLOCK_HIST_ID BIGINT NOT NULL AUTO_INCREMENT COMMENT '해제 이력 일련번호(PK)',
+ TRGET_ID VARCHAR(50) NULL COMMENT '해제 대상 계정 ID(전체해제 시 NULL 가능)',
+ TRGET_SE VARCHAR(10) NULL COMMENT '대상 회원구분 GNR/GRO/ENT/USR',
+ UNLOCK_TY CHAR(1) NOT NULL DEFAULT 'I' COMMENT '해제 유형 I:개별 A:전체',
+ UNLOCK_USER_ID VARCHAR(50) NULL COMMENT '해제 처리자(관리자) ID',
+ UNLOCK_RESN VARCHAR(500) NULL COMMENT '해제 사유',
+ UNLOCK_IP VARCHAR(45) NULL COMMENT '해제자 접속 IP(IPv6 대응)',
+ UNLOCK_DT DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP COMMENT '해제 일시',
+ PRIMARY KEY (UNLOCK_HIST_ID),
+ KEY IX_AUH_TRGET (TRGET_ID, UNLOCK_DT),
+ KEY IX_AUH_USER (UNLOCK_USER_ID, UNLOCK_DT),
+ KEY IX_AUH_DT (UNLOCK_DT)
+) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4
+ COMMENT='계정 잠금 해제 이력(개인정보보호 SER-001 #24)';
+
+
+-- =====================================================================
+-- 계정 잠금 임계값 10회 → 5회 (#24)
+-- 임계값은 하드코딩이 아니라 LOGIN_LOCK_COUNT.LOCK_COUNT(단일행) 데이터에서 조회됨
+-- (loginDAO.selectFailTotalCount). 아래 UPDATE 로 운영 정책값을 5회로 조정한다.
+-- ※ 소스(EgovLoginController)에는 값 부재 시 기본 5회 fallback 이 반영되어 있음.
+-- =====================================================================
+UPDATE LOGIN_LOCK_COUNT SET LOCK_COUNT = 5;
+-- 행이 없을 경우 대비(최초 세팅):
+-- INSERT INTO LOGIN_LOCK_COUNT (LOCK_COUNT) SELECT 5 FROM DUAL
+-- WHERE NOT EXISTS (SELECT 1 FROM LOGIN_LOCK_COUNT);
+
+-- ─────────────────────────────────────────────────────────────
+-- [privacy.author_change_history_create_mysql.sql]
+-- ─────────────────────────────────────────────────────────────
+-- =====================================================================
+-- 권한 변경 이력 테이블 : AUTHOR_CHANGE_HISTORY
+-- 문화품앗이 개인정보보호 기능개선 사업 (SFR-001, 역할별 차등 접근/권한 변경 추적)
+-- 개발항목 #4 - 담당: arko-privacy-authctrl (Fork A → 오케스트레이터 완료)
+-- 작성일: 2026-07-21 DB: MySQL 8 / MariaDB
+-- ---------------------------------------------------------------------
+-- [목적] 권한 신설·변경·회수(#1 getAuthorities 대상 COMTNEMPLYRSCRTYESTBS 등)의
+-- 모든 변경을 변경자·변경일시·변경전/후 권한과 함께 이력으로 적재한다.
+-- 권한 신청·승인 워크플로(#3)의 승인 반영 시점에 권한반영과 단일 트랜잭션으로 적재.
+--
+-- [보관·파기] 3년(1,095일) 이상 보관 전제. 월 1회 @Scheduled 파기 배치(#5)가
+-- 보관기간(1,095일) 경과 이력만 선별 파기한다. 개별 삭제 API 미제공.
+-- 다운로드 시 사유 입력 필수 + 다운로드 이력 적재(#6)는 별도 처리.
+--
+-- [무결성] 이력 위·변조 억제를 위해 변경전/후 권한을 함께 보관(사후 대사 가능).
+-- 애플리케이션 계층에 UPDATE statement 미정의(INSERT/SELECT/파기 DELETE 한정).
+-- =====================================================================
+
+CREATE TABLE AUTHOR_CHANGE_HISTORY (
+ HIST_ID BIGINT NOT NULL AUTO_INCREMENT COMMENT '이력 일련번호(PK)',
+ TRGET_UNIQ_ID VARCHAR(20) NOT NULL COMMENT '변경 대상자 고유식별자(ESNTL_ID)',
+ TRGET_ID VARCHAR(20) NULL COMMENT '변경 대상자 계정ID(참고용)',
+ CHG_SE_CODE CHAR(1) NOT NULL COMMENT '변경구분 G:부여(grant) R:회수(revoke) M:변경(modify)',
+ BEFORE_AUTHOR VARCHAR(1000) NULL COMMENT '변경 전 권한(코드 CSV 또는 스냅샷)',
+ AFTER_AUTHOR VARCHAR(1000) NULL COMMENT '변경 후 권한(코드 CSV 또는 스냅샷)',
+ CHG_RESN VARCHAR(500) NULL COMMENT '변경 사유(신청/승인 워크플로 연계 #3)',
+ APROVL_ID VARCHAR(20) NULL COMMENT '승인자 계정ID(승인 워크플로 #3)',
+ CHANGER_UNIQ_ID VARCHAR(20) NOT NULL COMMENT '변경자(처리자) 고유식별자',
+ CHANGER_ID VARCHAR(20) NULL COMMENT '변경자 계정ID(참고용)',
+ CHANGER_IP VARCHAR(45) NULL COMMENT '변경자 접속 IP(IPv6 대응)',
+ CHG_DT DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP COMMENT '변경 일시',
+ PRIMARY KEY (HIST_ID),
+ KEY IX_ACH_TRGET (TRGET_UNIQ_ID, CHG_DT),
+ KEY IX_ACH_CHANGER (CHANGER_UNIQ_ID, CHG_DT),
+ KEY IX_ACH_CHGDT (CHG_DT)
+) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4
+ COMMENT='권한 변경 이력(개인정보보호 SFR-001, 3년 이상 보관)';
+
+-- ─────────────────────────────────────────────────────────────
+-- [privacy.author_workflow_create_mysql.sql]
+-- ─────────────────────────────────────────────────────────────
+-- =====================================================================
+-- 권한 신청·승인 워크플로 + 권한이력 다운로드 이력
+-- 문화품앗이 개인정보보호 기능개선 사업 (SFR-001, 권한 신설·변경·회수 승인통제)
+-- 개발항목 #3(신청·승인 워크플로) / #6(권한이력 조회·다운로드) - 담당: arko-privacy-authctrl (Fork A 6차)
+-- 작성일: 2026-07-21 DB: MySQL 8 / MariaDB
+-- ---------------------------------------------------------------------
+-- [#3] AUTHOR_CHANGE_REQUEST : 권한 변경 신청→승인/반려 워크플로.
+-- 승인(A) 시 권한 반영(COMTNEMPLYRSCRTYESTBS)과 이력 적재(AUTHOR_CHANGE_HISTORY)를
+-- 단일 트랜잭션으로 처리한다(egovframework.com..*Impl AOP tx advisor).
+-- [#6] AUTHOR_HIST_DOWNLOAD_HISTORY : 권한이력 다운로드 증빙(사유 필수).
+-- =====================================================================
+
+CREATE TABLE AUTHOR_CHANGE_REQUEST (
+ REQUEST_ID BIGINT NOT NULL AUTO_INCREMENT COMMENT '신청 일련번호(PK)',
+ TRGET_UNIQ_ID VARCHAR(20) NOT NULL COMMENT '권한변경 대상자 고유식별자(ESNTL_ID)',
+ TRGET_ID VARCHAR(20) NULL COMMENT '권한변경 대상자 계정ID(참고용)',
+ REQUEST_SE CHAR(1) NOT NULL COMMENT '신청구분 N:신설(부여) C:변경 R:회수',
+ BEFORE_AUTHOR VARCHAR(1000) NULL COMMENT '신청 시점 현재 권한(스냅샷)',
+ AFTER_AUTHOR VARCHAR(1000) NULL COMMENT '신청 목표 권한(코드/CSV)',
+ REQUEST_RESN VARCHAR(500) NOT NULL COMMENT '신청 사유(필수)',
+ REQSTER_ID VARCHAR(20) NOT NULL COMMENT '신청자 계정ID',
+ REQSTER_UNIQ_ID VARCHAR(20) NULL COMMENT '신청자 고유식별자',
+ REQUEST_DT DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP COMMENT '신청 일시',
+ CONFM_STTUS CHAR(1) NOT NULL DEFAULT 'R' COMMENT '처리상태 R:신청 A:승인 J:반려',
+ CONFM_ID VARCHAR(20) NULL COMMENT '승인/반려 처리자 계정ID',
+ CONFM_UNIQ_ID VARCHAR(20) NULL COMMENT '승인/반려 처리자 고유식별자',
+ CONFM_DT DATETIME NULL COMMENT '승인/반려 일시',
+ CONFM_RESN VARCHAR(500) NULL COMMENT '반려 사유',
+ PRIMARY KEY (REQUEST_ID),
+ KEY IX_ACR_TRGET (TRGET_UNIQ_ID, REQUEST_DT),
+ KEY IX_ACR_STTUS (CONFM_STTUS, REQUEST_DT)
+) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4
+ COMMENT='권한 변경 신청·승인 워크플로(개인정보보호 SFR-001 #3)';
+
+CREATE TABLE AUTHOR_HIST_DOWNLOAD_HISTORY (
+ DWLD_ID BIGINT NOT NULL AUTO_INCREMENT COMMENT '다운로드 일련번호(PK)',
+ DOWNLOADER_ID VARCHAR(20) NOT NULL COMMENT '다운로드자 계정ID',
+ DOWNLOADER_UNIQ_ID VARCHAR(20) NULL COMMENT '다운로드자 고유식별자',
+ DOWNLOADER_IP VARCHAR(45) NULL COMMENT '다운로드자 접속 IP(IPv6 대응)',
+ DWLD_RESN VARCHAR(500) NOT NULL COMMENT '다운로드 사유(필수·미입력 차단)',
+ SEARCH_COND VARCHAR(1000) NULL COMMENT '다운로드 시 검색조건 스냅샷',
+ DWLD_CNT INT NOT NULL DEFAULT 0 COMMENT '다운로드 건수',
+ DWLD_DT DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP COMMENT '다운로드 일시',
+ PRIMARY KEY (DWLD_ID),
+ KEY IX_AHD_DWLD (DOWNLOADER_ID, DWLD_DT)
+) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4
+ COMMENT='권한이력 다운로드 증빙 이력(개인정보보호 SFR-001 #6)';
+
+-- ─────────────────────────────────────────────────────────────
+-- [privacy.job_mapping_create_mysql.sql]
+-- ─────────────────────────────────────────────────────────────
+-- =====================================================================
+-- 개인정보 처리 업무유형 매핑 테이블 : PDAL_JOB_MAPPING
+-- 문화품앗이 개인정보보호 기능개선 사업 (SFR-004 / SER-002)
+-- 개발항목 #32 - 담당: arko-privacy-auditlog (Fork B)
+-- 작성일: 2026-07-21 DB: MySQL 8 / MariaDB
+-- ---------------------------------------------------------------------
+-- [목적] 접속기록 수집(#31 PersonalDataLogAspect)이 가로챈 서비스 메서드를
+-- 업무코드(JOB_SE_CODE)·업무명(JOB_SE_NM)으로 매핑한다.
+-- AOP pointcut 범위(개인정보 처리 서비스 패키지) 안에서 신규 화면이 추가되면
+-- 이 테이블에 행만 등록하면 수집되며, Aspect/pointcut 소스 수정이 불요하다.
+--
+-- [매칭 규칙] Aspect가 (대상클래스 단순명, 메서드명)으로 조회.
+-- - TARGET_CLASS : 서비스 impl 단순 클래스명(예: EgovMberManageServiceImpl) 또는 FQN
+-- - METHOD_PTTRN : 메서드명 접두 매칭(예: 'select','insertMber') 또는 '*'(전체)
+-- - DML_GUBUN : 지정 시 우선, NULL이면 메서드명 접두로 유도(insert*→I/update*→U/delete*→D/그외 S)
+-- - SORT_ORDR : 다중 매칭 시 낮은 값 우선(구체 규칙을 위로)
+--
+-- ※ URL 패턴 기반 수집이 필요하면(서블릿 필터형) 동일 테이블에 URL_PTTRN 컬럼을 추가해 확장.
+-- =====================================================================
+
+CREATE TABLE PDAL_JOB_MAPPING (
+ MAPPING_ID BIGINT NOT NULL AUTO_INCREMENT COMMENT '매핑 일련번호(PK)',
+ TARGET_CLASS VARCHAR(200) NOT NULL COMMENT '대상 서비스 impl 단순 클래스명 또는 FQN',
+ METHOD_PTTRN VARCHAR(100) NOT NULL DEFAULT '*' COMMENT '메서드명 접두 매칭(또는 * 전체)',
+ JOB_SE_CODE VARCHAR(30) NOT NULL COMMENT '수행업무 유형 코드',
+ JOB_SE_NM VARCHAR(100) NOT NULL COMMENT '수행업무 유형(화면·기능명)',
+ DML_GUBUN CHAR(1) NULL COMMENT '처리구분 강제값 S/I/U/D (NULL=메서드명 유도)',
+ SORT_ORDR INT NOT NULL DEFAULT 100 COMMENT '매칭 우선순위(낮을수록 우선)',
+ USE_AT CHAR(1) NOT NULL DEFAULT 'Y' COMMENT '사용여부 Y/N',
+ RM VARCHAR(300) NULL COMMENT '비고',
+ PRIMARY KEY (MAPPING_ID),
+ KEY IX_PJM_LOOKUP (USE_AT, TARGET_CLASS, SORT_ORDR)
+) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4
+ COMMENT='개인정보 처리 업무유형 매핑(접속기록 #31/#32)';
+
+-- ---------------------------------------------------------------------
+-- 초기 매핑 시드 : 현행 개인정보 처리 서비스(#31 pointcut 대상과 일치)
+-- 신규 개인정보 화면 추가 시 아래 형식으로 행만 추가하면 수집된다.
+-- ---------------------------------------------------------------------
+
+-- ─────────────────────────────────────────────────────────────
+-- [privacy.download_history_create_mysql.sql]
+-- ─────────────────────────────────────────────────────────────
+-- =====================================================================
+-- 개인정보 접속기록 다운로드 이력 : PDAL_DOWNLOAD_HISTORY
+-- 문화품앗이 개인정보보호 기능개선 사업 (SFR-004 / SER-002)
+-- 개발항목 #42 - 담당: arko-privacy-auditlog (Fork B)
+-- 작성일: 2026-07-21 DB: MySQL 8 / MariaDB
+-- ---------------------------------------------------------------------
+-- [목적] 개인정보 접속기록(PERSONAL_DATA_ACCESS_LOG) 조회결과 다운로드 시,
+-- 다운로드 사유(필수)·다운로드자·일시·IP·건수를 이력으로 남긴다.
+-- 사유 미입력 시 다운로드는 서비스 계층에서 차단(본 테이블 미적재).
+-- [원칙] 개인정보 본문 미적재 — 다운로드 검색조건 요약(SEARCH_COND)만 보관.
+-- 이 테이블은 INSERT/SELECT 만 수행(위·변조 방지, UPDATE/DELETE 미정의).
+-- =====================================================================
+
+CREATE TABLE PDAL_DOWNLOAD_HISTORY (
+ DL_ID BIGINT NOT NULL AUTO_INCREMENT COMMENT '다운로드 이력 일련번호(PK)',
+ DOWNLOADER_ID VARCHAR(20) NOT NULL COMMENT '다운로드자 계정ID',
+ DOWNLOAD_DT DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP COMMENT '다운로드 일시',
+ DOWNLOADER_IP VARCHAR(45) NULL COMMENT '다운로드자 접속 IP(IPv6 대응)',
+ DL_REASON VARCHAR(500) NOT NULL COMMENT '다운로드 사유(필수 — 미입력 시 차단)',
+ SEARCH_COND VARCHAR(1000) NULL COMMENT '다운로드 검색조건 요약(개인정보 본문 미포함)',
+ RESULT_CNT INT NOT NULL DEFAULT 0 COMMENT '다운로드 건수',
+ REG_DT DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP COMMENT '등록 일시',
+ PRIMARY KEY (DL_ID),
+ KEY IX_PDDH_DT (DOWNLOAD_DT),
+ KEY IX_PDDH_USER (DOWNLOADER_ID, DOWNLOAD_DT)
+) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4
+ COMMENT='개인정보 접속기록 다운로드 이력(#42, SER-002)';
+
+-- [Oracle] 시퀀스 필요:
+-- CREATE SEQUENCE PDAL_DOWNLOAD_HISTORY_SEQ START WITH 1 INCREMENT BY 1 NOCACHE;
+
+-- ─────────────────────────────────────────────────────────────
+-- [privacy.inspection_report_create_mysql.sql]
+-- ─────────────────────────────────────────────────────────────
+-- =====================================================================
+-- 개인정보 접속기록 정기점검 리포트 : PDAL_INSPECTION_REPORT
+-- 문화품앗이 개인정보보호 기능개선 사업 (SFR-004 / SER-002)
+-- 개발항목 #43 - 담당: arko-privacy-auditlog (Fork B)
+-- 작성일: 2026-07-21 DB: MySQL 8 / MariaDB
+-- ---------------------------------------------------------------------
+-- [목적] 월 1회 정기점검 배치가 전월분 접속기록을 집계해 1행으로 적재한다.
+-- "월 1회 이상 점검 내역"을 화면 출력으로 제출(개인정보보호위 점검 대응).
+--
+-- [집계원] PERSONAL_DATA_ACCESS_LOG(#30) + 해시체인 무결성 검증(#40) 재사용.
+-- [자리표시] 이상징후 5유형(#35)·다운로드 이력(#42)은 미구현 → 컬럼만 두고 배치가 0/요약만 적재.
+-- [화면입력] INSPECTOR_ID/INSPECT_RESULT/ACTN_MATTER 는 배치가 아닌 점검자가 화면(#48)에서 입력.
+-- =====================================================================
+
+CREATE TABLE PDAL_INSPECTION_REPORT (
+ RPT_ID BIGINT NOT NULL AUTO_INCREMENT COMMENT '리포트 일련번호(PK)',
+ RPT_YM CHAR(6) NOT NULL COMMENT '점검 대상 연월(전월, yyyyMM)',
+ PERIOD_FROM DATETIME NOT NULL COMMENT '집계 시작일시',
+ PERIOD_TO DATETIME NOT NULL COMMENT '집계 종료일시',
+ TOTAL_LOG_CNT INT NOT NULL DEFAULT 0 COMMENT '총 접속기록 건수',
+ FAIL_CNT INT NOT NULL DEFAULT 0 COMMENT '처리결과 F(실패/차단) 건수 = 이상징후 일부',
+ ABNORMAL_SUMMARY VARCHAR(2000) NULL COMMENT '이상징후 5유형 요약(#35 연계 전 자리표시)',
+ INTEGRITY_RESULT VARCHAR(20) NOT NULL DEFAULT 'OK' COMMENT '해시체인 무결성: OK / VIOLATION',
+ INTEGRITY_MISMATCH_ID BIGINT NULL COMMENT '최초 불일치 LOG_ID(위반 시)',
+ INTEGRITY_TOTAL_CNT INT NOT NULL DEFAULT 0 COMMENT '무결성 검증 대상 건수',
+ HANDLER_SUMMARY VARCHAR(4000) NULL COMMENT '취급자별 처리현황 요약',
+ DOWNLOAD_CNT INT NOT NULL DEFAULT 0 COMMENT '다운로드 이력 건수(#42 연계 전 0)',
+ INSPECTOR_ID VARCHAR(20) NULL COMMENT '점검자 계정ID(화면 입력, #44)',
+ INSPECT_RESULT VARCHAR(500) NULL COMMENT '점검결과(화면 입력, #44)',
+ ACTN_MATTER VARCHAR(1000) NULL COMMENT '조치사항(화면 입력, #44)',
+ INSPECT_DT DATETIME NULL COMMENT '점검일시(점검결과 입력 시각, #44 감사증빙)',
+ REG_DT DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP COMMENT '리포트 생성일시',
+ PRIMARY KEY (RPT_ID),
+ UNIQUE KEY UX_PIR_YM (RPT_YM),
+ KEY IX_PIR_REGDT (REG_DT)
+) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4
+ COMMENT='개인정보 접속기록 정기점검 리포트(#43, 월 1회)';
+
+-- ─────────────────────────────────────────────────────────────
+-- [privacy.pdal_create_mysql.sql]
+-- ─────────────────────────────────────────────────────────────
+-- =====================================================================
+-- 개인정보 접속기록 표준 테이블 : PERSONAL_DATA_ACCESS_LOG
+-- 문화품앗이 개인정보보호 기능개선 사업 (SFR-004 / DBR-001 / SER-002)
+-- 개발항목 #30 - 담당: arko-privacy-auditlog (Fork B)
+-- 작성일: 2026-07-21 DB: MySQL 8 / MariaDB
+-- ---------------------------------------------------------------------
+-- [설계 원칙] 신규 스키마 설계가 아니라 기존 표준 PERSONAL_DATA_DML_HISTORY 를
+-- 표준 접속기록 테이블로 "승격" + 보관/무결성 컬럼 확장이다.
+--
+-- [기존 PERSONAL_DATA_DML_HISTORY → PERSONAL_DATA_ACCESS_LOG 컬럼 매핑]
+-- CHG_USER_ID → CONECT_ID (① 사용자 식별자)
+-- CREATE_DATE → CONECT_DT (② 접속일시, SYSDATE())
+-- CHG_CONECT_IP → CONECT_IP (③ 접속지 IP) * VARCHAR(45) IPv6 대응
+-- UI_GUBUN → JOB_SE_CODE/NM (④ 수행업무 유형) * 업무유형 매핑 테이블로 코드/명 분리
+-- ESNTL_ID → TRGET_ID (⑤ 처리대상 식별자)
+-- DML_GUBUN → DML_GUBUN (처리구분 S/I/U/D 그대로 계승)
+-- CHG_USER_SE → USER_SE (처리자 구분)
+-- [신규 확장 컬럼]
+-- TRGET_CN - 처리대상 건수/범위 (⑤ 보강)
+-- PROCESS_RESULT - 처리결과 S:성공 F:실패/차단 (비정상접근 탐지 5유형 적재 겸용)
+-- HASH_VAL - 위·변조 방지 해시 (SHA-256, 개발항목 #39)
+-- PREV_HASH_VAL - 직전 레코드 해시 (해시체인)
+--
+-- [보관·파기] 730일 보관 전제. 월 단위 RANGE COLUMNS 파티셔닝으로
+-- 파기 배치가 만료 파티션을 ALTER TABLE ... DROP PARTITION 으로 제거
+-- (개별 DELETE 미사용). 애플리케이션 계층에 UPDATE/DELETE statement 미정의.
+-- [분리 원칙] COMTNSYSLOG 의 7일 삭제정책은 본 테이블에 절대 적용하지 않는다.
+-- =====================================================================
+
+CREATE TABLE PERSONAL_DATA_ACCESS_LOG
+(
+ LOG_ID BIGINT NOT NULL AUTO_INCREMENT COMMENT '로그 일련번호',
+ CONECT_ID VARCHAR(20) NOT NULL COMMENT '① 사용자 식별자(ID)',
+ CONECT_DT DATETIME NOT NULL COMMENT '② 접속일시(파티션 키)',
+ CONECT_IP VARCHAR(45) NOT NULL COMMENT '③ 접속지 정보(IP, IPv6 대응)',
+ JOB_SE_CODE VARCHAR(30) NOT NULL COMMENT '④ 수행업무 유형 코드',
+ JOB_SE_NM VARCHAR(100) NOT NULL COMMENT '④ 수행업무 유형 명(화면/기능명)',
+ TRGET_ID VARCHAR(50) NULL COMMENT '⑤ 처리대상 식별자',
+ TRGET_CN VARCHAR(200) NULL COMMENT '⑤ 처리대상 건수/범위',
+ DML_GUBUN CHAR(1) NOT NULL COMMENT '처리구분 S:조회 I:등록 U:수정 D:삭제',
+ PROCESS_RESULT CHAR(1) NOT NULL DEFAULT 'S' COMMENT '처리결과 S:성공 F:실패/차단',
+ USER_SE VARCHAR(10) NULL COMMENT '처리자 구분',
+ HASH_VAL VARCHAR(64) NOT NULL COMMENT '위·변조 방지 해시(SHA-256 Hex)',
+ PREV_HASH_VAL VARCHAR(64) NULL COMMENT '직전 레코드 해시(해시체인)',
+ PRIMARY KEY (LOG_ID, CONECT_DT),
+ KEY IDX_PDAL_01 (CONECT_ID, CONECT_DT), -- 접속자 ID 검색 (현행 CONECT_MTHD LIKE 결함 해소)
+ KEY IDX_PDAL_02 (CONECT_DT), -- 기간 검색 / 파티션 프루닝
+ KEY IDX_PDAL_03 (TRGET_ID, CONECT_DT), -- 처리대상 추적
+ KEY IDX_PDAL_04 (JOB_SE_CODE, CONECT_DT) -- 업무유형/이상탐지유형 집계
+)
+ENGINE=InnoDB DEFAULT CHARSET=utf8
+COMMENT='개인정보 접속기록 표준 테이블(PERSONAL_DATA_DML_HISTORY 승격, 730일 보관)'
+PARTITION BY RANGE COLUMNS(CONECT_DT)
+(
+ -- 파티션 키(CONECT_DT)를 PK에 포함해야 RANGE COLUMNS 파티셔닝 성립 (MySQL/MariaDB 제약)
+ -- 월 단위 사전 생성. 유지보수 배치가 매월 다음 달 파티션을 ADD PARTITION 으로 선행 생성하고,
+ -- 파기 배치(월 1회)가 보관기간(기본 730일) 경과 파티션을 DROP PARTITION 으로 제거한다.
+ -- ※ MAXVALUE 파티션은 두지 않는다 — 만료 데이터가 catch-all 에 갇혀 DROP 불가해지는 것을 방지.
+ PARTITION p202607 VALUES LESS THAN ('2026-08-01'),
+ PARTITION p202608 VALUES LESS THAN ('2026-09-01'),
+ PARTITION p202609 VALUES LESS THAN ('2026-10-01'),
+ PARTITION p202610 VALUES LESS THAN ('2026-11-01'),
+ PARTITION p202611 VALUES LESS THAN ('2026-12-01'),
+ PARTITION p202612 VALUES LESS THAN ('2027-01-01'),
+ PARTITION p202701 VALUES LESS THAN ('2027-02-01'),
+ PARTITION p202702 VALUES LESS THAN ('2027-03-01')
+)
+;
+
+-- ---------------------------------------------------------------------
+-- [유지보수 참고] 월별 파티션 추가/삭제 예시 (배치가 동적으로 수행)
+--
+-- -- 다음 달 파티션 선행 생성
+-- ALTER TABLE PERSONAL_DATA_ACCESS_LOG
+-- ADD PARTITION (PARTITION p202703 VALUES LESS THAN ('2027-04-01'));
+--
+-- -- 보관기간(730일) 경과 파티션 파기
+-- ALTER TABLE PERSONAL_DATA_ACCESS_LOG DROP PARTITION p202607;
+-- ---------------------------------------------------------------------
+
+-- ─────────────────────────────────────────────────────────────
+-- [privacy.pdal_purge_history_create_mysql.sql]
+-- ─────────────────────────────────────────────────────────────
+-- =====================================================================
+-- 개인정보 접속기록 파기·증설 이력 : PDAL_PURGE_HISTORY
+-- 문화품앗이 개인정보보호 기능개선 사업 (SFR-004 / DBR-001)
+-- 개발항목 #37 - 담당: arko-privacy-auditlog (Fork B)
+-- 작성일: 2026-07-21 DB: MySQL 8 / MariaDB
+-- ---------------------------------------------------------------------
+-- [목적] PERSONAL_DATA_ACCESS_LOG 의 보관정책(730일) 파기 배치가 수행한
+-- 파티션 DROP(파기)·ADD(선행 증설) 내역을 증빙으로 기록한다.
+-- 개인정보 접속기록의 유일한 삭제 경로가 "파티션 DROP"임을 감사에서 입증하는 근거.
+--
+-- [원칙] 접속기록 본문(PERSONAL_DATA_ACCESS_LOG)에는 UPDATE/DELETE 를 정의하지 않으며,
+-- 파기는 개별 DELETE 가 아니라 파티션 단위 DROP 으로만 수행한다(개발항목 #37).
+-- =====================================================================
+
+CREATE TABLE PDAL_PURGE_HISTORY
+(
+ PURGE_ID BIGINT NOT NULL AUTO_INCREMENT COMMENT '이력 일련번호',
+ PARTITION_NAME VARCHAR(64) NOT NULL COMMENT '대상 파티션명(예: p202607)',
+ BOUNDARY_DT VARCHAR(30) NULL COMMENT '파티션 상한 경계값(LESS THAN, 예: 2026-08-01)',
+ PURGE_TYPE VARCHAR(10) NOT NULL COMMENT '유형 DROP:파기 ADD:선행증설',
+ PURGE_DT DATETIME NOT NULL COMMENT '수행 일시',
+ NOTE VARCHAR(300) NULL COMMENT '비고(보관기간·사유)',
+ PRIMARY KEY (PURGE_ID),
+ KEY IDX_PDAL_PURGE_01 (PURGE_DT),
+ KEY IDX_PDAL_PURGE_02 (PARTITION_NAME)
+)
+ENGINE=InnoDB DEFAULT CHARSET=utf8
+COMMENT='개인정보 접속기록 파티션 파기·증설 이력(개발항목 #37, 파기 증빙)';
+
+-- ─────────────────────────────────────────────────────────────
+-- [privacy.otp_device_create_mysql.sql]
+-- ─────────────────────────────────────────────────────────────
+-- =====================================================================
+-- OTP(2단계 인증) 비밀키 영구 저장 테이블
+-- 문화품앗이 개인정보보호 기능개선 사업 (SFR-003 / IAR-001, 추가 인증수단 1차 OTP)
+-- 개발항목 No.18 후속 - 담당: arko-privacy-authctrl
+-- 작성일: 2026-07-22 DB: MySQL 5.7/8 / MariaDB
+-- ---------------------------------------------------------------------
+-- [배경] OtpSecretStore 기본 구현이 InMemory(재기동 시 유실)라 이를 대체하는
+-- DB 영속 저장소(DaoOtpSecretStore)가 사용하는 테이블.
+-- [보안] SECRET_ENC 는 OtpSecretCipher(AES-GCM) 암호문(Base64)만 저장한다.
+-- 평문 비밀키/OTP 코드는 저장하지 않는다(SER-003).
+-- [키] UNIQ_ID = 개인정보취급자 고유식별자(ESNTL_ID), 취급자당 1건(PK).
+-- =====================================================================
+
+CREATE TABLE IF NOT EXISTS OTP_DEVICE (
+ UNIQ_ID VARCHAR(20) NOT NULL COMMENT '개인정보취급자 고유식별자(ESNTL_ID, PK)',
+ SECRET_ENC VARCHAR(255) NOT NULL COMMENT 'OTP 비밀키 AES-GCM 암호문(Base64) - 평문 금지',
+ REG_DT DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP COMMENT '등록(발급) 일시',
+ LAST_VERIFY_DT DATETIME NULL COMMENT '최종 검증 성공 일시(참고용)',
+ USE_CNT INT NOT NULL DEFAULT 0 COMMENT '검증 성공 누적 횟수(참고용)',
+ DEVICE_LABEL VARCHAR(100) NULL COMMENT '기기/계정 라벨(참고용)',
+ USE_YN CHAR(1) NOT NULL DEFAULT 'Y' COMMENT '사용여부 Y:사용 N:해지',
+ PRIMARY KEY (UNIQ_ID)
+) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4
+ COMMENT='OTP 2단계 인증 비밀키 영구 저장(개인정보보호 SFR-003 No.18)';
+
+-- ─────────────────────────────────────────────────────────────
+-- [privacy.diapause_emplyr_extend_mysql.sql]
+-- ─────────────────────────────────────────────────────────────
+-- =====================================================================
+-- 휴면(장기 미사용) 대상 확장 : COMTNDIAPAUSEMBER 회원유형 구분 컬럼 추가
+-- 문화품앗이 개인정보보호 기능개선 사업 (SFR-002, 장기 미사용 계정 관리)
+-- 개발항목 #7 - 담당: arko-privacy-authctrl (Fork A)
+-- 작성일: 2026-07-21 DB: MySQL 8 / MariaDB
+-- ---------------------------------------------------------------------
+-- [배경] 기존 휴면 대상은 일반회원(COMTNGNRLMBER)뿐이며, 휴면 테이블
+-- COMTNDIAPAUSEMBER 에는 회원유형 구분 컬럼이 없다. 개인정보취급자
+-- (COMTNEMPLYRINFO)를 휴면 대상에 포함(#7)하면서 일반회원 휴면/해제
+-- 흐름과 혼선되지 않도록 회원유형 구분 컬럼을 신설한다.
+--
+-- [안전] DEFAULT 'GNR'(일반회원)로 신설 → 기존 적재 행은 자동 'GNR' 처리되어
+-- 기존 selectDiapauseList(일반회원 join COMTNGNRLMBER) 흐름 무영향.
+-- 취급자 휴면 행은 'EMP'로 적재하여 별도 select/notify 경로로 처리한다.
+--
+-- [멱등] 재실행 안전을 위해 컬럼 존재 시 무시(수동 확인 후 실행).
+-- =====================================================================
+
+ALTER TABLE COMTNDIAPAUSEMBER
+ ADD COLUMN MBER_TY_CODE CHAR(3) NOT NULL DEFAULT 'GNR'
+ COMMENT '회원유형 GNR:일반회원 EMP:개인정보취급자';
+
+-- 조회 성능(취급자/일반 분리 조회) 보조 인덱스
+ALTER TABLE COMTNDIAPAUSEMBER
+ ADD INDEX IX_DIAPAUSE_MBERTY (MBER_TY_CODE, DIAPAUSE_STAT);
+
+
+-- =====================================================================
+-- PART 2. DML (기준/시드 데이터) ※ 멱등: NOT EXISTS 가드로 중복 적재 방지
+-- 수록: PDAL_JOB_MAPPING(업무유형 매핑 13행), ACCOUNT_MNG_POLICY(기본정책 1행)
+-- 미수록: OTP_DEVICE / PERSONAL_DATA_ACCESS_LOG / *_HISTORY = 런타임 적재(시드 없음)
+-- =====================================================================
+
+-- 1) PDAL_JOB_MAPPING — 개인정보 처리 업무유형 매핑(접속기록 AOP 분류 기준, 필수)
+INSERT INTO PDAL_JOB_MAPPING (TARGET_CLASS, METHOD_PTTRN, JOB_SE_CODE, JOB_SE_NM, DML_GUBUN, SORT_ORDR, RM)
+SELECT * FROM (
+ SELECT 'EgovMberManageServiceImpl' AS TARGET_CLASS, 'select' AS METHOD_PTTRN, 'MBER_VIEW' AS JOB_SE_CODE, '회원관리-조회' AS JOB_SE_NM, 'S' AS DML_GUBUN, 10 AS SORT_ORDR, '회원 개인정보 조회' AS RM UNION ALL
+ SELECT 'EgovMberManageServiceImpl', 'insert', 'MBER_REG', '회원관리-등록', 'I', 10, NULL UNION ALL
+ SELECT 'EgovMberManageServiceImpl', 'update', 'MBER_MOD', '회원관리-수정', 'U', 10, NULL UNION ALL
+ SELECT 'EgovMberManageServiceImpl', 'delete', 'MBER_DEL', '회원관리-삭제', 'D', 10, NULL UNION ALL
+ SELECT 'EgovMberManageServiceImpl', '*', 'MBER_ETC', '회원관리-기타', NULL, 90, '미분류 폴백' UNION ALL
+ SELECT 'EgovEntrprsManageServiceImpl', 'select', 'ENTRPRS_VIEW', '업체관리-조회', 'S', 10, '업체 담당자 개인정보 조회' UNION ALL
+ SELECT 'EgovEntrprsManageServiceImpl', 'insert', 'ENTRPRS_REG', '업체관리-등록', 'I', 10, NULL UNION ALL
+ SELECT 'EgovEntrprsManageServiceImpl', 'update', 'ENTRPRS_MOD', '업체관리-수정', 'U', 10, NULL UNION ALL
+ SELECT 'EgovEntrprsManageServiceImpl', 'delete', 'ENTRPRS_DEL', '업체관리-삭제', 'D', 10, NULL UNION ALL
+ SELECT 'EgovEntrprsManageServiceImpl', '*', 'ENTRPRS_ETC', '업체관리-기타', NULL, 90, '미분류 폴백' UNION ALL
+ SELECT 'MberSsnServiceImpl', '*', 'MBER_SSN', '회원 고유식별정보 처리', NULL, 20, '주민등록번호 등 고유식별정보' UNION ALL
+ SELECT 'BizMberMergeServiceImpl', '*', 'MBER_MERGE', '회원 통합/이관', NULL, 30, NULL UNION ALL
+ SELECT 'BizTalentMberManageServiceImpl', '*', 'TALENT_MBER', '재능나눔 회원관리', NULL, 30, NULL
+) seed
+WHERE NOT EXISTS (SELECT 1 FROM PDAL_JOB_MAPPING pjm WHERE pjm.JOB_SE_CODE = seed.JOB_SE_CODE);
+
+-- 2) ACCOUNT_MNG_POLICY — 장기 미사용 계정 관리 기본정책(단일행 POLICY_ID=1)
+INSERT INTO ACCOUNT_MNG_POLICY (POLICY_ID, DORMANT_DAY, ALERT_D1, ALERT_D2, ALERT_D3, DELETE_PRESERVE_DAY, BATCH_CYCLE, UPD_ID, UPD_DT)
+SELECT 1, 365, 30, 7, 0, 365, 'DAILY', 'SYSTEM', NOW()
+ FROM DUAL
+ WHERE NOT EXISTS (SELECT 1 FROM ACCOUNT_MNG_POLICY WHERE POLICY_ID = 1);
+
+-- (부록) LOGIN_LOCK_COUNT — 기존 arko 테이블(변동분 아님). 미존재 신규 환경에서만 사용(기본 5):
+-- INSERT INTO LOGIN_LOCK_COUNT (LOCK_COUNT)
+-- SELECT 5 FROM DUAL WHERE NOT EXISTS (SELECT 1 FROM LOGIN_LOCK_COUNT);
+
+-- ############################## END ##############################
diff --git a/개인정보보호_개발진행현황.md b/개인정보보호_개발진행현황.md
new file mode 100644
index 0000000..6412069
--- /dev/null
+++ b/개인정보보호_개발진행현황.md
@@ -0,0 +1,348 @@
+# 문화품앗이 개인정보보호 기능개선 사업 — 개발 진행현황
+
+> 발주기관: 한국문화원연합회 | 사업기간: 계약일~2026.12.18 | 대상: 문화품앗이(csv.culture.go.kr)
+> 근거: 개인정보보호위원회 「고유식별정보 안전조치 관리실태 점검」 결과통보(2025.11.21.)
+> 작성일: **2026-07-21** | 기준: `개인정보보호_개발항목.docx`(61개 개발항목) 대비 소스 실측
+
+---
+
+## 1. 종합 진행률 대시보드
+
+| 단계 | 상태 | 비고 |
+|------|:----:|------|
+| 요구사항 분석·설계 | ✅ 완료 | 제안서 Ⅲ장 + 개발항목 61개 정리 + 디자인 브리프(10개 화면) |
+| 자동화 하네스 구축 | ✅ 완료 | `arko-privacy-*` 에이전트 3종 + 스킬 4종 + 리뷰어 확장 |
+| **코드 구현** | 🟡 **진행 (42 / 61, 69%)** | 2026-07-21 7파 각 6개 — 1~6차 36개 + 7차(#9·#10·#11·#13·#36·#44) — 아래 §1-1 |
+| 시험·검증(Ⅲ-5) | ⬜ 대기 | 구현 후 진행 |
+| 적용·안정화(Ⅲ-6) | ⬜ 대기 | 구현 후 진행 |
+
+**개발항목 61개 코드 구현 기준 진행률: 42개 완료 (2026-07-21 당일, 7파 각 6개 — 69%)**
+
+- 분석·설계 및 하네스는 완료되어 **바로 구현 단계 진입 가능**한 상태.
+- 현재 워킹트리의 소스 변경(`EgovSpringSecurityLoginFilter.java`, `pom.xml`)은 **로컬 개발 IP 허용(127.0.0.1) 추가**로, 본 사업 산출물이 아님 — 진행률에서 제외.
+
+---
+
+## 1-1. 오늘의 작업 (2026-07-21, ~17:00 목표)
+
+fork 3개를 병렬로 투입해 **자체 완결적·파일 충돌 없는 기반 항목 6개**를 당일 완료 목표로 착수한다.
+(발주기관 확정 대기 #12·#19, 운영환경 실측 선행 #55~58, 대규모 화면 #45~54는 오늘 범위에서 제외.)
+
+| Fork | 영역 | 대상 # | 산출물 | 상태 |
+|:----:|------|:------:|--------|:----:|
+| **A** | 권한관리 | #1 `getAuthorities()` 구현 | `EgovUserDetailsSessionServiceImpl` + 신규 sqlmap/DDL | ✅ 완료 |
+| **A** | 권한관리 | #4 AUTHOR_CHANGE_HISTORY DDL 신설 | 신규 `.sql` DDL(3년 보관) | ✅ 완료 |
+| **B** | 접속기록 | #30 PERSONAL_DATA_ACCESS_LOG DDL | 신규 `.sql`(무결성 컬럼+월 파티셔닝) | ✅ 완료 |
+| **B** | 접속기록 | #39 SHA-256 해시체인 유틸 | 신규 util `.java`(HASH_VAL/PREV_HASH_VAL) | ✅ 완료 |
+| **C** | 인증체계 | #17 AuthMethodService 인터페이스 설계 | 신규 인터페이스 + 3종 구현 스텁 | ✅ 완료 |
+| **C** | 인증체계 | #27 비밀번호 해시 알고리즘 점검 | `EgovFileScrty.encryptPassword()` 점검노트 | ✅ 완료 |
+
+> 파일 충돌 방지: Fork A만 `sql-map-config`/기존 ServiceImpl 수정, Fork B·C는 신규 파일만 생성. 진행현황 MD 기록은 오케스트레이터 단독 수행.
+
+**착수 로그**
+- 2026-07-21 착수: Fork A/B/C 병렬 투입, 대상 6개 항목 🟡 진행중 전환.
+- 2026-07-21 **Fork C 완료** (#17, #27): 인증수단 인터페이스 4파일 + 비밀번호 해시 점검노트 생성. 실측 확인 후 ✅ 전환. 후속 발견: 무솔트·무반복 해시, 해시값 로그출력(#27 보완 권고), 인증수단 빈 등록(#21 후속).
+- 2026-07-21 **Fork B 완료** (#30, #39): 접속기록 DDL(월 파티셔닝) + 해시체인 유틸(javac+실행 검증) 생성. 실측 확인 후 ✅ 전환. 후속 연계: 파티션 자동증설·730일 파기 배치(#37), 단일스레드 순차적재 전제(#33).
+- 2026-07-21 **Fork A 완료** (#1, #4): #1 getAuthorities() 5계층 배선(ServiceImpl→DAO→sqlmap→config→빈주입) 완료·실측 정합 확인. Fork A가 #4 DDL 착수 직후 API 오류로 중단되어, #4 AUTHOR_CHANGE_HISTORY DDL은 **오케스트레이터가 직접 완료**(컬럼명 오타 CHG_RESN 정정 포함). → **1차 목표 6개 항목 전부 ✅.**
+
+### 2차 작업 (2026-07-21, 추가 6개 목표)
+
+1차 기반(#1·#4·#17·#30·#39) 위에 쌓이는 항목. 공용 config 수정은 Fork A에 단독 집중해 충돌 차단.
+
+| Fork | 영역 | 대상 # | 산출물 | 상태 |
+|:----:|------|:------:|--------|:----:|
+| **A** | 배치(config 단독소유) | #5 권한 이력 파기 배치 | AUTHOR_CHANGE_HISTORY DAO/sqlmap + @Scheduled(1,095일) | ✅ 완료 |
+| **A** | 배치(config 단독소유) | #40 해시체인 무결성 검증 배치 | HashChainService.verifyChain 재계산 배치 + 관리자 통보 | ✅ 완료 |
+| **B** | util(신규파일만) | #34 IP 취득 로직 보정 | ClientIpResolver(XFF→Proxy-Client-IP→getRemoteAddr) | ✅ 완료 |
+| **B** | util(신규파일만) | #16 세션 재발급(세션고정 차단) | SessionRegenerator util | ✅ 완료 |
+| **C** | resolver+분석 | #21 AuthMethodResolver 구현 | #17 인터페이스 기반 인증수단 분기 | ✅ 완료 |
+| **C** | resolver+분석 | #38 COMTNSYSLOG 7일 삭제 분리 | 기존 7일 삭제 SQL 진단 + 접속기록 미적용 분리 노트 | ✅ 완료 |
+
+- 2026-07-21 2차 착수: Fork A/B/C 병렬 재투입, 대상 6개 🟡 진행중 전환.
+
+### 3차 작업 (2026-07-21, 추가 6개 목표 — 통합 단계)
+
+1·2차 산출물(#1·#16·#17·#21·#30·#34·#39)을 실제로 연결·활용. 공용 config는 fork별로 배타 소유(A=egov-com-servlet.xml, B=sql-map-config+AOP, C=impl java만).
+
+| Fork | 영역 | 대상 # | 산출물 | 상태 |
+|:----:|------|:------:|--------|:----:|
+| **A** | 인터셉터(servlet.xml 단독) | #2 AuthorityInterceptor 등록 | URL·메뉴 권한 대조 차단(#1 getAuthorities 활용) | ✅ 완료 |
+| **A** | 인터셉터(servlet.xml 단독) | #14 SecondFactorInterceptor 등록 | 2차 인증 판정(#16 SessionRegenerator+#21 Resolver) | ✅ 완료 |
+| **B** | 접속기록수집(sqlmap+AOP 단독) | #31 PersonalDataLogAspect | 개인정보 서비스 한정 AOP 수집(#34 IP+#39 해시) | ✅ 완료 |
+| **B** | 접속기록수집(sqlmap+AOP 단독) | #32 업무유형 매핑 테이블 | URL패턴→업무코드 DDL+로더 | ✅ 완료 |
+| **C** | 인증수단(impl java만) | #18 OtpAuthService(TOTP) | #17 스텁 채움, TOTP 자체구현·비밀키 암호화 | ✅ 완료 |
+| **C** | 인증수단(impl java만) | #20 CertAuthService | #17 스텁 채움, GPKI 기존자산 위임 | ✅ 완료 |
+
+- 2026-07-21 3차 착수: Fork A/B/C 병렬 재투입, 대상 6개 🟡 진행중 전환.
+
+### 4차 작업 (2026-07-21, 추가 6개 목표)
+
+접근통제·계정잠금 + 접속기록 배치/비동기 + 세션/마스킹. 로그인 흐름은 Fork A 단독 소유로 충돌 차단.
+
+| Fork | 영역 | 대상 # | 산출물 | 상태 |
+|:----:|------|:------:|--------|:----:|
+| **A** | 로그인흐름(sql-map-config 단독) | #22 인증실패이력 활성경로 이설 | LOGIN_HISTORY/LOGIN_LOCK_COUNT 재사용, actionLogin 활성 | ✅ 완료 |
+| **A** | 로그인흐름(sql-map-config 단독) | #24 계정 잠금 10→5회 | 임계값 5 + 해제 관리자화면 전환·해제이력 적재 | ✅ 완료 |
+| **B** | 접속기록배치(자기sqlmap·스케줄러 단독) | #33 비동기 적재 큐 | 로그적재↔업무txn 분리(단일컨슈머 순번보장) | ✅ 완료 |
+| **B** | 접속기록배치(자기sqlmap·스케줄러 단독) | #37 보관 730일 파기 배치 | 월1회 파티션 DROP(개별 DELETE 미사용) | ✅ 완료 |
+| **C** | 세션·마스킹(web.xml+util 단독) | #25 세션 타임아웃 60→30분 | web.xml session-config + 정책 훅 | ✅ 완료 |
+| **C** | 세션·마스킹(web.xml+util 단독) | #29 2차인증 화면 마스킹 | 휴대폰번호 마스킹 util | ✅ 완료 |
+
+- 2026-07-21 4차 착수: Fork A/B/C 병렬 재투입, 대상 6개 🟡 진행중 전환.
+
+### 5차 작업 (2026-07-21, 추가 6개 목표)
+
+미사용계정 휴면(첫 착수) + 접속기록 조회·점검 + 접근통제 마무리(#23 핸드오프 해소). 로그인/인터셉터는 Fork C 단독.
+
+| Fork | 영역 | 대상 # | 산출물 | 상태 |
+|:----:|------|:------:|--------|:----:|
+| **A** | 휴면계정(umt·휴면 sqlmap 단독) | #7 휴면 대상 취급자 확장 | 휴면 체인 S→P를 COMTNEMPLYRINFO까지 | ✅ 완료 |
+| **A** | 휴면계정(umt·휴면 sqlmap 단독) | #8 MariaDB 휴면배치 statement 정비 | Oracle에만 있던 배치 statement MariaDB 추가 | ✅ 완료 |
+| **B** | 접속기록조회·점검(자기sqlmap·스케줄러 단독) | #41 접속기록 조회·검색 개선 | 접속자ID 등 신규 검색조건(현행 LIKE+날짜뿐) | ✅ 완료 |
+| **B** | 접속기록조회·점검(자기sqlmap·스케줄러 단독) | #43 정기점검 리포트 생성 배치 | 월1회 이상징후·무결성·처리현황·다운로드 집계 | ✅ 완료 |
+| **C** | 접근통제마무리(로그인·servlet.xml 단독) | #23 계정 열거 방지 | 실패 메시지 존재여부 무관 일관화(#22 핸드오프 해소) | ✅ 완료 |
+| **C** | 접근통제마무리(로그인·servlet.xml 단독) | #26 인터셉터 3단 순서 확정 | 인증→SecondFactor→Authority 순서 확정·검증 | ✅ 완료 |
+
+- 2026-07-21 5차 착수: Fork A/B/C 병렬 재투입, 대상 6개 🟡 진행중 전환.
+
+### 6차 작업 (2026-07-21, 추가 6개 목표 — 2개 영역 완결)
+
+권한관리(1~6)·접근통제(22~29) 완결 목표. 공용 config는 fork별 배타 소유(A=sql-map-config, B=BatchScheduler+등록된sqlmap, C=servlet.xml).
+
+| Fork | 영역 | 대상 # | 산출물 | 상태 |
+|:----:|------|:------:|--------|:----:|
+| **A** | 권한관리완결(sql-map-config 단독) | #3 권한 신청·승인 워크플로 | 신청R→승인A/반려J 테이블+서비스, 승인시 권한반영+#4이력 단일TX | ✅ 완료 |
+| **A** | 권한관리완결(sql-map-config 단독) | #6 권한 이력 조회·검색·다운로드 | #4 이력 양방향검색 + 다운로드 사유필수·다운로드이력 | ✅ 완료 |
+| **B** | 접속기록마무리(스케줄러·등록sqlmap 단독) | #35 비정상 접근 탐지 5유형 | 비인가/대량조회/비정상시간대/계정공유의심/인증실패반복 | ✅ 완료 |
+| **B** | 접속기록마무리(스케줄러·등록sqlmap 단독) | #42 다운로드 사유·이력 관리 | 접속기록 다운로드 사유필수+다운로드자·일시·IP·건수 이력 | ✅ 완료 |
+| **C** | 인증판정·정책(servlet.xml·인터셉터 단독) | #15 2차인증 적용대상·경로 판정 | 취급자 한정, 외부 필수/내부 정책(#14 확장) | ✅ 완료 |
+| **C** | 인증판정·정책(servlet.xml·인터셉터 단독) | #28 신규 인증정보 저장정책 적용 | OTP암호화/인증번호미저장/인증서 식별값만 점검·확정 | ✅ 완료 |
+
+- 2026-07-21 6차 착수: Fork A/B/C 병렬 재투입, 대상 6개 🟡 진행중 전환.
+
+### 7차 작업 (2026-07-21, 추가 6개 목표 — 2개 영역 완결)
+
+미사용계정(SFR-002) 완결 + 접속기록(SFR-004) 15/15 완결. 미사용계정 4개는 공유파일 충돌로 Fork A 단독, 접속기록 2개는 파일 분리(B=aspect, C=report).
+
+| Fork | 영역 | 대상 # | 산출물 | 상태 |
+|:----:|------|:------:|--------|:----:|
+| **A** | 미사용계정완결(휴면/정책·sql-map-config 단독) | #9 휴면 기준일수 정책값화 | 365 하드코딩→정책테이블 | ✅ 완료 |
+| **A** | 미사용계정완결 | #10 휴면 전 관리자 알림 D-30/D-7/D-Day | 현재 본인메일 2단계→3단계+관리자 | ✅ 완료 |
+| **A** | 미사용계정완결 | #11 장기 미사용 계정 현황 조회(백엔드) | ID·성명·소속·최종로그인·미접속일수·상태·휴면예정일 | ✅ 완료 |
+| **A** | 미사용계정완결 | #13 계정 관리 정책 설정(백엔드) | 기준일수·알림시점·삭제보존기간·배치주기 정책 CRUD | ✅ 완료 |
+| **B** | 처리기록확장(aspect·AOP context 단독) | #36 처리기록 적재 범위 확장 | 2개 Controller→전 처리지점, TRGET_ID 규약(sqlmap 무변경) | ✅ 완료 |
+| **C** | 점검결과보관(report·등록sqlmap 단독) | #44 점검결과 입력·보관(백엔드) | 점검자·일시·결과·조치사항 입력→PDAL_INSPECTION_REPORT | ✅ 완료 |
+
+- 2026-07-21 7차 착수: Fork A/B/C 병렬 재투입, 대상 6개 🟡 진행중 전환.
+- 2026-07-21 **Fork B(7차) 완료** (#36): 처리기록 pointcut 5→16 서비스 확장 + TRGET_ID 식별자 추출 규약(본문 미적재, fail-safe). 실측 확인 후 ✅(XML well-formed·헬퍼 배선·sqlmap 미변경).
+- 2026-07-21 **Fork C(7차) 완료** (#44): 점검결과 입력·보관(InspectionResult 3파일+INSPECT_DT+statement 3종). 실측 확인 후 ✅(미입력 차단·양쪽 XML well-formed·sql-map-config 미변경). **접속기록 영역(30~44) 15/15 완결.** 배포된 테이블엔 INSPECT_DT ALTER 필요(핸드오프).
+- 2026-07-21 **Fork A(7차) 완료** (#9,#10,#11,#13): 정책테이블(ACCOUNT_MNG_POLICY)+정책값화+D-30/7/Day 알림+현황조회, 10파일·javac EXIT=0. 실측 확인 후 ✅(365→정책 서브쿼리·sql-map-config 등록·알림 스케줄러 확인). → **미사용계정 영역 6/7 완결(#12 발주대기만). 7차 6개 전부 ✅ (누적 42/61, 69%).**
+- 2026-07-21 **Fork C(6차) 중단→완결** (#15, #28): Fork C가 #15(SecondFactorInterceptor 확장) 완료 후 #28 점검노트 착수 직전 API 오류 중단. #15는 실측 확인(외부필수/내부정책 판정·enforce=false·xml well-formed·중괄호 75/75)→✅. **#28 점검노트는 오케스트레이터가 직접 완결**(3종 저장정책 실측, 위반 0건, OTP URI 주의)→✅. → **접근통제 영역(22~29) 8/8 완결.**
+- 2026-07-21 **Fork B(6차) 완료** (#35, #42): 비정상 탐지 5유형(6파일, 표준컬럼 집계) + 다운로드 사유필수·이력(DDL+4파일). 실측 확인 후 ✅(#43 리포트 자리표시→실집계 `summarize()`/`countByPeriod()` 교체, 양쪽 XML well-formed).
+- 2026-07-21 **Fork A(6차) 완료** (#3, #6): 권한 신청·승인 워크플로(단일TX) + 권한이력 조회·다운로드(사유필수). 실측 확인 후 ✅(10파일·DDL·sql-map-config 등록·approveRequest 단일TX·다운로드 차단 확인). → **권한관리 영역(1~6) 6/6 완결. 6차 6개 전부 ✅ (누적 36/61, 59%).**
+- 2026-07-21 **Fork C(5차) 완료** (#23, #26): 계정 열거 방지(조건부 문구 제거→단일 메시지) + 인터셉터 3단 순서 확정·문서화. 실측 확인 후 ✅(문구 제거·순서·xml well-formed 확인). **4차 #22 핸드오프(계정열거) 해소 완료.** ①authenticInterceptor 재활성은 운영 협의 후속.
+- 2026-07-21 **Fork B(5차) 중단→분할 완결**: Fork B가 #41 Java 4파일까지 만들고 API 오류로 중단(누락 sqlmap·#43 미착수). #41은 **오케스트레이터가 누락 sqlmap statement(Mysql/Oracle) 완결**(DAO statement id 정합·XML well-formed 검증)→✅. #43은 전용 fork 재투입(진행중).
+- 2026-07-21 **Fork A(5차) 완료** (#7, #8): 휴면 취급자 확장(MBER_TY_CODE 분리) + MariaDB 휴면 statement 5종 이설. 실측 확인 후 ✅ — **핵심 발견: MariaDB sqlmap에 휴면 statement 전무해 실운영 휴면배치가 불능이던 것을 복구**(#56 착수전 확인사항과 직결). DAO↔sqlmap id·javac EXIT=0 확인.
+- 2026-07-21 **#43 재투입 fork 완료**: 정기점검 리포트 배치(PDAL_INSPECTION_REPORT+집계 3종+스케줄러). 실측 확인 후 ✅(파일·서비스·XML well-formed). #35 이상징후·#42 다운로드는 자리표시(후속 실집계 교체). → **5차 목표 6개 전부 ✅ (누적 30/61 — 절반 근접).**
+- 2026-07-21 **Fork C(4차) 완료** (#25, #29): web.xml 세션타임아웃 30분 + MaskingUtil(휴대폰/이메일/이름). 실측 확인 후 ✅(web.xml:218=30, javac 통과). 후속: 세션타임아웃 관리자 설정화면.
+- 2026-07-21 **Fork A(4차) 완료** (#22, #24): 운영 로그인 경로(`EgovLoginController.actionLogin`) 실패이력·잠금 이설 + ACCOUNT_UNLOCK_HISTORY 해제이력. 실측 확인 후 ✅ — **참조 서비스 7메서드·VO 3게터 인터페이스 실재 검증, fail-safe로 로그인 무손상.** 핸드오프: 필터 활성 시 이중카운트 정리, #23 실패메시지 완전통일(현재 존재계정에만 "현재 N회" 부가).
+- 2026-07-21 **Fork B(4차) 완료** (#33, #37): 비동기 적재 큐(단일컨슈머 순번보장) + 730일 파티션 파기·선행증설 배치. 실측 확인 후 ✅(Aspect enqueue 전환·파티션 DROP/ADD statement·BatchScheduler 등록 확인). → **4차 목표 6개 전부 ✅ (누적 24/61).**
+- 2026-07-21 **Fork A(3차) 완료** (#2, #14): 인터셉터 2종 신규+`egov-com-servlet.xml` 등록(순서 #26 반영), javac 통과·xml well-formed. **운영 안전 확인: SecondFactor enforce=false·Authority urlRoleMapping 공백=둘 다 fail-open이라 현재 사이트 무손상.** 실측 확인 후 ✅. 핵심 실측: 이 프로젝트는 `selectAnnotaionMapper`가 `/**/*.do` 전체 적용(mvc:interceptors 미사용)·기존 `authenticInterceptor` 비활성 → 스코프/잠금방지를 인터셉터 내부에서 처리. 후속: #52·#18~20 후 enforce=true, urlRoleMapping 운영 등록.
+- 2026-07-21 **Fork C(3차) 완료** (#18, #20): OtpAuthService(TOTP RFC6238 자체구현, RFC 테스트벡터 일치) 5파일 + CertAuthService(기존 GPKI actionCrtfctLogin 위임). 스텁→실구현, javac+실행 검증. 실측 확인 후 ✅(UnsupportedOperationException 0건, SmsAuth#19 미변경 유지). 후속: OTP 비밀키 영속화 테이블, CertProcessFilter 재활성.
+- 2026-07-21 **Fork B(3차) 완료** (#31, #32): PersonalDataLogAspect 수집 파이프라인 10클래스+DDL+sqlmap 6+AOP context, javac EXIT=0. 실측 확인 후 ✅(pointcut 개인정보 서비스 5개 한정 확인=과수집 방지, 기존 COMTNSYSLOG 광역 블록 주석 유지). 후속: #33 비동기 큐, #36 TRGET_ID 규약. → **3차 목표 6개 전부 ✅ (누적 18/61).**
+- 2026-07-21 **Fork B(2차) 완료** (#34, #16): ClientIpResolver(신뢰CIDR 검증) + SessionRegenerator 생성, javac+단위검증 통과. 실측 확인 후 ✅. 후속: 신뢰대역 운영값(#57), #14 인터셉터 연동 배선.
+- 2026-07-21 **Fork C(2차) 완료** (#21, #38): AuthMethodResolver 4종(javac 통과) + 시스템로그 삭제정책 분리노트. 실측 확인 후 ✅(7일 삭제 SQL `EgovSysLog_SQL_Mysql.xml:159` 실제와 정합, 스케줄러 비활성 확인). 후속: #18~20 구현체·빈등록.
+- 2026-07-21 **Fork A(2차) 완료** (#5, #40): 파기·무결성검증 배치 2종(배치 클래스+DAO+sqlmap 6파일) + `BatchScheduler` @Scheduled 2메서드 + sql-map-config 등록. 실측 확인 후 ✅(파기 SQL이 실제 DDL 컬럼 CHG_DT 사용 정합). **핵심 핸드오프: 전역 `task:annotation-driven`이 양쪽 context에 주석 처리→@Scheduled 현재 무동작(기존 배치도 동일). 활성화·crontab 경합은 운영 협의 필요.** → **2차 목표 6개 전부 ✅ (누적 12/61).**
+
+---
+
+## 2. 영역별 진행 요약
+
+| 영역 | 요구사항 ID | 항목(구간) | 담당 | 완료 | 진행중 | 미착수 |
+|------|-------------|:----------:|------|:----:|:------:|:------:|
+| Ⅰ. 권한관리 ✅ | SFR-001 | 1~6 | arko-privacy-authctrl | 6 | 0 | 0 |
+| Ⅰ. 미사용계정관리 | SFR-002 | 7~13 | arko-privacy-authctrl | 6 | 0 | 1 |
+| Ⅱ. 인증체계 | SFR-003 / IAR-001 | 14~21 | arko-privacy-authctrl | 7 | 0 | 1 |
+| Ⅱ. 접근통제·인증정보 ✅ | SER-001 / SER-003 | 22~29 | arko-privacy-authctrl | 8 | 0 | 0 |
+| Ⅲ. 접속기록 ✅ | SFR-004 / DBR-001 / SER-002 | 30~44 | arko-privacy-auditlog | 15 | 0 | 0 |
+| Ⅳ. 신규 화면(UI) | - | 45~54 | arko-frontend | 0 | 0 | 10 |
+| Ⅴ. 착수전 확인 | - | 55~58 | arko-privacy-analyst | 0 | 0 | 4 |
+| Ⅵ. 보안·품질 | SER-004 | 59~61 | arko-reviewer | 0 | 0 | 3 |
+| **합계** | | **61** | | **42** | **0** | **19** |
+
+범례: ✅ 완료 · 🟡 진행중 · ⬜ 미착수
+
+---
+
+## 3. 항목별 상세 진행현황
+
+### Ⅰ. 권한관리 (Ⅲ-2, SFR-001·SFR-002) — arko-privacy-authctrl
+
+#### 1) 권한 신설·변경·회수 및 역할별 차등 적용 (SFR-001)
+
+| # | 개발항목 | 상태 | 소스 실측 / 비고 |
+|:--:|----------|:----:|------------------|
+| 1 | `getAuthorities()` 구현 | ✅ 완료 | [Fork A] `EgovUserDetailsSessionServiceImpl` 세션 LoginVO.uniqId→`EgovUserAuthorityDAO.selectAuthoritiesByUniqId`→`PrivacyAuthority_SQL_Mysql.xml`(COMTNEMPLYRSCRTYESTBS 조회). sql-map-config 등록 + 빈 DAO 주입 배선 완료. 미인증 시 빈 목록(fail-safe) |
+| 2 | AuthorityInterceptor 신규 등록 | ✅ 완료 | [Fork A] `cmm/privacy/interceptor/AuthorityInterceptor.java` + `egov-com-servlet.xml` 등록. `/mnt/` 한정, `EgovUserDetailsHelper.getAuthorities()`(#1) 대조 미허용 403. **urlRoleMapping 기본 비어있음=fail-open(전경로 통과)** → 운영에서 매핑 점진 등록 |
+| 3 | 권한 신청·승인 워크플로 구현 | ✅ 완료 | [Fork A] `AUTHOR_CHANGE_REQUEST`(DDL)+5계층(`AuthorWorkflow*`). `approveRequest()` 승인 시 스냅샷→권한반영(COMTNEMPLYRSCRTYESTBS)→상태A→#4이력을 **단일TX**(context-transaction AOP, 예외 시 롤백). 화면 #46 후속 |
+| 4 | AUTHOR_CHANGE_HISTORY 이력 테이블 신설 | ✅ 완료 | `src/script/mysql/ddl/privacy.author_change_history_create_mysql.sql`. 변경자/변경일시/변경전후권한(BEFORE/AFTER_AUTHOR)/변경구분/승인자/IP + 3년(1,095일) 보관 전제. Fork A 중단→오케스트레이터 완료 |
+| 5 | 권한 이력 파기 배치 구현 | ✅ 완료 | [Fork A] `cmm/privacy/batch/`(Service/Impl/PurgeDAO) + `PrivacyAuthorHistoryPurge_SQL`(DELETE WHERE CHG_DT365`→`>(SELECT IFNULL(MAX(DORMANT_DAY),365) FROM ACCOUNT_MNG_POLICY)`(Mysql/Oracle). **미설정 시 365 fallback**, Java 시그니처 무변경 |
+| 10 | 휴면 전 관리자 알림 확장(D-30/D-7/D-Day) | ✅ 완료 | [Fork A] `DormantAlertService` — 정책 ALERT_D1/2/3(기본 30/7/0)일 전 대상 조회→본인+관리자(ADMIN_EMAIL) 메일(EgovSndngMail 재사용, fail-safe). `BatchScheduler.dormantPreAlertScheduler()` @Scheduled(매일 06:00) |
+| 11 | 장기 미사용 계정 현황 조회 화면 신규 | ✅ 완료 | [Fork A] `DormantAccountStatus`Service/DAO+VO. 취급자(COMTNEMPLYRINFO) 계정ID·성명·소속·직위·최종로그인·**미접속일수(DATEDIFF)**·상태·**휴면예정일** 복합검색+페이징. 일반회원 UNION·화면 후속 |
+| 12 | 계정 삭제 기능 구현 | ⬜ 미착수 | ⚠ 보존기간 발주기관 확정 필요 |
+| 13 | 계정 관리 정책 설정 화면 | ✅ 완료 | [Fork A] `ACCOUNT_MNG_POLICY`(DDL, DORMANT_DAY·ALERT_D1/2/3·DELETE_PRESERVE_DAY·BATCH_CYCLE·ADMIN_EMAIL 단일행+시드) + `AccountMngPolicy`Service/DAO/VO/Controller. 미설정 시 기본값 fallback. #9/#10이 이 테이블 참조. 화면 후속 |
+
+### Ⅱ. 인증체계 (Ⅲ-3, SFR-003·IAR-001·SER-001·SER-003) — arko-privacy-authctrl
+
+#### 3) 2단계 인증 삽입 (SFR-003)
+
+| # | 개발항목 | 상태 | 소스 실측 / 비고 |
+|:--:|----------|:----:|------------------|
+| 14 | SecondFactorInterceptor 신규 등록 | ✅ 완료 | [Fork A] `cmm/privacy/interceptor/SecondFactorInterceptor.java`, authority보다 먼저 등록(#26 순서 반영). `/mnt/` 한정 SECOND_FACTOR_DONE 검사. **enforce=false(미강제)** → #52 화면·#18~20 수단 완료 후 true+resolver 주입 활성화. javac 통과 |
+| 15 | 2차 인증 적용대상·경로 판정 로직 | ✅ 완료 | [Fork C] `SecondFactorInterceptor` 확장 — `/mnt/` 취급자 한정, **외부접속 필수(설정불가)/내부접속 정책(applyToInternal, internalIpCidrs CIDR 판정)**. enforce=false 유지(잠금방지). 중괄호 75/75·xml well-formed |
+| 16 | 세션 재발급 처리(세션고정 차단) | ✅ 완료 | [Fork B] `cmm/privacy/security/SessionRegenerator.java`. `regenerate()`=속성캡처→invalidate→getSession(true)→재적재, `onSecondFactorSuccess()` 표식. javac 통과. #14 SecondFactorInterceptor 연동 시 호출 배선 필요 |
+
+#### 4) 추가 인증수단 3종 (IAR-001)
+
+| # | 개발항목 | 상태 | 소스 실측 / 비고 |
+|:--:|----------|:----:|------------------|
+| 17 | AuthMethodService 인터페이스 설계 | ✅ 완료 | [Fork C] `com/uat/uia/service/AuthMethodService.java` + 스텁 3종(Otp/Sms/Cert) 생성. `issue(uniqId)`/`verify(uniqId,code)`/`isAvailable(uniqId)` throws Exception 확정. 빈 등록·실로직은 #18~21 후속 |
+| 18 | OtpAuthService 구현 (1차 적용) | ✅ 완료 | [Fork C] `OtpAuthService`+`OtpSecretStore`/`InMemory`/`OtpSecretCipher`(5파일). TOTP RFC6238 자체구현(HmacSHA1/30초/6자리/±1스텝), 비밀키 AES-128/GCM 암호화 저장·미노출. **RFC 테스트벡터 일치(287082/081804)**. 영속화=EMPLYR_OTP_SECRET 테이블 후속(Fork B 소유 sqlmap) |
+| 19 | SmsAuthService 구현 (2차 적용) | ⬜ 미착수 | ⚠ KT-TCS/MCSAgent3.0 연동규격 발주기관 확정 필요 |
+| 20 | CertAuthService 구현 (3차 적용) | ✅ 완료 | [Fork C] `CertAuthService` 실구현. 기존 `EgovLoginServiceImpl.actionCrtfctLogin`(:108)→`COMTNEMPLYRINFO.CRTFC_DN_VALUE`(EgovLoginUsr_SQL_Mysql.xml:434) 위임, **인증서 본문 미저장·DN 식별값만 대조**. 핸드오프: `CertProcessFilter`(web.xml:46 주석) 재활성 운영협의 |
+| 21 | AuthMethodResolver 구현 | ✅ 완료 | [Fork C] `AuthMethodResolver`+`AuthMethodType`(enum)+`AuthMethodPolicyProvider`+`DefaultAuthMethodPolicyProvider` 4종. Map 주입 분기, 폴백 OTP→SMS→CERT, `resolve(String) throws Exception`(1차 계약 정합). javac 통과. 실동작은 #18~20 구현체·빈등록 후 |
+
+#### 5) 인증 실패 이력 및 접근통제 정책 (SER-001)
+
+| # | 개발항목 | 상태 | 소스 실측 / 비고 |
+|:--:|----------|:----:|------------------|
+| 22 | 인증 실패 이력 활성 경로 이설 | ✅ 완료 | [Fork A] AS-IS: 실패이력/잠금이 `EgovSpringSecurityLoginFilter:466`(조건부)에만 존재. `EgovLoginController.actionLogin` 재작성(잠금선확인→성공시 이력정리→실패시 조건부 적재/잠금), **기존 LOGIN_HISTORY/LOGIN_LOCK_COUNT·서비스 7메서드 재사용(신규 statement 0)**, 전구간 fail-safe. 서비스/VO 메서드 실재 검증 |
+| 23 | 계정 열거 방지 처리 | ✅ 완료 | [Fork C] `EgovLoginController:238` 조건부 "현재 N회" 문구 제거→존재/미존재·실패횟수 무관 단일 `fail.common.login`. 실패이력·잠금은 내부 유지, 응답 노출 제거. 잠금안내만 의도적 예외 |
+| 24 | 계정 잠금 정책 정비(10회→5회) | ✅ 완료 | [Fork A] 임계값 `LOGIN_LOCK_COUNT` UPDATE=5 + 소스 fallback `DEFAULT_LOCK_THRESHOLD=5`. 해제화면 기존확인. **신규 `ACCOUNT_UNLOCK_HISTORY`**(DDL+VO+DAO+sqlmap, sql-map-config 등록)에 해제이력 훅(해제자+ClientIpResolver IP, fail-safe). 사유입력은 화면 후속 |
+| 25 | 세션 타임아웃 조정(60→30분) | ✅ 완료 | [Fork C] `web.xml:218` `30`(60→30) + 근거주석. 실사용 webapp/web.xml 확정. 관리자 설정화면은 후속 |
+| 26 | 인터셉터 3단 판정 순서 확정 | ✅ 완료 | [Fork C] `egov-com-servlet.xml:66` 리스트 순서 확정 ①authenticInterceptor(비활성)→②secondFactor→③authority + 근거주석. ①재활성 시 위치 유지 핸드오프. xml well-formed |
+
+#### 6) 인증정보 보호 (SER-003)
+
+| # | 개발항목 | 상태 | 소스 실측 / 비고 |
+|:--:|----------|:----:|------------------|
+| 27 | 비밀번호 해시 알고리즘 점검 | ✅ 완료 | [Fork C] 점검노트 `개인정보보호_비밀번호해시_점검노트.md`. 알고리즘 **SHA-256(기준 충족)**, 단 운영경로 **무솔트·무반복** + `BizMberFrtManageController.java:553` 해시값 로그출력 3건 보완 권고 |
+| 28 | 신규 인증정보 저장정책 적용 | ✅ 완료 | [Fork C→오케스트레이터] 점검노트 `개인정보보호_인증정보저장정책_점검노트.md`. OTP AES암호화저장(로그노출0)·SMS 미저장설계·CERT DN만 → **3종 준수, 위반 0건**. OTP 프로비저닝 URI 화면(#53) 취급 주의 1건 |
+| 29 | 2차 인증 화면 개인정보 마스킹 | ✅ 완료 | [Fork C] `cmm/privacy/util/MaskingUtil.java`. maskPhone(010-****-1234)/maskEmail(ho**@)/maskName(홍*동)/범용 mask. null·형식 방어, javac+self-check 통과. #52 화면에서 사용 |
+
+### Ⅲ. 접속 및 처리기록 관리 (Ⅲ-4, SFR-004·DBR-001·SER-002) — arko-privacy-auditlog
+
+#### 7) 접속기록 수집·저장 (SFR-004)
+
+| # | 개발항목 | 상태 | 소스 실측 / 비고 |
+|:--:|----------|:----:|------------------|
+| 30 | PERSONAL_DATA_ACCESS_LOG 신규 테이블 | ✅ 완료 | [Fork B] `src/script/mysql/ddl/privacy.pdal_create_mysql.sql`. 기존 PERSONAL_DATA_DML_HISTORY 실측 승격 + HASH_VAL/PREV_HASH_VAL, PK(LOG_ID,CONECT_DT), 월 RANGE COLUMNS 파티셔닝(접속자ID 검색 인덱스 포함). 파티션 자동증설/파기는 #37 연계 |
+| 31 | AOP pointcut 한정 수집 설계 | ✅ 완료 | [Fork B] `PersonalDataLogAspect`+Service/Impl+DAO(10파일)+`context-syslogaop.xml`. pointcut **개인정보 서비스 impl 5개 한정**(과수집 방지), `ClientIpResolver`(#34)+`HashChainService.linkAndSeal`(#39) 적재, synchronized 순번직렬화. 본문 미적재. 비동기(#33)·TRGET_ID(#36) 후속 |
+| 32 | 업무유형 매핑 테이블 구현 | ✅ 완료 | [Fork B] `privacy.job_mapping_create_mysql.sql`(`PDAL_JOB_MAPPING`+시드 13행) + `JobMappingResolver`(@Component 캐시)+DAO+sqlmap. (클래스,메서드)→업무코드/명, 신규화면은 행 등록만으로 수집(소스 무수정) |
+| 33 | 비동기 적재 큐 구현 | ✅ 완료 | [Fork B] `AccessLogQueue`(LinkedBlockingQueue cap1만+단일 데몬컨슈머). Aspect가 enqueue만(업무 응답 무영향), 컨슈머가 persistNow 순차→**단일 라이터로 해시체인 순번 보장**. 백프레셔(offer 5s), @PreDestroy flush 유실방지 |
+| 34 | IP 취득 로직 보정 | ✅ 완료 | [Fork B] `cmm/privacy/auditlog/ClientIpResolver.java`. XFF→Proxy/WL-Proxy→HTTP_CLIENT_IP→getRemoteAddr 순 + **신뢰프록시 CIDR 검증(헤더위조 방지)** + IPv6 루프백 정규화. javac+단위검증 통과. AS-IS `IpObtainInterceptor`(getRemoteAddr+인증시만) 격차 확인. 신뢰대역 운영값=#57 후 확정 |
+
+#### 8) 비정상 접근 탐지 및 처리기록 (SER-002)
+
+| # | 개발항목 | 상태 | 소스 실측 / 비고 |
+|:--:|----------|:----:|------------------|
+| 35 | 비정상 접근 탐지 5유형 구현 | ✅ 완료 | [Fork B] `AbnormalAccessType`(enum)+`Policy`+`Detection`Service/Impl/DAO+VO(6파일). 표준컬럼 집계로 5유형(비인가F/대량조회/업무외시간/동일ID다중IP/실패반복≥5). 임계 정책추상화. sqlmap 5종(Mysql/Oracle). **#43 리포트 실집계 교체** |
+| 36 | 처리기록 적재 범위 확장 | ✅ 완료 | [Fork B] pointcut 5→16개 개인정보 취급 서비스(회원/취급자/휴면/신청/활동/그룹/마일리지/프로필/쪽지/SMS 등 명시열거, 조직·통계·게시성 제외). **TRGET_ID 식별자만 베스트에포트 추출**(getter/Map키 우선순위, String인자 스킵, 본문 미적재, fail-safe). sqlmap 무변경(컬럼 기보유). javac EXIT=0 |
+
+#### 9) 보관정책 및 위·변조 방지 (DBR-001)
+
+| # | 개발항목 | 상태 | 소스 실측 / 비고 |
+|:--:|----------|:----:|------------------|
+| 37 | 보관정책 730일 + 파기 배치 구현 | ✅ 완료 | [Fork B] `AccessLogPartitionDAO`+`PDAL_PURGE_HISTORY`(DDL). INFORMATION_SCHEMA.PARTITIONS 경계≤오늘-730일 파티션 DROP + 당월~+2M ADD 선행증설. `BatchScheduler.purgeAccessLogPartitionScheduler()`(매월1일 02:00). ⚠파티션명 `$` 치환=Java계산값(안전, 리뷰포인트) |
+| 38 | COMTNSYSLOG 7일 삭제정책 재설계 | ✅ 완료 | [Fork C] 노트 `개인정보보호_시스템로그삭제정책_분리노트.md`. 7일 삭제 SQL 위치 `EgovSysLog_SQL_Mysql.xml:159`(COMTNSYSLOG 단일, Quartz 1H이나 **현재 스케줄러 비활성**) 실측 확인. 오적용 위험 낮음(테이블 하드코딩+namespace 분리), 분리 보강 5개 권고 |
+| 39 | SHA-256 해시체인 구현 | ✅ 완료 | [Fork B] `cmm/privacy/auditlog/HashChainService.java`(+VO). MessageDigest 자체구현, 결합규칙 CONECT_ID\|CONECT_DT\|CONECT_IP\|JOB_SE_CODE\|TRGET_ID\|DML_GUBUN\|PREV_HASH_VAL→SHA-256. computeHash/linkAndSeal/verifyChain, javac+실행 변조탐지 검증. 순번보장=단일스레드 순차INSERT 전제(#33) |
+| 40 | 해시체인 무결성 검증 배치 | ✅ 완료 | [Fork A] `AccessLogIntegrityDAO`+`PrivacyAccessLogIntegrity_SQL`(LOG_ID ASC, CONECT_DT 형식일치) + `verifyAccessLogHashChain()`→verifyChain. `BatchScheduler.verifyAccessLogHashChainScheduler()` @Scheduled(매월1일 03:00). 불일치 시 log.error+notifyAdmin(#49 TODO)·리포트(#43) 반환 |
+
+#### 10) 조회·다운로드 및 정기점검
+
+| # | 개발항목 | 상태 | 소스 실측 / 비고 |
+|:--:|----------|:----:|------------------|
+| 41 | 접속기록 조회·검색 화면 개선 | ✅ 완료 | [Fork B→오케스트레이터] `PersonalDataAccessLogSearch`DAO/Service/Impl/VO + `selectAccessLogList`/`Count` sqlmap(Mysql/Oracle). 접속자ID/업무유형/대상/IP/처리구분/결과/기간 복합+페이징. Fork B 중단→오케스트레이터가 누락 sqlmap 완결(codebase 관용 맞춰 동적WHERE 인라인, XML well-formed). 화면 #47 후속 |
+| 42 | 다운로드 사유·이력 관리 | ✅ 완료 | [Fork B] `PDAL_DOWNLOAD_HISTORY`(DDL)+VO/DAO+`AccessLogDownloadService`. **사유 공백 시 IllegalArgumentException 차단** + 다운로드자·일시·IP·사유·건수 이력(본문 미보관, 10만건 상한). sqlmap 3종. **#43 리포트 실집계 교체** |
+| 43 | 정기점검 리포트 생성 배치 | ✅ 완료 | [Fork B] `PDAL_INSPECTION_REPORT`(DDL)+`InspectionReportVO`/`DAO`+`generateMonthlyInspectionReport()`+`BatchScheduler`(매월1일 05:00). 집계: 총/실패건수·#40무결성(재사용)·취급자별 top20 [구현], 이상징후#35·다운로드#42 [자리표시]. sql-map-config 무수정(기존 sqlmap 확장) |
+| 44 | 점검결과 입력·보관 화면 | ✅ 완료 | [Fork C] `InspectionResult`Service/Impl/Controller + `INSPECT_DT` 컬럼 추가 + statement 3종(목록/상세/UPDATE). `saveInspectionResult()` 결과 미입력 차단·리포트 존재확인·INSPECTOR_ID/INSPECT_RESULT/ACTN_MATTER/INSPECT_DT 갱신(감사증빙, 배치집계값 불변). 화면 #48 후속 |
+
+### Ⅳ. 신규 화면 (디자인.md 연계) — arko-frontend
+
+| # | 개발항목 | 구분 | 상태 |
+|:--:|----------|:----:|:----:|
+| 45 | 권한부여 신청/승인 관리 화면 | mnt | ⬜ 미착수 |
+| 46 | 권한변경 이력 조회 화면 | mnt | ⬜ 미착수 |
+| 47 | 개인정보 접속기록 조회/다운로드 화면 | mnt | ⬜ 미착수 |
+| 48 | 접속기록 정기점검 리포트 대시보드 | mnt | ⬜ 미착수 |
+| 49 | 비정상접근 탐지 알림함 화면 | mnt | ⬜ 미착수 |
+| 50 | 인증수단 관리 설정 화면 | mnt | ⬜ 미착수 |
+| 51 | 계정 잠금/휴면계정 관리 확장 화면 | mnt | ⬜ 미착수 |
+| 52 | 2차 인증(추가인증) 입력 화면 | frt | ⬜ 미착수 |
+| 53 | 마이페이지 인증수단 등록/관리 화면 | frt | ⬜ 미착수 |
+| 54 | 계정 잠금 안내 화면 | frt | ⬜ 미착수 |
+
+> UI 목업 브리프(스티치 AI 프롬프트 포함)는 `디자인.md`에 문서화 완료 — **설계 산출물은 준비됨**, 코드 구현은 미착수.
+
+### Ⅴ. 착수 즉시 확인 필요 사항 — arko-privacy-analyst
+
+| # | 확인항목 | 상태 | 리스크 |
+|:--:|----------|:----:|--------|
+| 55 | 운영 web.xml 필터 매핑 상태 확인 | ⬜ 미확인 | R1 — 소스/운영 배포본 상이 가능성 |
+| 56 | 휴면 배치 운영 로그 확인 | ⬜ 미확인 | MariaDB statement 부재 실운영 재확인 |
+| 57 | CONECT_IP 적재값 분포 확인 | ⬜ 미확인 | Apache+L4 이중구성 L4 IP 고정 여부 |
+| 58 | 인터셉터 등록부 현재 상태 확인 | ⬜ 미확인 | 등록 순서 재검증 |
+
+### Ⅵ. 보안·품질 점검 (SER-004) — arko-reviewer
+
+| # | 개발항목 | 상태 | 비고 |
+|:--:|----------|:----:|------|
+| 59 | 시큐어코딩 적용 및 점검 | ⬜ 미착수 | 행안부 기준 · 구현 후 |
+| 60 | 웹취약점 점검 | ⬜ 미착수 | 국정원 기준 · 개발 완료 후 |
+| 61 | 중점 점검 5종 확인 | ⬜ 미착수 | 인증우회/권한상승/접속기록조작/계정열거/인증정보노출 |
+
+---
+
+## 4. 완료된 선행 작업 (구현 착수 기반)
+
+| 산출물 | 위치 | 상태 |
+|--------|------|:----:|
+| 요구사항·기술설계 원문 | `E:\지역문화원\제안서\mds\Ⅲ_사업수행계획.md` (Ⅲ-2/3/4) | ✅ |
+| 개발항목 정리(61개) | `개인정보보호_개발항목.docx` | ✅ |
+| 신규 화면 디자인 브리프(10종) | `디자인.md` | ✅ |
+| 권한/인증 구현 에이전트 | `.claude/agents/arko-privacy-authctrl.md` | ✅ |
+| 접속기록 구현 에이전트 | `.claude/agents/arko-privacy-auditlog.md` | ✅ |
+| 요구사항 분석 에이전트 | `.claude/agents/arko-privacy-analyst.md` | ✅ |
+| 구현 패턴 스킬(권한/접속/추적/오케스트레이터) | `.claude/skills/arko-privacy-*` | ✅ |
+| 리뷰어 개인정보보호 점검항목 확장 | `.claude/agents/arko-reviewer.md` | ✅ |
+
+---
+
+## 5. 권장 착수 순서
+
+1. **Ⅴ. 착수전 확인(55~58)** — 운영 환경 실측으로 리스크 R1 등 조기 해소 (구현 전 필수)
+2. **Ⅰ-1) 권한관리(1~6)** — `getAuthorities()` 구현이 전체 권한통제의 뿌리 → 최우선
+3. **Ⅱ. 인증체계(14~29)** — 인터셉터 3단 순서 확정 후 2단계 인증·인증수단 3종
+4. **Ⅲ. 접속기록(30~44)** — 테이블·AOP·해시체인·배치
+5. **Ⅳ. 화면(45~54)** — 백엔드 진척에 맞춰 frt/mnt 병행
+6. **Ⅵ. 보안·품질(59~61)** — 각 기능 완료분 대상 상시 + 최종 웹취약점 점검
+
+## 6. 발주기관 확정 대기 항목 (블로커)
+
+| 항목 | 관련 # | 내용 |
+|------|:------:|------|
+| 계정 삭제 보존기간 | 12 | 취급자/회원 각 보존기간 정책 |
+| 문자발송모듈 연동 규격 | 19 | KT-TCS / MCSAgent3.0 연동 스펙 |
+
+---
+
+_본 문서는 `개인정보보호_개발항목.docx`의 61개 개발항목을 소스 실측(2026-07-21)과 대조해 작성한 진행현황표이다. 코드 구현 진척 시 각 항목의 상태(⬜→🟡→✅)와 소스 실측 열을 갱신한다._
diff --git a/개인정보보호_개발항목.docx b/개인정보보호_개발항목.docx
new file mode 100644
index 0000000..941c81b
Binary files /dev/null and b/개인정보보호_개발항목.docx differ
diff --git a/개인정보보호_비밀번호해시_점검노트.md b/개인정보보호_비밀번호해시_점검노트.md
new file mode 100644
index 0000000..5a6ed8e
--- /dev/null
+++ b/개인정보보호_비밀번호해시_점검노트.md
@@ -0,0 +1,63 @@
+# 비밀번호 해시 알고리즘 점검노트 (개발항목 #27)
+
+> 사업: 문화품앗이 개인정보보호 기능개선 (Ⅲ-3 인증체계 / SER-003 인증정보 보호)
+> 점검일: 2026-07-21 | 점검자: Fork C(인증체계) | 대상: `EgovFileScrty.encryptPassword()`
+> 성격: **점검(진단)만 수행 — 소스 미수정.** 조치는 후속 개발항목에서 진행.
+
+---
+
+## 1. 결론 (요약)
+
+**알고리즘은 SHA-256으로 「개인정보의 안전성 확보조치 기준」의 일방향 암호화(SHA-256 이상) 요건을 충족한다. 다만 실제 운영 경로가 무솔트(no-salt) 오버로드를 사용하고, 반복(iteration)이 없으며, 해시값을 stdout으로 출력하는 잔존 코드가 있어 3건의 보완이 권고된다.**
+
+| 점검 항목 | 판정 | 근거 |
+|-----------|:----:|------|
+| 해시 알고리즘 SHA-256 이상 | ✅ 충족 | `MessageDigest.getInstance("SHA-256")` |
+| 솔트(salt) 적용 | ⚠ 미흡 | 실제 경로가 무솔트 오버로드 사용, 솔트 API는 미사용 |
+| 키 스트레칭(반복) | ⚠ 미흡 | 1회 다이제스트, iteration/work-factor 없음 |
+| 검증 시 상수시간 비교 | ✅ 양호 | `checkPassword`가 `MessageDigest.isEqual` 사용(단, 미사용) |
+| 인증정보 로그 노출 | ⚠ 미흡 | 해시값 `System.out.println` 잔존 |
+
+---
+
+## 2. 실측 상세
+
+### 2-1. 알고리즘 (파일: `src/main/java/egovframework/com/utl/sim/service/EgovFileScrty.java`)
+
+| 메서드 | 위치 | 알고리즘 | 솔트 | 인코딩 |
+|--------|------|----------|:----:|--------|
+| `encryptPassword(String data)` | L219~237 | SHA-256 | ❌ 없음 | Base64 |
+| `encryptPassword(String data, byte[] salt)` | L246~261 | SHA-256 (`md.update(salt)` 후 `digest`) | ✅ 있음 | Base64 |
+| `checkPassword(String data, String encoded, byte[] salt)` | L271~281 | SHA-256 | ✅ 있음 | `MessageDigest.isEqual`(상수시간 비교) |
+
+- 세 메서드 모두 **단일 패스 SHA-256**(반복/키 스트레칭 없음).
+- 솔트 오버로드와 `checkPassword`는 구현되어 있으나 **실제 호출 지점이 없다**(주석 처리된 `main()` 테스트 L283~297에서만 참조).
+
+### 2-2. 실제 사용 경로 — 무솔트 오버로드가 운영 경로
+
+`encryptPassword(String)` (무솔트) 호출 지점(발췌):
+
+| 경로 | 위치 | 성격 |
+|------|------|------|
+| **로그인 인증** | `EgovLoginServiceImpl.java:83` (`actionLogin` → `enpassword`) | 취급자/회원 로그인 검증 |
+| 비밀번호 변경 | `EgovLoginServiceImpl.java:206`, `:237` | |
+| 회원/사용자/업체 가입·변경 | `EgovUserManageServiceImpl`, `EgovMberManageServiceImpl`, `EgovEntrprsManageServiceImpl` 등 다수 | 저장 시 해시 |
+| 프론트 회원관리 | `kr.or.arko.frt.biz.umt.web.*`, `kr.or.arko.frt.biz.merge.*` 등 | |
+
+→ 로그인·가입·변경 등 **모든 실제 비밀번호 처리 경로가 무솔트 SHA-256**을 사용한다. 동일 평문 → 동일 해시가 되어 레인보우 테이블·동일비밀번호 식별에 취약.
+
+### 2-3. 인증정보 로그 노출 (SER-003 / 중점점검 #61 인증정보노출 연계)
+
+- `BizMberFrtManageController.java:553`
+ `System.out.println("3패스워드확인::" + EgovFileScrty.encryptPassword(mberManageVO.getPassword()));`
+ → 비밀번호 해시값을 표준출력(로그)에 기록. 디버그 잔존 코드로 판단되며 제거 대상.
+
+---
+
+## 3. 권고 (조치는 후속 항목에서)
+
+1. **솔트 전면 적용** — 운영 경로를 솔트 오버로드(`encryptPassword(String, byte[])`) + `checkPassword`로 이설. 사용자별 랜덤 솔트를 생성·저장(가입 시)하고 검증은 `checkPassword`(상수시간 비교)로 통일. 기존 무솔트 해시는 로그인 성공 시점 재해시(rehash-on-login) 방식으로 점진 마이그레이션.
+2. **키 스트레칭 검토** — 단일 SHA-256 대신 반복(iteration) 적용 또는 표준 비밀번호 해시(PBKDF2 등)로의 상향을 발주기관과 협의. (기준상 SHA-256은 충족이나, 브루트포스 내성 강화 관점의 개선 권고.)
+3. **해시값 로그 출력 제거** — `BizMberFrtManageController.java:553`의 `System.out.println` 삭제. 신규·변경 코드에 인증정보(비밀번호/해시/OTP비밀키) 출력 금지 규칙 적용(보안·품질 #61과 연계 점검).
+
+> 본 점검은 진단 단계 산출물이며, 위 3건의 실제 코드 수정은 별도 개발항목 승인 후 진행한다.
diff --git a/개인정보보호_시스템로그삭제정책_분리노트.md b/개인정보보호_시스템로그삭제정책_분리노트.md
new file mode 100644
index 0000000..9a91963
--- /dev/null
+++ b/개인정보보호_시스템로그삭제정책_분리노트.md
@@ -0,0 +1,45 @@
+# COMTNSYSLOG 7일 삭제정책 분리 분석노트 (#38)
+
+> 문화품앗이 개인정보보호 기능개선 사업 (SFR-004 / DBR-001 / SER-002)
+> 개발항목 #38 — 담당: Fork C | 작성일: 2026-07-21
+> 목적: 기존 시스템로그(COMTNSYSLOG) 7일 삭제정책이 신규 개인정보 접속기록
+> (`PERSONAL_DATA_ACCESS_LOG`, 730일 보관)에 **오적용되지 않도록** 분리 상태를 진단·보강한다.
+
+---
+
+## 1. 기존 7일 삭제정책 실측 (파일:라인)
+
+| 계층 | 위치 | 내용 |
+|------|------|------|
+| SQL(삭제문) | `src/main/resources/egovframework/sqlmap/com/sym/log/lgm/EgovSysLog_SQL_Mysql.xml:159~165` | `DELETE FROM COMTNSYSLOG WHERE OCCRRNC_DE < DATE_FORMAT(ADDDATE(SYSDATE(), -7), '%Y%m%d')` |
+| DAO | `src/main/java/egovframework/com/sym/log/lgm/service/impl/SysLogDAO.java:47~50` | `logInsertSysLogSummary()` 내부에서 **요약 INSERT 직후 삭제 DELETE를 한 메서드로 묶어 호출** |
+| Service | `.../service/impl/EgovSysLogServiceImpl.java:60~64` | `logInsertSysLogSummary()` 위임 |
+| Scheduling | `.../service/EgovSysLogScheduling.java:36~38` | `sysLogSummary()` → 서비스 호출 |
+| 스케줄 설정 | `src/main/resources/egovframework/spring/com/context-scheduling-sym-log-lgm.xml:6~28` | Quartz `MethodInvokingJobDetailFactoryBean`+`SimpleTriggerBean`(1시간 주기). **단, `SchedulerFactoryBean`(L22~28)은 주석 처리 → 현재 비활성** |
+| 타 DB 동일본 | `EgovSysLog_SQL_{Oracle,Altibase,Cubrid,Tibero}.xml`의 동일 `SysLogDAO.logDeleteSysLogSummary` | MySQL과 동일 구조 |
+
+**대상:** `COMTNSYSLOG` 단일 테이블. 삭제 기준 컬럼 `OCCRRNC_DE`(발생일자, `YYYYMMDD` 문자열). 보관 7일.
+**주기:** 정의상 매 1시간(요약 배치에 종속) — 요약 INSERT가 실행될 때마다 7일 경과분 삭제.
+**현재 활성 여부:** **비활성.** 트리거를 물고 있는 `SchedulerFactoryBean`이 주석 처리되어 스케줄러에 등록되지 않음(`context-scheduling-sym-log-lgm.xml:22~28`).
+
+## 2. 개인정보 접속기록 오적용 위험 판정
+
+**판정: 직접 오적용 위험 낮음(구조적 분리됨). 단, 결합 설계로 인한 잠재적 혼동 리스크 있음.**
+
+- 삭제문이 테이블명 `COMTNSYSLOG`에 하드코딩되어 있어 `PERSONAL_DATA_ACCESS_LOG`를 물리적으로 건드릴 수 없다. namespace도 `SysLogDAO`로 분리되어 있다.
+- 신규 접속기록의 유일한 삭제 경로는 **파티션 DROP 방식 730일 파기 배치(#37, Fork A 담당)**로 별도 설계되어, 개별 DELETE 및 7일 정책과 경로가 완전히 다르다.
+- **다만 잠재 리스크 2가지:**
+ 1. **결합(coupling) 리스크** — 7일 DELETE가 `logInsertSysLogSummary()`(요약 INSERT)에 **묶여 있어**, 향후 이 메서드를 재사용·확장하는 개발자가 삭제 부작용을 인지하지 못할 수 있다. 요약과 파기는 관심사가 다르므로 분리가 바람직.
+ 2. **오해 리스크** — 신규 접속기록 보관/파기를 구현하며 기존 `SysLogDAO`/`EgovSysLog_SQL_*.xml`에 접속기록용 statement를 추가하면 두 로그 체계가 한 namespace에 섞여 7일 정책이 잘못 상속될 수 있다.
+
+## 3. 분리 보강 권고
+
+1. **namespace 절대 분리(필수):** 개인정보 접속기록 관련 SQL은 **절대** `SysLogDAO`/`EgovSysLog_SQL_*.xml`에 추가하지 않는다. 신규 `PrivacyAccessLog*`/전용 sqlmap namespace로만 관리한다. (Fork A #30/#37 산출물이 이미 별도 DDL/파일로 분리됨 — 이 원칙 유지.)
+2. **`PERSONAL_DATA_ACCESS_LOG`에 UPDATE/DELETE statement 미정의(필수):** 애플리케이션 계층에 개별 DELETE를 만들지 않는다(위·변조 방지 + 보관정책 일원화). 유일 삭제 경로 = 파티션 DROP 파기 배치(#37).
+3. **방어 주석 추가(권고):** `EgovSysLog_SQL_Mysql.xml`의 `logDeleteSysLogSummary` 및 `SysLogDAO.logInsertSysLogSummary()`에 "이 7일 삭제정책은 COMTNSYSLOG 전용이며 PERSONAL_DATA_ACCESS_LOG(730일)에 적용 금지" 주석을 남겨 혼동 차단. (본 노트는 진단 중심이라 소스 미수정 — 방어 주석은 후속 반영 권고.)
+4. **요약/파기 분리(선택·개선):** 7일 삭제를 요약 INSERT 메서드에서 떼어 별도 메서드로 분리하면 결합 리스크 해소. 단 기존 동작 변경이므로 운영 영향 검토 후 진행.
+5. **활성화 시 주의(운영):** 현재 시스템로그 요약/삭제 스케줄러가 비활성 상태다. 향후 활성화(`SchedulerFactoryBean` 주석 해제) 시 7일 삭제가 함께 켜지므로, 접속기록 파기 배치(#37)와 **주기·대상이 독립적으로 동작하는지** 재확인한다.
+
+## 4. 결론
+
+기존 7일 삭제정책은 `COMTNSYSLOG` 전용으로 물리·논리적으로 분리되어 있어 신규 접속기록에 직접 오적용될 위험은 낮으며, 현재 스케줄러 자체가 비활성이다. 다만 삭제가 요약 배치에 결합된 구조라 향후 혼동을 막기 위해 **① 접속기록 SQL의 namespace 절대 분리, ② 접속기록 DELETE statement 미정의, ③ 방어 주석**을 유지·반영할 것을 권고한다. 이 원칙은 Fork A의 #30(DDL)/#37(파티션 파기 배치) 산출물이 별도 파일·namespace로 이미 준수하고 있다.
diff --git a/개인정보보호_인증정보저장정책_점검노트.md b/개인정보보호_인증정보저장정책_점검노트.md
new file mode 100644
index 0000000..b28f2cf
--- /dev/null
+++ b/개인정보보호_인증정보저장정책_점검노트.md
@@ -0,0 +1,43 @@
+# 신규 인증정보 저장정책 점검노트 (#28, SER-003)
+
+> 문화품앗이 개인정보보호 기능개선 사업 | 작성일: 2026-07-21
+> 대상: 추가 인증수단 3종(OTP/SMS/공동인증서)의 인증정보 저장정책 준수 여부 점검
+> 근거: 개발항목 #28 — "OTP비밀키 암호화저장 / 휴대폰 인증번호 미저장 / 공동인증서 본문 미저장(식별값만 매핑)"
+
+## 1. 점검 결론
+
+| 인증수단 | 저장정책 | 준수 | 근거(파일:라인) |
+|----------|----------|:----:|-----------------|
+| OTP(#18) | 비밀키 **암호화 저장**, 복호화는 검증 순간 한정, 로그·화면 미노출 | ✅ 준수 | `OtpAuthService.java:85`(encrypt 후 save), `:97~107`(검증 시 복호화) |
+| SMS(#19) | 인증번호 **미저장**(만료시각만 세션/캐시), 3분 내 검증 후 폐기 | ✅ 설계 준수 | `SmsAuthService.java:23,31,35`(미저장 명시, 실발송은 발주기관 연동 대기) |
+| 공동인증서(#20) | 인증서 **본문 미저장**, 식별값(DN)만 매핑 | ✅ 준수 | `CertAuthService.java:30,69`(DN만 대조, 본문 미취급) |
+
+**종합: 3종 모두 SER-003 저장정책을 준수한다. 단 OTP 프로비저닝 URI 취급에 1건 주의(아래 3절).**
+
+## 2. 인증수단별 상세
+
+### 2.1 OTP (OtpAuthService) — ✅ 준수
+- **암호화 저장**: `issue/provision()`에서 160bit 비밀키 생성 → `OtpSecretCipher.encrypt()`(AES-128/GCM) 후 `OtpSecretStore.save()`. 평문 비밀키를 저장소에 넣지 않는다. (`OtpAuthService.java:81~85`)
+- **복호화 최소화**: `verify()`에서만 복호화(`:107 secretCipher.decrypt`), 검증 직후 지역변수로만 사용.
+- **로그/예외 노출 없음**: 비밀키·OTP 코드를 `LOG`/`System.out`/예외 메시지에 출력하는 코드 없음(grep 결과 0건). `isAvailable()`은 존재여부(boolean)만 반환.
+- **마스터키 외부화**: 암호화 마스터키는 `arko.otp.master.key` 프로퍼티/환경변수(미설정 시 개발용 폴백 + TODO). → 운영 배포 시 마스터키 주입 필수(핸드오프).
+
+### 2.2 SMS (SmsAuthService) — ✅ 설계 준수 (실발송 #19 대기)
+- 클래스 주석·시그니처에 **인증번호 미저장**(만료시각만 세션/캐시 보관), 3분 내 검증 후 폐기 명시(`:23,31`).
+- 실제 발송·검증 본문은 `// TODO(#19)`: KT-TCS/MCSAgent3.0 연동규격 **발주기관 확정 대기**. 구현 시에도 인증번호 원문 저장 금지 원칙 유지할 것(핸드오프).
+
+### 2.3 공동인증서 (CertAuthService) — ✅ 준수
+- `verify(uniqId, code)`에서 code=인증서 **DN(식별값)**을 기존 GPKI 경로(`EgovLoginServiceImpl.actionCrtfctLogin` → `COMTNEMPLYRINFO.CRTFC_DN_VALUE`)로 대조. **인증서 본문(공개키/서명 원문)은 취급·저장하지 않음**(`:30,69`).
+- `issue()`는 서버측 no-op(클라이언트 서명 기반), 실제 서명요청은 `CertProcessFilter`(web.xml:46 주석) 재활성 핸드오프.
+
+## 3. 주의·보완 권고
+
+1. **OTP 프로비저닝 URI(주의)**: `OtpAuthService.buildOtpAuthUri()`(`:147~151`)가 반환하는 `otpauth://...?secret=` URI에는 **평문 비밀키가 포함**된다(QR 등록용 표준 동작). 이 URI는 등록 화면(#53)에서 **HTTPS 전송·1회 표시·로그 미기록** 원칙으로 취급해야 한다. 화면 구현(#53) 시 이 URI를 서버/접근로그·에러로그에 남기지 말 것.
+2. **OTP 마스터키 운영 주입**: 개발용 폴백 키가 운영에 유입되지 않도록 배포 시 `arko.otp.master.key` 필수 설정.
+3. **SMS 구현 시 원칙 유지**(#19): 인증번호 원문 미저장, 세션/캐시엔 만료시각·해시만.
+4. **접속기록 연계**: 2차 인증 성공/실패도 개인정보 접근 맥락에서 접속기록(#31) 대상 여부 검토(현재 pointcut은 개인정보 처리 서비스 5종 한정).
+
+## 4. 관련 개발항목
+- [#18 OtpAuthService](#) TOTP·AES-GCM / [#19 SmsAuthService](#) 발주기관 연동 대기 / [#20 CertAuthService](#) GPKI 위임
+- [#27 비밀번호 해시 점검노트](개인정보보호_비밀번호해시_점검노트.md) — 기존 비밀번호(SHA-256, 무솔트 보완권고)와 별개
+- 저장정책 위반 소지: 점검 시점 **0건**(로그/화면 평문 노출 없음). OTP 프로비저닝 URI만 화면 구현 시 주의.
diff --git a/디자인.md b/디자인.md
new file mode 100644
index 0000000..fb15ecd
--- /dev/null
+++ b/디자인.md
@@ -0,0 +1,226 @@
+# 디자인.md — 개인정보보호 기능개선 신규 화면 디자인 브리프 (스티치 AI 연동용)
+
+> 2026 문화품앗이 개인정보보호 기능개선 사업(한국문화원연합회 발주, SFR-001~004)의 신규 화면을
+> [Google Stitch](https://stitch.withgoogle.com/) 로 목업 생성하기 위한 디자인 브리프.
+> 대상 도메인: **frt**(일반회원), **mnt**(관리자). mbl/eng는 이번 범위에서 제외.
+
+## 0. 사용법
+
+1. 새 Stitch 프로젝트를 열 때, **2. 공통 디자인 시스템**의 해당 도메인(frt 또는 mnt) 블록을
+ 프롬프트 맨 앞에 항상 포함한다 — 기존 서비스와 톤앤매너가 어긋난 목업이 나오는 것을 방지한다.
+2. **4. 신규 화면 명세**의 각 화면 카드 안 "스티치 프롬프트" 코드블록을 그대로 복사해
+ Stitch의 화면 생성/편집 입력창에 붙여넣는다.
+3. 화면 간 일관성이 필요하면(예: mnt 신규 화면 7개를 한 앱으로) 여러 화면 프롬프트를 이어 붙여
+ 한 번에 "여러 화면이 있는 앱" 형태로 요청해도 된다.
+4. Stitch 결과물은 목업일 뿐이다. 실제 반영 시 **5. 반영 체크리스트**를 반드시 확인한다.
+
+## 1. 요구사항 ↔ 화면 매핑
+
+| 요구사항 영역 | 대상 패키지 | 신규 화면 | 도메인 |
+|---|---|---|---|
+| Ⅲ-2 권한관리 | `egovframework.com.sec.ram/rgm` | 권한부여 신청/승인 관리 | mnt |
+| Ⅲ-2 권한관리 | 〃 | 권한변경 이력 조회 (AUTHOR_CHANGE_HISTORY) | mnt |
+| Ⅲ-3 인증체계 | `egovframework.com.uat.uia` | 2차 인증(추가인증) 입력 화면 | frt |
+| Ⅲ-3 인증체계 | 〃 | 마이페이지 – 인증수단 등록/관리 | frt |
+| Ⅲ-3 인증체계 | 〃 | 인증수단 관리 설정 (AuthMethodService 3종) | mnt |
+| Ⅲ-3 인증체계 | `egovframework.com.uss.umt` | 계정 잠금/휴면계정 관리 확장 | mnt |
+| Ⅲ-3 인증체계 | 〃 | 계정 잠금 안내 화면 | frt |
+| Ⅲ-4 접속기록 | `egovframework.com.sym.log` | 개인정보 접속기록 조회/다운로드 (PERSONAL_DATA_ACCESS_LOG) | mnt |
+| Ⅲ-4 접속기록 | 〃 | 접속기록 정기점검 리포트 대시보드 | mnt |
+| Ⅲ-4 접속기록 | 〃 | 비정상접근 탐지 알림함 (5유형) | mnt |
+
+## 2. 공통 디자인 시스템
+
+### 2-1. frt (일반회원 — 리뉴얼 디자인, `webapp/new/`)
+
+```
+스타일: 공공기관 회원 포털의 2024년 리뉴얼 디자인. 따뜻하고 신뢰감 있는 톤, 둥근 모서리와 부드러운 그림자.
+폰트: 제목은 GmarketSans Bold, 본문/라벨은 Pretendard.
+주 색상: #f1692a (버튼/포인트), hover 시 #f04c1b.
+중립 색상: 본문 텍스트 #212121, 보조 텍스트 #616161, 흐린 텍스트 #9e9e9e,
+ 구분선 #eeeeee / #e0e0e0, 옅은 배경 #fcfcfc / #f9f9f9 / #f5f5f5, 카드 배경 흰색.
+경고/에러 텍스트: #ed4040.
+버튼: border-radius 6px, 배경 #f1692a, 글자색 흰색, 폰트 굵게.
+입력폼: line-height 45px, padding 16px, 얇은 회색 테두리.
+카드/패널: border-radius 10px, box-shadow 0 0 20px rgba(0,0,0,0.2), 내부 padding 56px 48px, 흰 배경.
+상태 뱃지(둥근 알약형): 대기/요청 상태는 빨강(#ed4040), 진행/활성 상태는 파랑(#3c79d5),
+ 종료/마감 상태는 회색(#848690).
+레이아웃: 최소 폭 1400px 데스크톱 기준, 본문 영역은 최대 폭 1200px 중앙 정렬.
+상단에 페이지 타이틀(중앙 정렬, 32px bold)과 우측 정렬 breadcrumb(홈 아이콘 > 상위메뉴 > 현재메뉴).
+```
+
+### 2-2. mnt (관리자 — 레거시 백오피스 스킨, `webapp/css/site/mnt/`)
+
+```
+스타일: 데이터 밀도가 높은 전통적인 관리자 백오피스. 각진 사각형, 그림자 없음, 여백이 좁고 촘촘함.
+ frt의 둥근 카드/그림자 스타일과 절대 섞지 않는다.
+폰트: 나눔고딕(NanumGothic) 본문, 맑은 고딕 보조.
+주 색상: #005ea8 (헤더/포인트 블루), 강조 숫자·통계는 #f1592a (오렌지).
+중립 색상: 본문 텍스트 #333 / #636363, 얇은 회색 테두리 #ddd / #d1d1d1, 옅은 배경 #f4f4f4.
+레이아웃: 상단에 검색 필터 영역(fieldset, 라벨+드롭다운+텍스트입력+검색버튼이 한 줄에 나열),
+ 그 아래 데이터 표(테두리 있는 table, 헤더 행 진한 배경, 좌측 체크박스 열, 우측 정렬 숫자),
+ 표 하단 중앙 페이지네이션, 표 아래 우측 정렬 액션 버튼 그룹(각진 사각 버튼).
+대시보드형 화면은 상단에 통계 요약 카드(사각형, 얇은 테두리, 큰 숫자 + 작은 라벨) 3~6개를 가로로 배치.
+```
+
+## 3. 신규 화면 공통 원칙
+
+- mnt 신규 화면은 기존 `EgovAccountUnlockManage.jsp` 류의 **검색필터 + 표 + 페이지네이션 + 하단 액션버튼** 구조를 그대로 따른다. 완전히 새로운 레이아웃 문법을 만들지 않는다.
+- frt 신규 화면(2차 인증 등)은 `frt/member/login.jsp` 류의 **중앙 정렬 카드(boxR-shadow) + form-control 입력 + btn-login 버튼** 구조를 그대로 따른다.
+- 승인/이력/로그 성격의 화면은 상태를 항상 색상 뱃지로 표시한다 (대기=빨강, 진행/승인=파랑, 종료/반려=회색 — 2-1 참조).
+- 접속기록·이력류 화면은 반드시 **기간 필터 + 다운로드(엑셀/CSV) 버튼**을 상단에 노출한다 (개인정보 처리방침상 열람 이력 제공 요건).
+
+## 4. 신규 화면 명세
+
+### [mnt] 권한부여 신청/승인 관리
+
+- **요구사항:** Ⅲ-2 권한관리 — 승인 워크플로
+- **목적:** 관리자 권한 신청 → 승인권자 승인/반려 → 이력 자동 기록
+- **핵심 요소:** 상단 통계카드(대기/승인/반려 건수), 검색필터(신청자/신청부서/상태/신청기간), 목록표(신청자, 소속, 요청권한, 신청사유, 신청일, 상태뱃지, 승인자), 행 선택 후 일괄 승인/반려, 반려 시 사유 입력 필수 팝업
+
+```
+mnt 관리자 백오피스 스타일로 "권한부여 신청/승인 관리" 화면을 그려줘.
+상단에 대기/승인/반려 건수를 보여주는 사각형 통계 카드 3개를 가로로 배치.
+그 아래 검색 필터(신청자, 신청부서 드롭다운, 상태 드롭다운, 신청기간 날짜범위, 검색 버튼).
+그 아래 표: 체크박스, 번호, 신청자, 소속부서, 요청권한, 신청사유, 신청일, 상태(빨강=대기/파랑=승인/회색=반려 알약형 뱃지), 승인자.
+표 하단에 페이지네이션, 우측에 "일괄 승인", "반려" 각진 사각 버튼.
+행을 클릭하면 상세 승인 팝업이 뜨고, 팝업 안에는 신청 상세 정보와 반려 사유 입력 textarea, 승인/반려 버튼이 있음.
+색상은 포인트 블루 #005ea8, 강조 오렌지 #f1592a, 그림자 없는 각진 스타일.
+```
+
+### [mnt] 권한변경 이력 조회 (AUTHOR_CHANGE_HISTORY)
+
+- **요구사항:** Ⅲ-2 권한관리 — 변경 이력 추적
+- **목적:** 누가/언제/어떤 권한을/누구에게/왜 변경했는지 열람
+- **핵심 요소:** 검색필터(대상자/처리자/변경유형/기간), 목록표(변경일시, 대상자, 변경 전 권한, 변경 후 권한, 처리자, 처리사유, IP), 엑셀 다운로드 버튼
+
+```
+mnt 관리자 백오피스 스타일로 "권한변경 이력 조회" 화면을 그려줘.
+상단 검색 필터: 대상자 이름/ID, 처리자, 변경유형 드롭다운(부여/회수/변경), 기간 날짜범위, 검색 버튼, 우측에 "엑셀 다운로드" 버튼.
+그 아래 표: 번호, 변경일시, 대상자, 변경 전 권한, 변경 후 권한, 처리자, 처리사유, 접속 IP.
+표 하단 페이지네이션. 그림자 없는 각진 표, 헤더 행은 옅은 파란 배경, 포인트 컬러는 #005ea8.
+```
+
+### [mnt] 개인정보 접속기록 조회/다운로드 (PERSONAL_DATA_ACCESS_LOG)
+
+- **요구사항:** Ⅲ-4 접속기록
+- **목적:** 개인정보처리시스템 접속기록(조회/변경/삭제/다운로드) 열람 및 법정 보관기간 내 제공
+- **핵심 요소:** 검색필터(처리자/대상 개인정보주체/처리유형: 조회·다운로드·수정·삭제/기간), 목록표(처리일시, 처리자, 처리자 IP, 대상 개인정보주체, 처리유형뱃지, 처리 메뉴/화면명, 처리결과), 상세보기 팝업, 다운로드(CSV) 버튼, 우측 상단에 "3년 보관, 730일 경과 데이터는 자동 파기 대상" 안내 문구
+
+```
+mnt 관리자 백오피스 스타일로 "개인정보 접속기록 조회" 화면을 그려줘.
+상단에 작은 안내 배너: "본 기록은 관련 법령에 따라 일정 기간 보관되며 이후 자동 파기됩니다."
+검색 필터: 처리자 ID, 대상 개인정보주체 ID, 처리유형 드롭다운(조회/다운로드/수정/삭제), 기간 날짜범위, 검색/전체보기 버튼, 우측에 "CSV 다운로드" 버튼.
+표: 번호, 처리일시, 처리자, 처리자 IP, 대상자, 처리유형(색상 알약형 뱃지: 조회=파랑, 다운로드=주황, 수정=초록, 삭제=빨강), 처리 화면/메뉴명, 처리결과.
+행 클릭 시 상세 팝업(처리 전/후 값, 사유, 승인여부 표시).
+하단 페이지네이션. 각진 표 스타일, 포인트 컬러 #005ea8, 강조 오렌지 #f1592a.
+```
+
+### [mnt] 접속기록 정기점검 리포트 대시보드
+
+- **요구사항:** Ⅲ-4 접속기록 — 정기 점검
+- **목적:** 월별/기간별 접속기록 통계를 요약해 정기 점검 보고서 형태로 확인
+- **핵심 요소:** 상단 요약 통계 카드(총 접속건수, 다운로드 건수, 이상탐지 건수, 미확인 알림 수), 기간별 추이 막대/선 그래프, 처리유형별 비중 도넛/막대, 하단 이상탐지 상세 리스트, "점검 리포트 생성" 버튼(PDF/엑셀 산출)
+
+```
+mnt 관리자 백오피스 스타일로 "접속기록 정기점검 리포트" 대시보드 화면을 그려줘.
+상단에 사각형 통계 카드 4개(총 접속건수, 다운로드 건수, 이상탐지 건수, 미확인 알림 수) 가로 배치, 큰 숫자는 오렌지 #f1592a.
+그 아래 좌측에 최근 6개월 접속건수 추이 막대 그래프, 우측에 처리유형별 비중 도넛 차트.
+하단에 "이상탐지 상세" 표: 탐지일시, 탐지유형, 대상 계정, 위험도(높음=빨강/중간=주황/낮음=회색 뱃지), 확인여부, 조치 버튼.
+우측 상단에 "점검 리포트 생성" 각진 버튼(PDF/엑셀 아이콘).
+그림자 없는 각진 카드, 포인트 블루 #005ea8, 데이터 밀도 높은 백오피스 톤.
+```
+
+### [mnt] 비정상접근 탐지 알림함 (5유형)
+
+- **요구사항:** Ⅲ-4 접속기록 — 이상행위 탐지
+- **목적:** 동시간대 다중 IP 접속, 심야시간 대량 접속, 대량 조회/다운로드, 반복 인증 실패, 권한 외 화면 접근 등 5개 유형의 이상탐지 알림 확인·조치
+- **핵심 요소:** 좌측 유형별 필터 탭(5유형 + 전체), 목록표(탐지일시, 탐지유형뱃지, 대상 계정, 상세 사유, 위험도, 확인여부), 미확인 항목 강조, 행 클릭 시 상세 팝업(관련 접속기록 원본 연결), "확인 처리"/"계정 임시잠금" 액션 버튼
+
+```
+mnt 관리자 백오피스 스타일로 "비정상접근 탐지 알림함" 화면을 그려줘.
+좌측에 세로 탭 필터: 전체 / 동시간대 다중IP접속 / 심야시간 대량접속 / 대량 조회·다운로드 / 반복 인증실패 / 권한외 화면접근.
+우측 메인에 표: 번호, 탐지일시, 탐지유형(색상 알약형 뱃지), 대상 계정, 상세 사유 텍스트, 위험도(높음=빨강/중간=주황/낮음=회색), 확인여부(미확인 행은 옅은 빨간 배경으로 강조).
+행 클릭 시 상세 팝업: 관련 접속기록 원본 리스트, "확인 처리", "계정 임시잠금" 각진 버튼.
+표 하단 페이지네이션. 그림자 없는 각진 스타일, 포인트 블루 #005ea8, 위험 강조는 #ed4040.
+```
+
+### [mnt] 인증수단 관리 설정 (AuthMethodService 3종)
+
+- **요구사항:** Ⅲ-3 인증체계
+- **목적:** 관리자가 서비스 전체에서 허용할 2차 인증수단(예: OTP 앱, SMS, 이메일 등 3종)을 활성/비활성 설정하고 회원구분별 강제 적용 여부 지정
+- **핵심 요소:** 인증수단 3개를 카드 또는 표 형태로 나열, 각 항목에 토글 스위치(사용/미사용), 회원구분별(개인/그룹/활동처/기관관리자) 강제 적용 체크박스 매트릭스, 저장 버튼
+
+```
+mnt 관리자 백오피스 스타일로 "인증수단 관리 설정" 화면을 그려줘.
+상단에 인증수단 3개(OTP 앱, SMS 인증, 이메일 인증)를 가로로 나열한 카드형 표, 각 카드에 아이콘, 설명, 우측에 사용/미사용 토글 스위치.
+그 아래 표: 행은 회원구분(개인/그룹/활동처/기관관리자), 열은 인증수단 3종, 각 셀에 체크박스로 "이 회원구분에 이 인증수단 강제 적용" 설정.
+하단 우측에 "저장" 각진 버튼. 포인트 블루 #005ea8, 각진 표 스타일, 그림자 없음.
+```
+
+### [mnt] 계정 잠금/휴면계정 관리 확장
+
+- **요구사항:** Ⅲ-3 인증체계 — 계정 잠금 5회, 휴면계정 정책
+- **목적:** 기존 잠금해제 관리 화면에 "휴면계정" 탭과 잠금 사유(5회 실패/관리자 수동/휴면전환)를 구분해 표시
+- **핵심 요소:** 상단 탭(잠금 계정 / 휴면 계정), 검색필터(회원구분/잠금·휴면 사유/기간), 목록표(계정, 이름, 최종접속일, 잠금·휴면 사유뱃지, 처리버튼), 일괄 해제
+
+```
+mnt 관리자 백오피스 스타일로 "계정 잠금/휴면계정 관리" 화면을 그려줘.
+상단에 탭 2개: "잠금 계정" / "휴면 계정".
+탭 아래 검색필터: 회원구분 드롭다운, 잠금·휴면 사유 드롭다운(5회 실패/관리자 수동/장기 미접속 휴면전환), 기간, 검색버튼.
+표: 체크박스, 번호, 회원구분, 이름, 계정ID, 최종접속일, 사유(알약형 뱃지: 5회실패=빨강, 관리자수동=회색, 휴면전환=주황), 처리상태.
+하단에 "선택 해제", "전체 해제" 각진 버튼. 포인트 블루 #005ea8, 각진 표 스타일.
+```
+
+### [frt] 2차 인증(추가인증) 입력 화면
+
+- **요구사항:** Ⅲ-3 인증체계
+- **목적:** 아이디/비밀번호 로그인 성공 후 추가 인증수단(OTP/SMS/이메일) 입력을 요구
+- **핵심 요소:** 중앙 정렬 카드, 안내 문구, 인증수단 선택 탭 또는 라디오, 인증번호 입력창 + 타이머(예: 03:00), 재발송 버튼, 인증 확인 버튼
+
+```
+frt 회원 포털의 리뉴얼 디자인 스타일로 "2차 인증(추가인증)" 화면을 그려줘.
+중앙에 둥근 모서리(radius 10px)와 부드러운 그림자가 있는 흰색 카드, 폭은 화면 중앙 800px.
+카드 상단에 자물쇠 아이콘과 "본인확인을 위해 추가 인증이 필요합니다" 안내 문구(GmarketSans 제목 폰트).
+그 아래 인증수단 선택 라디오 3개(OTP 앱 / SMS 인증 / 이메일 인증).
+그 아래 인증번호 입력창(둥근 테두리, padding 넉넉)과 우측에 "03:00" 남은시간 타이머, 아래에 작은 "인증번호 재발송" 링크.
+하단에 폭 100%, radius 6px, 배경 오렌지(#f1692a) "인증 확인" 버튼.
+전체적으로 따뜻하고 신뢰감 있는 공공서비스 톤, 여백 넉넉.
+```
+
+### [frt] 마이페이지 – 인증수단 등록/관리
+
+- **요구사항:** Ⅲ-3 인증체계
+- **목적:** 회원이 마이페이지에서 본인 인증수단을 등록/변경/해제
+- **핵심 요소:** 등록된 인증수단 리스트(카드형), 각 항목에 상태뱃지(등록됨/미등록), 등록·해제 버튼, OTP 등록 시 QR코드 표시 팝업
+
+```
+frt 회원 포털의 리뉴얼 디자인 스타일로 마이페이지 내 "인증수단 관리" 화면을 그려줘.
+상단 타이틀 "인증수단 관리"(중앙정렬, GmarketSans bold, 32px)와 우측 브레드크럼(홈 > 마이페이지 > 인증수단 관리).
+본문 최대폭 1200px 중앙정렬. 그 아래 카드 3개를 세로로 나열(OTP 앱, SMS 인증, 이메일 인증), 각 카드는 둥근 모서리 흰 배경에 그림자.
+각 카드 좌측에 아이콘과 인증수단명, 우측에 상태 뱃지(등록됨=파랑 알약형, 미등록=회색)와 "등록" 또는 "해제" 버튼(radius 6px, 오렌지 배경).
+OTP 카드의 "등록" 버튼을 누르면 열리는 팝업: QR코드 이미지, 등록 확인용 6자리 코드 입력창, "등록 완료" 버튼.
+```
+
+### [frt] 계정 잠금 안내 화면
+
+- **요구사항:** Ⅲ-3 인증체계 — 로그인 5회 실패 시 잠금
+- **목적:** 로그인 5회 실패로 잠긴 계정에게 사유와 해제 절차 안내
+- **핵심 요소:** 경고 아이콘, 잠금 사유/잠금 해제 가능 시각 안내, 본인인증을 통한 잠금 해제 버튼, 고객센터 안내
+
+```
+frt 회원 포털의 리뉴얼 디자인 스타일로 "계정 잠금 안내" 화면을 그려줘.
+중앙 정렬 카드(둥근 모서리, 그림자), 상단에 경고 아이콘과 빨간색(#ed4040) "계정이 잠겼습니다" 타이틀.
+그 아래 안내 문구: "비밀번호 5회 오류로 계정이 잠금 처리되었습니다." 및 잠금 해제 가능 조건 설명.
+중앙에 "본인인증하고 잠금 해제하기" radius 6px 오렌지 버튼.
+하단에 옅은 배경 박스로 고객센터 문의 안내(전화번호, 운영시간).
+```
+
+## 5. 반영 체크리스트 (Stitch 목업 → 실제 구현 시)
+
+- [ ] mnt 화면은 실제 구현 시 `ui:pagination` 태그, `EgovComAbstractDAO` 기반 iBatis 2 쿼리와 맞물리므로, Stitch가 제안한 표 컬럼 수/정렬이 실제 VO 필드와 일치하는지 확인
+- [ ] 상태 뱃지 색상은 2-1/2-2에 정의된 팔레트를 그대로 사용 (임의 색상 추가 금지)
+- [ ] frt 신규 화면은 `new/css/sub/member.css` 등 기존 클래스(`boxR-shadow`, `box-sm`, `form-control`, `btn-login`)를 재사용하고 새 클래스를 남발하지 않는다
+- [ ] 다운로드(CSV/엑셀) 버튼이 포함된 화면은 실제 다운로드 시 개인정보 마스킹/권한 체크 로직이 필요함을 백엔드 설계에 반영
+- [ ] 접근성: 상태를 색상만으로 구분하지 않고 텍스트 라벨을 항상 병기 (색약 사용자 고려)
+- [ ] Stitch가 생성한 폰트가 Pretendard/GmarketSans/NanumGothic이 아니면 실제 반영 시 교체