diff --git a/src/main/java/egovframework/com/cmm/privacy/crypto/MberCryptoAdminController.java b/src/main/java/egovframework/com/cmm/privacy/crypto/MberCryptoAdminController.java index 17cbb38..5bb72a2 100644 --- a/src/main/java/egovframework/com/cmm/privacy/crypto/MberCryptoAdminController.java +++ b/src/main/java/egovframework/com/cmm/privacy/crypto/MberCryptoAdminController.java @@ -180,7 +180,16 @@ public class MberCryptoAdminController { return reauthRedirect(viewUrl(mberId, reason)); } - MberCryptoVO vo = mberCryptoAdminService.selectMber(mberId); + MberCryptoVO vo; + try { + vo = mberCryptoAdminService.selectMber(mberId); + } catch (IllegalStateException e) { + // 복호화 실패 — 상세는 값이 정확해야 하므로 열지 않는다. + // 다만 스택트레이스를 그대로 띄우지 않고 원인을 문장으로 알린다. + return listRedirect("이 회원의 이름을 복호화할 수 없습니다." + + " 저장된 암호문이 현재 암호화 키와 맞지 않습니다(키 교체 또는 암호문 손상)." + + " 대상 회원ID: " + mberId); + } if (vo == null) { return listRedirect("대상 회원을 찾을 수 없습니다."); } @@ -344,7 +353,10 @@ public class MberCryptoAdminController { } for (int i = 0; i < list.size(); i++) { MberCryptoVO vo = list.get(i); - vo.setMberNm(MaskingUtil.maskName(vo.getMberNm())); + // 복호화 실패 표식은 이름이 아니므로 마스킹하지 않는다(마스킹하면 표식이 뭉개진다). + if (!vo.isDecryptFailed()) { + vo.setMberNm(MaskingUtil.maskName(vo.getMberNm())); + } vo.setMoblphonNo(MaskingUtil.maskPhone(vo.getMoblphonNo())); vo.setMberEmailAdres(MaskingUtil.maskEmail(vo.getMberEmailAdres())); // 암호문·해시는 화면으로 나갈 이유가 없다. 목록 모델에서 지운다. diff --git a/src/main/java/egovframework/com/cmm/privacy/crypto/MberCryptoAdminDAO.java b/src/main/java/egovframework/com/cmm/privacy/crypto/MberCryptoAdminDAO.java index 93b289a..e307d6d 100644 --- a/src/main/java/egovframework/com/cmm/privacy/crypto/MberCryptoAdminDAO.java +++ b/src/main/java/egovframework/com/cmm/privacy/crypto/MberCryptoAdminDAO.java @@ -35,11 +35,33 @@ import egovframework.com.cmm.service.impl.EgovComAbstractDAO; @Repository("mberCryptoAdminDAO") public class MberCryptoAdminDAO extends EgovComAbstractDAO { - /** 회원 목록(검색·페이징). 반환 직전 이름을 복호화해 {@code mberNm} 에 되돌려 쓴다. */ + /** 목록에서 복호화가 실패한 행의 이름 자리에 대신 넣는 표식. */ + public static final String DECRYPT_FAIL_MARK = "(복호화 실패)"; + + /** + * 회원 목록(검색·페이징). 반환 직전 이름을 복호화해 {@code mberNm} 에 되돌려 쓴다. + * + *
행 단위로 실패를 견딘다. 공용 {@link MemberNmDecryptSupport} 는 실패를 숨기지 + * 않도록 예외를 던지는데, 목록에서 그대로 쓰면 한 행이 깨진 순간 페이지 전체가 죽는다. + * 키를 교체했거나 암호문 한 건이 손상된 상황에서 관리자 화면마저 열리지 않으면 원인을 + * 확인할 방법이 없어진다. 그래서 여기서만 행 단위로 감싸 실패한 행에 표식을 남기고 + * 나머지는 정상 표시한다 — 실패를 감추는 게 아니라 어느 행인지 드러내는 처리다. + * 단건 조회({@link #selectMber})는 값이 정확해야 하므로 종전대로 예외를 그대로 올린다.
+ */ @SuppressWarnings("unchecked") public List왜 이 표식이 필요한가: 목록은 페이지의 모든 행을 복호화하는데, 키를 교체했거나 + * 암호문 한 건이 손상되면 그 한 행 때문에 화면 전체가 죽는다. 관리자 화면이 통째로 열리지 + * 않으면 정작 문제를 확인할 방법도 사라진다. 그래서 목록에서는 실패한 행만 표시해 두고 + * 나머지는 정상적으로 보여준다 — 실패를 숨기는 것이 아니라 행 단위로 드러내는 것이다. + * (상세 조회는 값이 정확해야 하므로 지금처럼 실패를 그대로 알린다)
+ */ + private boolean decryptFailed; + public String getMberId() { return mberId; } public void setMberId(String mberId) { this.mberId = mberId; } @@ -107,4 +118,7 @@ public class MberCryptoVO implements Serializable { public String getPassword() { return password; } public void setPassword(String password) { this.password = password; } + + public boolean isDecryptFailed() { return decryptFailed; } + public void setDecryptFailed(boolean decryptFailed) { this.decryptFailed = decryptFailed; } } diff --git a/src/main/webapp/WEB-INF/jsp/site/mnt/sec/mberCrypto/list.jsp b/src/main/webapp/WEB-INF/jsp/site/mnt/sec/mberCrypto/list.jsp index 4bc5c5f..15bf413 100644 --- a/src/main/webapp/WEB-INF/jsp/site/mnt/sec/mberCrypto/list.jsp +++ b/src/main/webapp/WEB-INF/jsp/site/mnt/sec/mberCrypto/list.jsp @@ -58,6 +58,9 @@ function fn_history() { .msg { margin:6px 0 10px; padding:8px 10px; background:#fff8e6; border:1px solid #e6d5a8; font-size:13px; color:#7a5b00; } .btn_area { margin:8px 0; text-align:right; } .nodata { color:#aaa; } +/* 복호화 실패 행 — 숨기지 않고 어느 행인지 드러낸다(화면은 계속 열린다) */ +.badge.decfail { background:#8a3b3b; } +tr.decfail td { background:#fdf3f3; } .reauth { float:left; font-size:12px; color:#556; padding-top:4px; } .reauth strong { color:#4a7ebb; } #search_wrap .srow { white-space:nowrap; } @@ -112,7 +115,8 @@ function fn_history() { · 이 목록의 이름·휴대폰·이메일은 항상 마스킹되어 표시됩니다. 평문은 사유를 입력하고 상세를 연 한 건에서만 보입니다.