diff --git a/src/main/java/egovframework/com/cmm/privacy/auditlog/PersonalDataAccessLogController.java b/src/main/java/egovframework/com/cmm/privacy/auditlog/PersonalDataAccessLogController.java
index 35b8c98..81d445a 100644
--- a/src/main/java/egovframework/com/cmm/privacy/auditlog/PersonalDataAccessLogController.java
+++ b/src/main/java/egovframework/com/cmm/privacy/auditlog/PersonalDataAccessLogController.java
@@ -9,9 +9,12 @@ import java.net.URLEncoder;
import java.text.SimpleDateFormat;
import java.util.ArrayList;
import java.util.Date;
+import java.util.Calendar;
+import java.util.HashSet;
import java.util.LinkedHashMap;
import java.util.List;
import java.util.Map;
+import java.util.Set;
import javax.annotation.Resource;
import javax.servlet.http.HttpServletRequest;
@@ -58,6 +61,19 @@ public class PersonalDataAccessLogController {
/** 화면에 띄울 '내 다운로드 요청' 최대 건수(만료 전 기준). 폭주 방지용 상한이다. */
private static final int MY_JOB_MAX = 100;
+ /** 감사 표시 — 이상(처리결과 차단·비인가). */
+ private static final String AUDIT_ABNORMAL = "A";
+ /** 감사 표시 — 점검(정상 처리였으나 이상징후 대상). */
+ private static final String AUDIT_WATCH = "W";
+ /** 감사 표시 계산 시 기간 미지정이면 적용할 기본 조회기간(일). */
+ private static final int AUDIT_DEFAULT_DAYS = 30;
+ /** 업무시간 시작(시). AbnormalAccessPolicy 기본값과 동일. */
+ private static final int OFF_HOURS_START = 9;
+ /** 업무시간 종료(시). */
+ private static final int OFF_HOURS_END = 18;
+ /** 화면 안내에 표기할 접속기록 보관기간(일). PrivacyBatchServiceImpl 의 파기 기준과 같은 값. */
+ private static final int ACCESS_LOG_RETENTION_DAYS = 730;
+
@Resource(name = "personalDataAccessLogSearchService")
private PersonalDataAccessLogSearchService personalDataAccessLogSearchService;
@@ -70,6 +86,10 @@ public class PersonalDataAccessLogController {
@Resource(name = "accessLogDownloadJobService")
private AccessLogDownloadJobService accessLogDownloadJobService;
+ /** 감사 표시용 이상징후 탐지(#35). 목록 렌더 시 검색기간 기준으로 대조한다. */
+ @Resource(name = "abnormalAccessDetectionService")
+ private AbnormalAccessDetectionService abnormalAccessDetectionService;
+
/**
* 개인정보 접속기록 조회 화면(서버 렌더링).
* 뷰: /WEB-INF/jsp/site/mnt/sec/accessLog/list.jsp
@@ -77,6 +97,27 @@ public class PersonalDataAccessLogController {
@RequestMapping(value = "/mnt/sec/accessLog/listView.do")
public String listView(@ModelAttribute("personalDataAccessLogSearchVO") PersonalDataAccessLogSearchVO searchVO,
ModelMap model) throws Exception {
+ return renderList(searchVO, model, false);
+ }
+
+ /**
+ * 개인정보 처리기록 조회 화면 — 제안서 시안 좌측 (2) 메뉴.
+ *
+ *
(1)(접속기록 조회)이 접속·조회를 포함한 전체 기록이라면, 이 화면은 조회(S)를 빼고
+ * 등록·수정·삭제만 남긴다. 정보주체 소명이나 감사 대응에서 "누가 이 사람의 개인정보를
+ * 바꿨는가"를 먼저 보게 되는 쪽이라 별도 진입점을 둔다. 화면(JSP)과 검색조건은 (1)과 공유하고
+ * {@code excludeSelect} 조건 하나만 다르다 — 같은 표를 두 벌 관리하지 않기 위해서다.
+ */
+ @RequestMapping(value = "/mnt/sec/processLog/listView.do")
+ public String processListView(@ModelAttribute("personalDataAccessLogSearchVO") PersonalDataAccessLogSearchVO searchVO,
+ ModelMap model) throws Exception {
+ searchVO.setExcludeSelect(true);
+ return renderList(searchVO, model, true);
+ }
+
+ /** (1)·(2) 공통 렌더링. {@code processOnly} 는 화면 제목·안내 문구 분기에만 쓴다. */
+ private String renderList(PersonalDataAccessLogSearchVO searchVO, ModelMap model, boolean processOnly)
+ throws Exception {
if (handlerUser() == null) {
return LOGIN_REDIRECT;
}
@@ -97,9 +138,102 @@ public class PersonalDataAccessLogController {
model.addAttribute("dmlSummary", selectDmlSummary(searchVO));
// 내 다운로드 작업 목록(비동기) — 상태·진행률 확인 및 내려받기
model.addAttribute("dlJobList", selectMyDownloadJobs());
+ model.addAttribute("processOnly", Boolean.valueOf(processOnly));
+ model.addAttribute("retentionDays", Integer.valueOf(ACCESS_LOG_RETENTION_DAYS));
+ // 시안의 "감사" 칸 — 목록 각 행에 이상/점검 표시를 채우고 기간 전체 탐지 요약을 함께 싣는다
+ markAudit(list, searchVO, model);
return "site/mnt/sec/accessLog/list";
}
+ /**
+ * 목록 각 행에 감사 표시를 채우고, 검색기간 전체의 탐지 요약을 모델에 싣는다.
+ *
+ * 판정 규칙(제안서 시안의 표기와 동일):
+ *
+ * - 이상(A) — 처리결과가 실패·차단(F). 비인가 접근 시도가 여기 걸린다.
+ * - 점검(W) — 처리는 정상이었으나 이상징후 대상인 경우. 대량조회·계정공유의심·
+ * 인증실패반복 대상자이거나, 업무시간 밖에 처리된 기록이다.
+ *
+ *
+ * 기간을 반드시 좁힌다. 탐지는 5개의 집계 질의라 기간이 열려 있으면 730일치를 훑는다.
+ * 검색조건에 기간이 없으면 최근 {@link #AUDIT_DEFAULT_DAYS}일로 잘라 계산한다 — 목록 한 장을
+ * 여는 비용이 감사 표시 때문에 커지면 안 된다(PSR-001).
+ *
+ * 실패해도 목록은 보여준다. 감사 표시는 부가 정보이므로, 탐지가 실패하면 표시 없이
+ * 넘어간다(목록 자체가 막히면 본래 목적인 접속기록 조회가 불가능해진다).
+ */
+ private void markAudit(List list, PersonalDataAccessLogSearchVO searchVO,
+ ModelMap model) {
+ if (list == null || list.isEmpty()) {
+ return;
+ }
+ try {
+ SimpleDateFormat dtFmt = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
+ String from = searchVO.getFromDtParam();
+ String to = searchVO.getToDtParam();
+ if (from == null || to == null) {
+ Calendar cal = Calendar.getInstance();
+ String computedTo = dtFmt.format(cal.getTime());
+ cal.add(Calendar.DAY_OF_MONTH, -AUDIT_DEFAULT_DAYS);
+ String computedFrom = dtFmt.format(cal.getTime());
+ from = (from == null) ? computedFrom : from;
+ to = (to == null) ? computedTo : to;
+ }
+
+ List results = abnormalAccessDetectionService.detect(from, to);
+
+ // 대상자형 유형(대량조회·계정공유·인증실패반복)의 접속자ID 집합 = 점검 대상
+ Set watchIds = new HashSet();
+ int detected = 0;
+ for (int i = 0; i < results.size(); i++) {
+ AbnormalAccessResultVO r = results.get(i);
+ detected += r.getCount();
+ List