diff --git a/src/main/java/egovframework/com/cmm/privacy/account/AccountMngController.java b/src/main/java/egovframework/com/cmm/privacy/account/AccountMngController.java index 5b43ad2..f558e36 100644 --- a/src/main/java/egovframework/com/cmm/privacy/account/AccountMngController.java +++ b/src/main/java/egovframework/com/cmm/privacy/account/AccountMngController.java @@ -3,21 +3,27 @@ package egovframework.com.cmm.privacy.account; import java.util.List; import javax.annotation.Resource; +import javax.servlet.http.HttpServletRequest; import org.springframework.stereotype.Controller; import org.springframework.ui.ModelMap; import org.springframework.web.bind.annotation.ModelAttribute; import org.springframework.web.bind.annotation.RequestMapping; +import egovframework.com.cmm.LoginVO; +import egovframework.com.cmm.util.EgovUserDetailsHelper; +import egovframework.rte.ptl.mvc.tags.ui.pagination.PaginationInfo; + /** * 계정 관리 정책·현황 컨트롤러 (SFR-002). * - *
#13 계정 관리 정책 조회/수정, #11 장기 미사용 계정 현황 조회 백엔드 엔드포인트. - * 화면(JSP)은 후속(#11/#13 프론트) — 여기서는 jsonView 반환.
+ *#13 계정 관리 정책 조회/수정, #11 장기 미사용 계정 현황 조회 백엔드 엔드포인트.
*/ @Controller public class AccountMngController { + private static final String LOGIN_REDIRECT = "redirect:/uat/uia/egovLoginUsr.do"; + @Resource(name = "accountMngPolicyService") private AccountMngPolicyService accountMngPolicyService; @@ -27,25 +33,188 @@ public class AccountMngController { /** #13 정책 조회. */ @RequestMapping(value = "/mnt/privacy/accountPolicy.do") public String selectPolicy(ModelMap model) throws Exception { + if (handlerUser() == null) { + return denyJson(model); + } model.addAttribute("policy", accountMngPolicyService.selectPolicy()); return "jsonView"; } /** #13 정책 수정. */ @RequestMapping(value = "/mnt/privacy/accountPolicyUpdate.do") - public String updatePolicy(@ModelAttribute("policy") AccountMngPolicyVO policy, ModelMap model) throws Exception { + public String updatePolicy(@ModelAttribute("policy") AccountMngPolicyVO policy, + HttpServletRequest request, ModelMap model) throws Exception { + LoginVO loginVO = handlerUser(); + if (loginVO == null) { + return denyJson(model); + } + if (!isSameOrigin(request)) { + model.addAttribute("result", "fail"); + model.addAttribute("message", "잘못된 요청 경로입니다."); + return "jsonView"; + } + + String invalid = validatePolicy(policy); + if (invalid != null) { + model.addAttribute("result", "fail"); + model.addAttribute("message", invalid); + return "jsonView"; + } + + policy.setUpdId(loginVO.getId()); accountMngPolicyService.updatePolicy(policy); model.addAttribute("result", "success"); return "jsonView"; } - /** #11 장기 미사용 계정 현황 목록(페이징). */ + /** + * #13 계정 관리 정책 설정 화면(서버 렌더링). + * 뷰: /WEB-INF/jsp/site/mnt/sec/accountPolicy/policy.jsp + */ + @RequestMapping(value = "/mnt/sec/accountPolicy/policyView.do") + public String accountPolicyView(ModelMap model) throws Exception { + if (handlerUser() == null) { + return LOGIN_REDIRECT; + } + model.addAttribute("policy", accountMngPolicyService.selectPolicy()); + return "site/mnt/sec/accountPolicy/policy"; + } + + /** 화면 우회 입력(직접 POST)에 대비한 서버측 범위 검증. 위반 시 사용자 메시지 반환. */ + private String validatePolicy(AccountMngPolicyVO p) { + if (p.getDormantDay() < 1 || p.getDormantDay() > 3650) { + return "휴면 전환 기준일수는 1~3650일 범위로 입력하세요."; + } + if (p.getAlertD1() < 0 || p.getAlertD2() < 0 || p.getAlertD3() < 0 + || p.getAlertD1() >= p.getDormantDay() || p.getAlertD2() >= p.getDormantDay() || p.getAlertD3() >= p.getDormantDay()) { + return "사전알림 시점은 0 이상이며 휴면 기준일수보다 작아야 합니다."; + } + if (p.getAlertD1() <= p.getAlertD2() || p.getAlertD2() <= p.getAlertD3()) { + return "사전알림 시점은 1차 > 2차 > 3차 순서로 입력하세요."; + } + if (p.getDeletePreserveDay() < 1 || p.getDeletePreserveDay() > 3650) { + return "삭제 보존기간은 1~3650일 범위로 입력하세요."; + } + if (!"DAILY".equals(p.getBatchCycle()) && !"WEEKLY".equals(p.getBatchCycle()) && !"MONTHLY".equals(p.getBatchCycle())) { + return "배치 주기가 올바르지 않습니다."; + } + // [개인정보/#22-1] 인증 실패 이력 보관기간. 화면 우회 입력 대비 서버측 범위 검증. + if (p.getLoginHistKeepDay() < 1 || p.getLoginHistKeepDay() > 3650) { + return "인증 실패 이력 보관기간은 1~3650일 범위로 입력하세요."; + } + // #12 파기 배치 사용여부 — 체크박스 미전송 시 null 이 오므로 'N' 으로 보정 + if (p.getPurgeUseYn() == null || p.getPurgeUseYn().trim().length() == 0) { + p.setPurgeUseYn("N"); + } else if (!"Y".equals(p.getPurgeUseYn()) && !"N".equals(p.getPurgeUseYn())) { + return "계정 파기 사용여부 값이 올바르지 않습니다."; + } + String email = p.getAdminEmail(); + if (email != null && email.trim().length() > 0) { + // ADMIN_EMAIL VARCHAR(200) — 길이 초과 시 DB 오류 대신 사용자 메시지로 반환 + if (email.length() > 200 || !email.matches("^[\\w.%+-]+@[\\w.-]+\\.[A-Za-z]{2,}$")) { + return "관리자 수신 이메일 형식이 올바르지 않습니다."; + } + } + return null; + } + + /** + * 취급자 영역(/mnt) 인증·구분 판정. 인터셉터 3단(인증판정 비활성 / SecondFactor·Authority 는 + * 미인증 통과 fail-open)만으로는 이 컨트롤러가 다루는 계정정책·취급자 목록이 무인증으로 + * 노출되므로, 화면·엔드포인트 단에서 직접 판정한다. + * + * @return 인증된 업무사용자(USR) LoginVO, 그 외에는 null + */ + private LoginVO handlerUser() { + if (!Boolean.TRUE.equals(EgovUserDetailsHelper.isAuthenticated())) { + return null; + } + Object o = EgovUserDetailsHelper.getAuthenticatedUser(); + if (!(o instanceof LoginVO)) { + return null; + } + LoginVO loginVO = (LoginVO) o; + return "USR".equals(loginVO.getUserSe()) ? loginVO : null; + } + + /** + * CSRF 부분 완화. Spring Security 2.0.4 에는 CSRF 토큰 기능이 없으므로, + * Origin/Referer 가 전달된 경우에 한해 요청 호스트와 동일한지 확인한다 + * (헤더 미전송 환경은 과잉차단을 피해 통과 — 토큰 방식 전환 전까지의 임시 방어). + */ + private boolean isSameOrigin(HttpServletRequest request) { + String source = request.getHeader("Origin"); + if (source == null) { + source = request.getHeader("Referer"); + } + if (source == null) { + return true; + } + String sourceHost = hostOf(source); + String requestHost = hostOf(request.getHeader("Host")); + return sourceHost != null && sourceHost.equalsIgnoreCase(requestHost); + } + + /** "https://host:8080/path" · "host:8080" 등에서 호스트명만 추출. */ + private String hostOf(String value) { + if (value == null) { + return null; + } + String host = value; + int scheme = host.indexOf("://"); + if (scheme >= 0) { + host = host.substring(scheme + 3); + } + int slash = host.indexOf('/'); + if (slash >= 0) { + host = host.substring(0, slash); + } + int colon = host.indexOf(':'); + if (colon >= 0) { + host = host.substring(0, colon); + } + return host.length() == 0 ? null : host; + } + + private String denyJson(ModelMap model) { + model.addAttribute("result", "fail"); + model.addAttribute("message", "접근 권한이 없습니다. 관리자 계정으로 로그인 후 이용하세요."); + return "jsonView"; + } + + /** #11 장기 미사용 계정 현황 목록(페이징, AJAX/JSON). */ @RequestMapping(value = "/mnt/privacy/dormantStatusList.do") public String selectDormantStatusList(@ModelAttribute("searchVO") DormantAccountStatusVO searchVO, ModelMap model) throws Exception { + if (handlerUser() == null) { + return denyJson(model); + } List#22 로 살아난 {@code LOGIN_HISTORY} 적재분을 관리자가 계정·IP·기간으로 + * 조회한다. 화면: {@code /WEB-INF/jsp/site/mnt/sec/loginFailHistory/list.jsp}
+ * + *접근통제: 인터셉터 3단 중 ①인증판정이 비활성이고 ②③은 fail-open 이라 + * /mnt 하위라도 무인증 노출이 가능하므로, {@link #handlerUser()} 로 화면·엔드포인트 + * 단에서 직접 판정한다(AccountMngController 와 동일 방식).
+ * + *+ * << 개정이력(Modification Information) >> + * 수정일 수정자 수정내용 + * ---------- ------ --------------------------- + * 2026.08.05 URP 최초 생성 (개인정보보호 SER-001 #22-1) + *+ */ +@Controller +public class LoginFailHistoryController { + + private static final String LOGIN_REDIRECT = "redirect:/uat/uia/egovLoginUsr.do"; + + @Resource(name = "loginFailHistoryService") + private LoginFailHistoryService loginFailHistoryService; + + /** + * 인증 실패 이력 조회 화면(서버 렌더링). + * 뷰: /WEB-INF/jsp/site/mnt/sec/loginFailHistory/list.jsp + */ + @RequestMapping(value = "/mnt/sec/loginFailHistory/listView.do") + public String listView(@ModelAttribute("loginFailHistoryVO") LoginFailHistoryVO searchVO, + ModelMap model) throws Exception { + if (handlerUser() == null) { + return LOGIN_REDIRECT; + } + + List
{@code LOGIN_HISTORY} 조회(화면) + 보관기간 경과분 파기(배치). + * 적재(insert)·리셋(update)은 로그인 흐름 소유인 {@code loginDAO} 가 담당하므로 + * 여기서는 정의하지 않는다(이중 경로 방지).
+ * + *statement 는 {@code PrivacyLoginFailHistory_SQL_Mysql.xml} + * (namespace {@code LoginFailHistoryDAO}), sql-map-config 등록.
+ * + *+ * << 개정이력(Modification Information) >> + * 수정일 수정자 수정내용 + * ---------- ------ --------------------------- + * 2026.08.05 URP 최초 생성 (개인정보보호 SER-001 #22-1) + *+ */ +@Repository("loginFailHistoryDAO") +public class LoginFailHistoryDAO extends EgovComAbstractDAO { + + /** 인증 실패 이력 목록(검색·페이징) */ + @SuppressWarnings("unchecked") + public List
+ * << 개정이력(Modification Information) >> + * 수정일 수정자 수정내용 + * ---------- ------ --------------------------- + * 2026.08.05 URP 최초 생성 (개인정보보호 SER-001 #22-1) + *+ */ +public interface LoginFailHistoryService { + + /** + * 인증 실패 이력 목록 조회. 호출 후 {@code searchVO.totalRecordCount} 가 세팅된다. + * + * @param searchVO 검색조건·페이징 + * @return 실패 이력 목록 + * @throws Exception 조회 오류 + */ + List
+ * << 개정이력(Modification Information) >> + * 수정일 수정자 수정내용 + * ---------- ------ --------------------------- + * 2026.08.05 URP 최초 생성 (개인정보보호 SER-001 #22-1) + *+ */ +@Service("loginFailHistoryService") +public class LoginFailHistoryServiceImpl implements LoginFailHistoryService { + + /** 1회 파기 처리 상한 — 배치가 오래 멈췄다 재가동되는 날 백로그 폭주 방지. */ + private static final int PURGE_LIMIT = 5000; + + /** 정책값 미설정 시 보관기간 기본값(일). */ + private static final int DEFAULT_KEEP_DAY = 730; + + @Resource(name = "loginFailHistoryDAO") + private LoginFailHistoryDAO loginFailHistoryDAO; + + @Override + public List
{@code LOGIN_HISTORY} 조회 결과 + 검색조건 + 페이징. + * #22(실패이력 활성 경로 이설)로 적재가 살아난 뒤, #22-1 에서 접속 IP(CONECT_IP)와 + * 보존(RESET_YN 소프트 리셋)을 추가하면서 조회 화면 대상이 되었다.
+ * + *+ * << 개정이력(Modification Information) >> + * 수정일 수정자 수정내용 + * ---------- ------ --------------------------- + * 2026.08.05 URP 최초 생성 (개인정보보호 SER-001 #22-1) + *+ */ +public class LoginFailHistoryVO implements Serializable { + + private static final long serialVersionUID = 1L; + + // ---- 조회 결과 ---- + /** 시도 계정ID(LOGIN_HISTORY.ID) */ + private String id; + /** 사용자구분(GNR:개인 GRO:그룹 ENT:활동처 USR:업무사용자) */ + private String gubun; + /** 실패 일시(yyyy-MM-dd HH:mm:ss) */ + private String createDate; + /** 접속 IP(#22-1) */ + private String conectIp; + /** 잠금카운터 리셋여부 Y:이후 로그인 성공으로 리셋 N:유효 */ + private String resetYn; + /** 성명(회원 테이블 조인, 없으면 null) */ + private String userNm; + + // ---- 검색조건 ---- + /** 계정ID(부분일치) */ + private String searchId; + /** 사용자구분(완전일치) */ + private String searchGubun; + /** 접속 IP(부분일치) */ + private String searchIp; + /** 조회 시작일(yyyy-MM-dd) */ + private String searchBgnDe; + /** 조회 종료일(yyyy-MM-dd) */ + private String searchEndDe; + /** 리셋여부 필터(Y/N, 공백=전체) */ + private String searchResetYn; + + // ---- 페이징 ---- + private int pageIndex = 1; + private int recordCountPerPage = 10; + private int firstIndex = 0; + private int totalRecordCount = 0; + + public String getId() { return id; } + public void setId(String id) { this.id = id; } + public String getGubun() { return gubun; } + public void setGubun(String gubun) { this.gubun = gubun; } + public String getCreateDate() { return createDate; } + public void setCreateDate(String createDate) { this.createDate = createDate; } + public String getConectIp() { return conectIp; } + public void setConectIp(String conectIp) { this.conectIp = conectIp; } + public String getResetYn() { return resetYn; } + public void setResetYn(String resetYn) { this.resetYn = resetYn; } + public String getUserNm() { return userNm; } + public void setUserNm(String userNm) { this.userNm = userNm; } + + public String getSearchId() { return searchId; } + public void setSearchId(String searchId) { this.searchId = searchId; } + public String getSearchGubun() { return searchGubun; } + public void setSearchGubun(String searchGubun) { this.searchGubun = searchGubun; } + public String getSearchIp() { return searchIp; } + public void setSearchIp(String searchIp) { this.searchIp = searchIp; } + public String getSearchBgnDe() { return searchBgnDe; } + public void setSearchBgnDe(String searchBgnDe) { this.searchBgnDe = searchBgnDe; } + public String getSearchEndDe() { return searchEndDe; } + public void setSearchEndDe(String searchEndDe) { this.searchEndDe = searchEndDe; } + public String getSearchResetYn() { return searchResetYn; } + public void setSearchResetYn(String searchResetYn) { this.searchResetYn = searchResetYn; } + + public int getPageIndex() { return pageIndex; } + public void setPageIndex(int pageIndex) { this.pageIndex = pageIndex; } + public int getRecordCountPerPage() { return recordCountPerPage; } + public void setRecordCountPerPage(int recordCountPerPage) { this.recordCountPerPage = recordCountPerPage; } + public int getFirstIndex() { return firstIndex; } + public void setFirstIndex(int firstIndex) { this.firstIndex = firstIndex; } + public int getTotalRecordCount() { return totalRecordCount; } + public void setTotalRecordCount(int totalRecordCount) { this.totalRecordCount = totalRecordCount; } +} diff --git a/src/main/java/egovframework/com/uat/uia/service/impl/LoginDAO.java b/src/main/java/egovframework/com/uat/uia/service/impl/LoginDAO.java index ad2d1da..4d2f9fa 100644 --- a/src/main/java/egovframework/com/uat/uia/service/impl/LoginDAO.java +++ b/src/main/java/egovframework/com/uat/uia/service/impl/LoginDAO.java @@ -144,14 +144,17 @@ public class LoginDAO extends EgovComAbstractDAO { insert("loginDAO.insertFailHistory", vo); } - // KTH. 20190603 로그인 성공 시 당일 실패 이력 정보 delete + // KTH. 20190603 로그인 성공 시 당일 실패 이력 정보 정리 + // [개인정보/#22-1] 실제 동작은 DELETE 가 아니라 RESET_YN='Y' 소프트 리셋. + // 이력은 보관기간(ACCOUNT_MNG_POLICY.LOGIN_HIST_KEEP_DAY)까지 보존하고, + // 잠금 카운트만 리셋한다. 메서드명은 호출부 영향 최소화를 위해 유지. public void deleteLoginSuccess(LoginVO vo) { - delete("loginDAO.deleteLoginSuccess", vo); + update("loginDAO.deleteLoginSuccess", vo); } - - // KTH. 20190607 + + // KTH. 20190607 / [개인정보/#22-1] 동일 — 당일 전체 소프트 리셋 public void deleteLoginSuccessTodayAll(LoginVO vo) { - delete("loginDAO.deleteLoginSuccessAll", vo); + update("loginDAO.deleteLoginSuccessAll", vo); } // 휴먼고객 확인 diff --git a/src/main/java/egovframework/com/uat/uia/web/EgovLoginController.java b/src/main/java/egovframework/com/uat/uia/web/EgovLoginController.java index 3c5e04f..123f96c 100644 --- a/src/main/java/egovframework/com/uat/uia/web/EgovLoginController.java +++ b/src/main/java/egovframework/com/uat/uia/web/EgovLoginController.java @@ -27,6 +27,7 @@ import egovframework.com.cmm.EgovMessageSource; import egovframework.com.cmm.EgovWebUtil; import egovframework.com.cmm.LoginVO; import egovframework.com.cmm.annotation.IncludedInfo; +import egovframework.com.cmm.privacy.auditlog.ClientIpResolver; import egovframework.com.cmm.service.EgovCmmUseService; import egovframework.com.cmm.service.Globals; import egovframework.com.cmm.util.EgovUserDetailsHelper; @@ -221,6 +222,10 @@ public class EgovLoginController { threshold = totVo.getLockCount(); } // 실패 이력 적재(LOGIN_HISTORY) + // [개인정보/#22-1] 접속 IP 기록 — 어디서 시도된 실패인지 추적 가능하게 한다. + // ClientIpResolver(#34): X-Forwarded-For → Proxy-Client-IP → getRemoteAddr, + // 신뢰 프록시 CIDR 검증 포함(Apache+L4 구성에서 getRemoteAddr 만으로는 부정확). + loginVO.setIp(ClientIpResolver.getClientIp(request)); loginService.insertLoginFail(loginVO); // 당일 누적 실패 횟수 currentFail = loginService.selectFailCount(map); diff --git a/src/main/java/kr/or/arko/cmm/batch/BatchScheduler.java b/src/main/java/kr/or/arko/cmm/batch/BatchScheduler.java index 39bfc58..598b25e 100644 --- a/src/main/java/kr/or/arko/cmm/batch/BatchScheduler.java +++ b/src/main/java/kr/or/arko/cmm/batch/BatchScheduler.java @@ -65,6 +65,14 @@ public class BatchScheduler { @Resource(name = "dormantAlertService") private egovframework.com.cmm.privacy.account.DormantAlertService dormantAlertService; + /** 개인정보보호 장기 미사용 계정 파기 서비스 (#12 보존기간 경과 계정 개인정보 파기) */ + @Resource(name = "accountDeleteService") + private egovframework.com.cmm.privacy.account.AccountDeleteService accountDeleteService; + + /** [개인정보/#22-1] 인증 실패 이력 보관기간 파기 */ + @Resource(name = "loginFailHistoryService") + private egovframework.com.cmm.privacy.lock.LoginFailHistoryService loginFailHistoryService; + /** * 테스트 배치 예제 *//* @@ -180,10 +188,9 @@ public class BatchScheduler { * [개인정보보호 #5] 권한 변경 이력(AUTHOR_CHANGE_HISTORY) 파기 배치. * 매월 1일 04:00, 보관기간(1,095일=3년) 경과 이력만 선별 파기. 개별 삭제 API 미제공. * - *
※ 스케줄 활성화 주의: 본 프로젝트는 {@code
※ 2026-07-28 context-common.xml의 {@code
※ 스케줄 활성화 주의: 전역 {@code
※ 전역 {@code
2026-07-30: 대상자 본인 개별 발송을 제거했다(요구사항은 관리자 알림이며, 5만건 규모에서 + * 본인 건별 발송이 휴면 배치 처리량을 지배했다). 상세는 {@code DormantAlertServiceImpl} 주석 참조.
+ * + * (스케줄 활성화 주의는 {@link #purgeAuthorChangeHistoryScheduler()} 참조.) * * @throws Exception 알림 처리 오류 */ @Scheduled(cron = "0 0 6 * * ?") public void dormantPreAlertScheduler() throws Exception { int sent = dormantAlertService.sendDormantPreAlerts(); - log.info("[개인정보/#10] 휴면 사전알림 배치 완료 — " + sent + "건 발송"); + log.info("[개인정보/#10] 휴면 사전알림 배치 완료 — 관리자 요약 " + sent + "통 발송"); + } + + /** + * [개인정보보호 #12] 장기 미사용 계정 개인정보 파기 배치. + * 매일 02:30, 휴면 확정('P') 후 보존기간(ACCOUNT_MNG_POLICY.DELETE_PRESERVE_DAY 정책값)이 + * 경과한 계정의 개인정보를 파기(PII 마스킹 + 상태 'D')하고 ACCOUNT_DELETE_HISTORY 에 이력을 남긴다. + * 휴면 등록 배치(01:00)·사전알림(06:00)과 시각을 분리했다. + * + *실제 파기 수행 여부는 정책 플래그로 제어한다. 배치는 매일 등록되어 돌지만 + * {@code ACCOUNT_MNG_POLICY.PURGE_USE_YN}(기본 'N')이 'Y'가 아니면 서비스가 즉시 0건으로 + * 종료한다. 켜고 끄는 것은 계정 관리 정책 화면(#13)에서 하며 재배포가 필요 없다. + * 1회 처리건수는 SQL에서 200건으로 상한(백로그 일시 폭주 방지).
+ * + * @throws Exception 파기 오류 + */ + @Scheduled(cron = "0 30 2 * * ?") + public void purgeExpiredAccountScheduler() throws Exception { + int purged = accountDeleteService.purgeExpiredAccounts(); + log.info("[개인정보/#12] 장기 미사용 계정 파기 배치 완료 — " + purged + "건"); + } + + /** + * [개인정보보호 #22-1] 인증 실패 이력(LOGIN_HISTORY) 보관기간 파기 배치. + * 매일 03:30, {@code ACCOUNT_MNG_POLICY.LOGIN_HIST_KEEP_DAY}(기본 730일=2년)이 + * 경과한 실패 이력을 삭제한다. 1회 처리 상한 5,000건(백로그 폭주 방지, 잔여분은 다음 회차). + * + *#22 이설 이전에는 로그인 성공 시 당일 이력을 DELETE 해 사실상 보관 개념이 없었다. + * #22-1 에서 성공 시 처리를 소프트 리셋(RESET_YN='Y')으로 바꿔 이력을 보존하게 되면서, + * 무한 적재를 막는 파기 경로로 이 배치가 필요해졌다.
+ * + *계정 파기(#12, 02:30)·접속기록 파티션 파기(#37, 매월 1일 02:00)와 시각을 분리했다. + * (스케줄 활성화 주의는 {@link #purgeAuthorChangeHistoryScheduler()} 참조.)
+ * + * @throws Exception 파기 오류 + */ + @Scheduled(cron = "0 30 3 * * ?") + public void purgeExpiredLoginFailHistoryScheduler() throws Exception { + int purged = loginFailHistoryService.purgeExpiredLoginFailHistory(); + log.info("[개인정보/#22-1] 인증 실패 이력 파기 배치 완료 — " + purged + "건"); } } \ 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 0c2aab6..ac3cd81 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 @@ -141,48 +141,66 @@ + - - -| * | +
+ |
+
|---|---|
| * | +
+ 휴면 예정일 D- |
+
| * | +
+ 휴면 예정일 D- |
+
| * | +
+ 휴면 예정일 D- |
+
| * | +
+ |
+
| * | ++ + '사용' 선택 시 매일 02:30 배치가 위 기준일 경과 휴면계정의 개인정보를 파기합니다(1회 최대 200건). + 파기된 개인정보는 복구할 수 없습니다. + | +
| * | +
+ |
+
| * | ++ + 휴면 판정 배치 실행 주기 + | +
| + |
+ |
+
| 최종 수정 | +
+ |
+