From 6a64d35a949f4067af764655c042b68f22731f43 Mon Sep 17 00:00:00 2001 From: Rinjae Date: Mon, 27 Jul 2026 18:20:49 +0900 Subject: [PATCH] =?UTF-8?q?=EB=A1=9C=EA=B7=B8=EC=9D=B8=20=EB=B0=8F=20?= =?UTF-8?q?=EC=9D=B8=EC=A6=9D=20=EA=B4=80=EB=A0=A8=20=EC=97=94=ED=8B=B0?= =?UTF-8?q?=ED=8B=B0,=20=EB=A0=88=ED=8F=AC=EC=A7=80=ED=86=A0=EB=A6=AC=20?= =?UTF-8?q?=EA=B0=9C=EC=84=A0:=20-=20=EC=8B=A4=ED=8C=A8=20=EC=82=AC?= =?UTF-8?q?=EC=9C=A0/=EB=A1=9C=EA=B7=B8=EC=9D=B8=20=EC=9C=A0=ED=98=95=20?= =?UTF-8?q?=EC=BD=94=EB=93=9C=20=ED=95=84=EB=93=9C=20=EC=B6=94=EA=B0=80(Us?= =?UTF-8?q?erLog)=20-=20=EB=A7=8C=EB=A3=8C=20=EC=9D=B8=EC=A6=9D=EB=B2=88?= =?UTF-8?q?=ED=98=B8=20=EC=9D=BC=EA=B4=84=20=EC=82=AD=EC=A0=9C=20=EB=A0=88?= =?UTF-8?q?=ED=8F=AC=EC=A7=80=ED=86=A0=EB=A6=AC=20=EB=A9=94=EC=84=9C?= =?UTF-8?q?=EB=93=9C=20=EC=B6=94=EA=B0=80=20-=20UUID=20v7=20=EC=A0=81?= =?UTF-8?q?=EC=9A=A9(TwoFactorAuth)=20-=20PK=20=EC=A0=95=EB=A0=AC=20?= =?UTF-8?q?=ED=9A=A8=EC=9C=A8=20=EA=B0=9C=EC=84=A0=20-=20=EC=9D=B8?= =?UTF-8?q?=EC=A6=9D=EB=B2=88=ED=98=B8=20=EC=8B=A4=ED=8C=A8=20=EC=82=AC?= =?UTF-8?q?=EC=9C=A0=20=EC=BD=94=EB=93=9C=20=EC=B6=94=EA=B0=80(AuthNumberE?= =?UTF-8?q?xception)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../repository/AppRequestRepository.java | 2 ++ .../portal/portaluser/entity/PortalUser.java | 6 +++- .../portaluser/entity/TwoFactorAuth.java | 3 +- .../repository/TwoFactorAuthRepository.java | 10 ++++++ .../service/AuthNumberException.java | 32 ++++++++++++++++++- .../apim/portal/user/entity/UserLog.java | 8 +++++ 6 files changed, 58 insertions(+), 3 deletions(-) diff --git a/src/main/java/com/eactive/apim/portal/apprequest/repository/AppRequestRepository.java b/src/main/java/com/eactive/apim/portal/apprequest/repository/AppRequestRepository.java index 5e4bb04..dfb2167 100644 --- a/src/main/java/com/eactive/apim/portal/apprequest/repository/AppRequestRepository.java +++ b/src/main/java/com/eactive/apim/portal/apprequest/repository/AppRequestRepository.java @@ -23,6 +23,8 @@ public interface AppRequestRepository extends BaseRepository List findAllByOrgAndTypeIsInAndApproval_ApprovalStatusIn(PortalOrg org, List types, List approvalStates); + List findAllByOrgAndTypeIsInAndApprovalIsNull(PortalOrg org, List types); + int countAllByOrgAndTypeIsInAndApproval_ApprovalStatusIn(PortalOrg org, List types, List approvalStates); Optional findByIdAndOrg(String id, PortalOrg org); diff --git a/src/main/java/com/eactive/apim/portal/portaluser/entity/PortalUser.java b/src/main/java/com/eactive/apim/portal/portaluser/entity/PortalUser.java index c8df0a9..e01593f 100644 --- a/src/main/java/com/eactive/apim/portal/portaluser/entity/PortalUser.java +++ b/src/main/java/com/eactive/apim/portal/portaluser/entity/PortalUser.java @@ -88,7 +88,11 @@ public class PortalUser extends Auditable implements Serializable, com.eactive.e @Comment("로그인실패횟수") private Integer loginFailureCount; - @Column(name = "password_change_date") + // PASSWORD_CHANGE_DATE 컬럼은 VARCHAR2(14) yyyyMMddHHmmss 문자열이다. + // LocalDateTime 을 그대로 바인딩하면 27자로 변환되어 ORA-12899(값 초과)가 발생하므로 + // 14자 문자열로 변환하는 컨버터를 적용한다. + @Column(name = "password_change_date", length = 14) + @Convert(converter = com.eactive.eai.data.converter.LocalDateTimeToStringConverter14.class) @Comment("비밀번호변경일시") private LocalDateTime passwordChangeDate; diff --git a/src/main/java/com/eactive/apim/portal/portaluser/entity/TwoFactorAuth.java b/src/main/java/com/eactive/apim/portal/portaluser/entity/TwoFactorAuth.java index a838918..632fac2 100644 --- a/src/main/java/com/eactive/apim/portal/portaluser/entity/TwoFactorAuth.java +++ b/src/main/java/com/eactive/apim/portal/portaluser/entity/TwoFactorAuth.java @@ -20,7 +20,8 @@ public class TwoFactorAuth { @Id @Column(name = "id", length = 36, nullable = false) - @GenericGenerator(name = "uuid-gen", strategy = "uuid2") + // UUID v7(타임스탬프 기반, 시간순 정렬) — PK 인덱스 삽입 지역성 개선. 기존 v4 행과 컬럼 호환. + @GenericGenerator(name = "uuid-gen", strategy = "com.eactive.apim.portal.jpa.UuidV7Generator") @GeneratedValue(generator = "uuid-gen", strategy = GenerationType.IDENTITY) private String id; diff --git a/src/main/java/com/eactive/apim/portal/portaluser/repository/TwoFactorAuthRepository.java b/src/main/java/com/eactive/apim/portal/portaluser/repository/TwoFactorAuthRepository.java index ef636a0..a774cb9 100644 --- a/src/main/java/com/eactive/apim/portal/portaluser/repository/TwoFactorAuthRepository.java +++ b/src/main/java/com/eactive/apim/portal/portaluser/repository/TwoFactorAuthRepository.java @@ -2,7 +2,9 @@ package com.eactive.apim.portal.portaluser.repository; import com.eactive.apim.portal.portaluser.entity.TwoFactorAuth; import org.springframework.data.jpa.repository.JpaRepository; +import org.springframework.data.jpa.repository.Modifying; +import java.time.LocalDateTime; import java.util.Optional; public interface TwoFactorAuthRepository extends JpaRepository { @@ -12,6 +14,14 @@ public interface TwoFactorAuthRepository extends JpaRepository findByRecipient(String recipientKey); diff --git a/src/main/java/com/eactive/apim/portal/portaluser/service/AuthNumberException.java b/src/main/java/com/eactive/apim/portal/portaluser/service/AuthNumberException.java index a6dac78..17b7dc2 100644 --- a/src/main/java/com/eactive/apim/portal/portaluser/service/AuthNumberException.java +++ b/src/main/java/com/eactive/apim/portal/portaluser/service/AuthNumberException.java @@ -1,7 +1,37 @@ package com.eactive.apim.portal.portaluser.service; -public class AuthNumberException extends RuntimeException{ +/** + * 인증번호(2FA/이메일/SMS) 검증·발송 실패 예외. + * + *

