UserLog 엔티티 필드 추가 및 관련 API User-Agent 처리 지원
- UserLog에 userAgent(500자)와 duplicate(Boolean) 컬럼 추가 - 로그인/실패/로그아웃 로깅 시 User-Agent와 중복 세션 여부 기록 - TwoFactorService/Controller Request 객체 전달 및 처리 반영
This commit is contained in:
@@ -41,8 +41,9 @@ public class TwoFactorController {
|
||||
public TwoFactorSendResponse send(@RequestParam String channel,
|
||||
@RequestParam(required = false) String purpose,
|
||||
@RequestParam(required = false, defaultValue = "false") boolean force,
|
||||
HttpServletRequest request,
|
||||
HttpSession session) {
|
||||
return twoFactorService.send(session, channel, purpose, force);
|
||||
return twoFactorService.send(request, session, channel, purpose, force);
|
||||
}
|
||||
|
||||
/** 인증번호 검증 */
|
||||
|
||||
@@ -115,7 +115,8 @@ public class TwoFactorService {
|
||||
// SEND
|
||||
// =========================================================================
|
||||
|
||||
public TwoFactorSendResponse send(HttpSession session, String channel, String purpose, boolean force) {
|
||||
public TwoFactorSendResponse send(HttpServletRequest request, HttpSession session, String channel,
|
||||
String purpose, boolean force) {
|
||||
TwoFactorSendResponse res = new TwoFactorSendResponse();
|
||||
|
||||
TwoFactorContext.Mode mode = resolveMode(session);
|
||||
@@ -156,7 +157,7 @@ public class TwoFactorService {
|
||||
res.setMessage("진행 중인 다른 인증 절차가 있습니다. 강제 종료 후 진행하시겠습니까?");
|
||||
return res;
|
||||
}
|
||||
discardContext(session, ctx); // 강제 종료(감사 기록 포함)
|
||||
discardContext(request, session, ctx); // 강제 종료(감사 기록 포함)
|
||||
} else if (ctx.getStartedAt() != null
|
||||
&& ctx.getStartedAt().plusSeconds(RESEND_THROTTLE_SECONDS).isAfter(LocalDateTime.now())) {
|
||||
res.setValid(false);
|
||||
@@ -280,7 +281,7 @@ public class TwoFactorService {
|
||||
String stateError = revalidateLoginState(user);
|
||||
if (stateError != null) {
|
||||
userLogService.logFailure(loginId, request.getRemoteAddr(), session.getId(),
|
||||
LoginFailureReason.ACCOUNT_DISABLED);
|
||||
LoginFailureReason.ACCOUNT_DISABLED, request.getHeader("User-Agent"));
|
||||
clearPending(session);
|
||||
res.setValid(false);
|
||||
res.setTerminated(true);
|
||||
@@ -316,7 +317,8 @@ public class TwoFactorService {
|
||||
|
||||
if (ctx != null && ctx.getMode() == TwoFactorContext.Mode.LOGIN) {
|
||||
String loginId = (String) session.getAttribute(ATTR_PENDING_LOGIN_ID);
|
||||
userLogService.logFailure(loginId, request.getRemoteAddr(), session.getId(), failureReason);
|
||||
userLogService.logFailure(loginId, request.getRemoteAddr(), session.getId(), failureReason,
|
||||
request.getHeader("User-Agent"));
|
||||
}
|
||||
if (ctx != null && ctx.getRecipient() != null) {
|
||||
authNumberStorage.deleteAuthNumber(ctx.getRecipient());
|
||||
@@ -456,10 +458,11 @@ public class TwoFactorService {
|
||||
}
|
||||
|
||||
/** 강제 종료: 인증번호 삭제 + (로그인 컨텍스트면) 취소 감사 기록 */
|
||||
private void discardContext(HttpSession session, TwoFactorContext ctx) {
|
||||
private void discardContext(HttpServletRequest request, HttpSession session, TwoFactorContext ctx) {
|
||||
if (ctx.getMode() == TwoFactorContext.Mode.LOGIN) {
|
||||
String loginId = (String) session.getAttribute(ATTR_PENDING_LOGIN_ID);
|
||||
userLogService.logFailure(loginId, "-", session.getId(), LoginFailureReason.TWO_FACTOR_CANCELLED);
|
||||
userLogService.logFailure(loginId, request.getRemoteAddr(), session.getId(),
|
||||
LoginFailureReason.TWO_FACTOR_CANCELLED, request.getHeader("User-Agent"));
|
||||
}
|
||||
if (ctx.getRecipient() != null) {
|
||||
authNumberStorage.deleteAuthNumber(ctx.getRecipient());
|
||||
@@ -472,7 +475,8 @@ public class TwoFactorService {
|
||||
LoginFailureReason reason, HttpServletRequest request) {
|
||||
if (ctx.getMode() == TwoFactorContext.Mode.LOGIN) {
|
||||
String loginId = (String) session.getAttribute(ATTR_PENDING_LOGIN_ID);
|
||||
userLogService.logFailure(loginId, request.getRemoteAddr(), session.getId(), reason);
|
||||
userLogService.logFailure(loginId, request.getRemoteAddr(), session.getId(), reason,
|
||||
request.getHeader("User-Agent"));
|
||||
clearPending(session);
|
||||
}
|
||||
if (ctx.getRecipient() != null) {
|
||||
|
||||
@@ -92,7 +92,12 @@ public class LoginFinalizer {
|
||||
|
||||
String ip = request.getRemoteAddr();
|
||||
String sessionId = request.getSession().getId();
|
||||
userLogService.logSuccess(rawUsername, ip, sessionId, loginType);
|
||||
// 다른 기기/브라우저의 활성 세션이 남아 있는 상태의 로그인인지(아래 forceLogoutOtherSessions 로
|
||||
// 기존 세션이 끊기는 건인지) 기록한다. 현재 세션 등록(registerSession)은 이 뒤에 일어나므로
|
||||
// 여기서 조회되는 활성 세션은 항상 "다른 곳"의 세션이다.
|
||||
boolean duplicate = userSessionService.getActiveSession(normalizedUsername).isPresent();
|
||||
userLogService.logSuccess(rawUsername, ip, sessionId, loginType, request.getHeader("User-Agent"),
|
||||
duplicate);
|
||||
|
||||
String contextPath = request.getContextPath();
|
||||
HttpSession session = request.getSession();
|
||||
|
||||
@@ -12,17 +12,22 @@ import org.springframework.transaction.annotation.Transactional;
|
||||
@Transactional
|
||||
public class PortalUserLogService {
|
||||
|
||||
/** PTL_USER_LOG.USER_AGENT 컬럼 길이. 초과분은 잘라서 저장한다(초과 시 ORA-12899 로 로그인 실패). */
|
||||
private static final int USER_AGENT_MAX_LENGTH = 500;
|
||||
|
||||
private final UserLogRepository userLogRepository;
|
||||
|
||||
public PortalUserLogService(UserLogRepository userLogRepository) {
|
||||
this.userLogRepository = userLogRepository;
|
||||
}
|
||||
|
||||
public void logSuccess(String userId, String ip, String sessionId) {
|
||||
logSuccess(userId, ip, sessionId, LoginType.NORMAL);
|
||||
}
|
||||
|
||||
public void logSuccess(String userId, String ip, String sessionId, LoginType loginType) {
|
||||
/**
|
||||
* 로그인 성공 기록.
|
||||
*
|
||||
* @param duplicate 로그인 시점에 다른 활성 세션이 남아 있어 기존 세션이 강제 해제된 건이면 true
|
||||
*/
|
||||
public void logSuccess(String userId, String ip, String sessionId, LoginType loginType, String userAgent,
|
||||
boolean duplicate) {
|
||||
UserLog log = new UserLog();
|
||||
log.setLoginId(userId);
|
||||
log.setLoginTime(LocalDateTime.now());
|
||||
@@ -30,15 +35,13 @@ public class PortalUserLogService {
|
||||
log.setSessionId(sessionId);
|
||||
log.setSuccess(true);
|
||||
log.setLoginType(loginType != null ? loginType.name() : null);
|
||||
log.setUserAgent(truncateUserAgent(userAgent));
|
||||
log.setDuplicate(duplicate);
|
||||
|
||||
userLogRepository.save(log);
|
||||
}
|
||||
|
||||
public void logFailure(String userId, String ip, String sessionId) {
|
||||
logFailure(userId, ip, sessionId, LoginFailureReason.UNKNOWN);
|
||||
}
|
||||
|
||||
public void logFailure(String userId, String ip, String sessionId, LoginFailureReason reason) {
|
||||
public void logFailure(String userId, String ip, String sessionId, LoginFailureReason reason, String userAgent) {
|
||||
UserLog log = new UserLog();
|
||||
log.setLoginId(userId);
|
||||
log.setLoginTime(LocalDateTime.now());
|
||||
@@ -46,19 +49,29 @@ public class PortalUserLogService {
|
||||
log.setSessionId(sessionId);
|
||||
log.setSuccess(false);
|
||||
log.setFailureReason(reason != null ? reason.name() : LoginFailureReason.UNKNOWN.name());
|
||||
log.setUserAgent(truncateUserAgent(userAgent));
|
||||
|
||||
userLogRepository.save(log);
|
||||
}
|
||||
|
||||
@Transactional
|
||||
public void logLogout(String userId, String ip, String sessionId) {
|
||||
public void logLogout(String userId, String ip, String sessionId, String userAgent) {
|
||||
UserLog log = new UserLog();
|
||||
log.setLoginId(userId);
|
||||
log.setLogoutTime(LocalDateTime.now());
|
||||
log.setIp(ip);
|
||||
log.setSessionId(sessionId);
|
||||
log.setSuccess(true);
|
||||
log.setUserAgent(truncateUserAgent(userAgent));
|
||||
|
||||
userLogRepository.save(log);
|
||||
}
|
||||
|
||||
private String truncateUserAgent(String userAgent) {
|
||||
if (userAgent == null) {
|
||||
return null;
|
||||
}
|
||||
return userAgent.length() <= USER_AGENT_MAX_LENGTH
|
||||
? userAgent : userAgent.substring(0, USER_AGENT_MAX_LENGTH);
|
||||
}
|
||||
}
|
||||
|
||||
+2
-1
@@ -97,7 +97,8 @@ public class PortalAuthenticationFailureHandler implements AuthenticationFailure
|
||||
}
|
||||
}
|
||||
|
||||
userLogService.logFailure(username, ip, sessionId, resolveFailureReason(exception));
|
||||
userLogService.logFailure(username, ip, sessionId, resolveFailureReason(exception),
|
||||
request.getHeader("User-Agent"));
|
||||
|
||||
// 로그인 실패 시 세션 정보 로깅
|
||||
logLoginFailure(request, username, exception);
|
||||
|
||||
Reference in New Issue
Block a user