로그인 및 인증 관련 엔티티, 레포지토리 개선:
eapim-portal CI (from elink-portal-common) / build (push) Has been cancelled

- 실패 사유/로그인 유형 코드 필드 추가(UserLog)
- 만료 인증번호 일괄 삭제 레포지토리 메서드 추가
- UUID v7 적용(TwoFactorAuth) - PK 정렬 효율 개선
- 인증번호 실패 사유 코드 추가(AuthNumberException)
This commit is contained in:
Rinjae
2026-07-27 18:20:49 +09:00
parent 72d2150a6e
commit 6a64d35a94
6 changed files with 58 additions and 3 deletions
@@ -23,6 +23,8 @@ public interface AppRequestRepository extends BaseRepository<AppRequest, String>
List<AppRequest> findAllByOrgAndTypeIsInAndApproval_ApprovalStatusIn(PortalOrg org, List<AppRequestType> types, List<ApprovalState> approvalStates);
List<AppRequest> findAllByOrgAndTypeIsInAndApprovalIsNull(PortalOrg org, List<AppRequestType> types);
int countAllByOrgAndTypeIsInAndApproval_ApprovalStatusIn(PortalOrg org, List<AppRequestType> types, List<ApprovalState> approvalStates);
Optional<AppRequest> findByIdAndOrg(String id, PortalOrg org);
@@ -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;
@@ -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;
@@ -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<TwoFactorAuth, String> {
@@ -12,6 +14,14 @@ public interface TwoFactorAuthRepository extends JpaRepository<TwoFactorAuth, St
void deleteAllByRecipientKey(String recipientKey);
/**
* 만료된 인증번호 레코드를 일괄 삭제한다. (정리 스케줄러용)
* expires_on 은 LocalDateTimeToStringConverter14(yyyyMMddHHmmss) 로 저장되며,
* 해당 포맷은 사전식 정렬이 시간순과 일치하므로 문자열 &lt; 비교가 곧 시간 비교다.
*/
@Modifying
int deleteAllByExpiresAtBefore(LocalDateTime threshold);
// 아래 두 메서드는 recipient(암호화 컬럼) 기반이라 신뢰할 수 없음 — 신규 코드에서는 RecipientKey 버전을 사용할 것.
@Deprecated
Optional<TwoFactorAuth> findByRecipient(String recipientKey);
@@ -1,7 +1,37 @@
package com.eactive.apim.portal.portaluser.service;
public class AuthNumberException extends RuntimeException{
/**
* 인증번호(2FA/이메일/SMS) 검증·발송 실패 예외.
*
* <p>기존에는 메시지 문자열만 있었으나, 감사 로그에서 실패 사유(만료/불일치/미존재)를
* 코드로 구분해야 하므로 {@link Reason} 을 추가했다. 기존 메시지 전용 생성자는 유지되며
* 이 경우 {@code reason == null} 이다(하위호환).</p>
*/
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;
}
}
@@ -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;
}