기존에는 메시지 문자열만 있었으나, 감사 로그에서 실패 사유(만료/불일치/미존재)를 + * 코드로 구분해야 하므로 {@link Reason} 을 추가했다. 기존 메시지 전용 생성자는 유지되며 + * 이 경우 {@code reason == null} 이다(하위호환).

+ */ +public class AuthNumberException extends RuntimeException { + + /** 실패 사유 분류 */ + public enum Reason { + /** 저장된 인증번호가 없음(미발송/이미 소비/스케줄러 정리) */ + NOT_FOUND, + /** 유효시간 초과 */ + EXPIRED, + /** 인증번호 불일치 */ + MISMATCH + } + + private final Reason reason; + public AuthNumberException(String message) { super(message); + this.reason = null; + } + + public AuthNumberException(String message, Reason reason) { + super(message); + this.reason = reason; + } + + public Reason getReason() { + return reason; } } diff --git a/src/main/java/com/eactive/apim/portal/user/entity/UserLog.java b/src/main/java/com/eactive/apim/portal/user/entity/UserLog.java index c19a33d..62d0bac 100644 --- a/src/main/java/com/eactive/apim/portal/user/entity/UserLog.java +++ b/src/main/java/com/eactive/apim/portal/user/entity/UserLog.java @@ -45,4 +45,12 @@ public class UserLog { @Column(name = "success", nullable = false) private boolean success; + /** 실패 사유 코드 (LoginFailureReason). 성공 행은 null */ + @Column(name = "failure_reason", length = 64) + private String failureReason; + + /** 로그인 유형 코드 (LoginType: NORMAL/TWO_FACTOR/SIGNUP_AUTO). 실패 행·과거 행은 null */ + @Column(name = "login_type", length = 32) + private String loginType; + }