Add HTTP session logging for authentication events
This commit is contained in:
@@ -0,0 +1,99 @@
|
|||||||
|
package com.eactive.apim.portal.common.util;
|
||||||
|
|
||||||
|
import javax.servlet.http.HttpServletRequest;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* HTTP 요청 관련 유틸리티 클래스
|
||||||
|
*/
|
||||||
|
public class HttpRequestUtil {
|
||||||
|
|
||||||
|
private static final String[] IP_HEADER_CANDIDATES = {
|
||||||
|
"X-Forwarded-For",
|
||||||
|
"Proxy-Client-IP",
|
||||||
|
"WL-Proxy-Client-IP",
|
||||||
|
"HTTP_X_FORWARDED_FOR",
|
||||||
|
"HTTP_X_FORWARDED",
|
||||||
|
"HTTP_X_CLUSTER_CLIENT_IP",
|
||||||
|
"HTTP_CLIENT_IP",
|
||||||
|
"HTTP_FORWARDED_FOR",
|
||||||
|
"HTTP_FORWARDED",
|
||||||
|
"HTTP_VIA",
|
||||||
|
"REMOTE_ADDR",
|
||||||
|
"X-Real-IP"
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Proxy를 통한 접속도 고려하여 실제 클라이언트 IP 주소를 반환
|
||||||
|
*
|
||||||
|
* @param request HttpServletRequest 객체
|
||||||
|
* @return 클라이언트 IP 주소
|
||||||
|
*/
|
||||||
|
public static String getClientIpAddress(HttpServletRequest request) {
|
||||||
|
if (request == null) {
|
||||||
|
return "unknown";
|
||||||
|
}
|
||||||
|
|
||||||
|
String ip = null;
|
||||||
|
|
||||||
|
// 헤더들을 순회하면서 IP 주소 찾기
|
||||||
|
for (String header : IP_HEADER_CANDIDATES) {
|
||||||
|
ip = request.getHeader(header);
|
||||||
|
if (ip != null && !ip.isEmpty() && !"unknown".equalsIgnoreCase(ip)) {
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 헤더에서 IP를 찾지 못한 경우 기본 메서드 사용
|
||||||
|
if (ip == null || ip.isEmpty() || "unknown".equalsIgnoreCase(ip)) {
|
||||||
|
ip = request.getRemoteAddr();
|
||||||
|
}
|
||||||
|
|
||||||
|
// X-Forwarded-For 헤더는 여러 IP를 포함할 수 있음 (쉼표로 구분)
|
||||||
|
// 첫 번째 IP가 실제 클라이언트 IP
|
||||||
|
if (ip != null && ip.contains(",")) {
|
||||||
|
ip = ip.split(",")[0].trim();
|
||||||
|
}
|
||||||
|
|
||||||
|
return ip;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Proxy를 통한 접속도 고려하여 실제 클라이언트 호스트명을 반환
|
||||||
|
*
|
||||||
|
* @param request HttpServletRequest 객체
|
||||||
|
* @return 클라이언트 호스트명
|
||||||
|
*/
|
||||||
|
public static String getClientHost(HttpServletRequest request) {
|
||||||
|
if (request == null) {
|
||||||
|
return "unknown";
|
||||||
|
}
|
||||||
|
|
||||||
|
String host = request.getHeader("X-Forwarded-Host");
|
||||||
|
if (host == null || host.isEmpty() || "unknown".equalsIgnoreCase(host)) {
|
||||||
|
host = request.getRemoteHost();
|
||||||
|
}
|
||||||
|
|
||||||
|
return host;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 요청이 Proxy를 통해 들어왔는지 확인
|
||||||
|
*
|
||||||
|
* @param request HttpServletRequest 객체
|
||||||
|
* @return Proxy를 통한 접속이면 true, 아니면 false
|
||||||
|
*/
|
||||||
|
public static boolean isProxied(HttpServletRequest request) {
|
||||||
|
if (request == null) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
for (String header : IP_HEADER_CANDIDATES) {
|
||||||
|
String value = request.getHeader(header);
|
||||||
|
if (value != null && !value.isEmpty() && !"unknown".equalsIgnoreCase(value)) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,43 @@
|
|||||||
|
package com.eactive.apim.portal.common.util;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* JDK 8 호환성을 위한 문자열 반복 유틸리티
|
||||||
|
*/
|
||||||
|
public class StringRepeatUtil {
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 문자를 지정된 횟수만큼 반복하여 문자열을 생성
|
||||||
|
*
|
||||||
|
* @param ch 반복할 문자
|
||||||
|
* @param count 반복 횟수
|
||||||
|
* @return 반복된 문자열
|
||||||
|
*/
|
||||||
|
public static String repeat(char ch, int count) {
|
||||||
|
if (count <= 0) {
|
||||||
|
return "";
|
||||||
|
}
|
||||||
|
char[] chars = new char[count];
|
||||||
|
for (int i = 0; i < count; i++) {
|
||||||
|
chars[i] = ch;
|
||||||
|
}
|
||||||
|
return new String(chars);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 문자열을 지정된 횟수만큼 반복
|
||||||
|
*
|
||||||
|
* @param str 반복할 문자열
|
||||||
|
* @param count 반복 횟수
|
||||||
|
* @return 반복된 문자열
|
||||||
|
*/
|
||||||
|
public static String repeat(String str, int count) {
|
||||||
|
if (str == null || count <= 0) {
|
||||||
|
return "";
|
||||||
|
}
|
||||||
|
StringBuilder sb = new StringBuilder(str.length() * count);
|
||||||
|
for (int i = 0; i < count; i++) {
|
||||||
|
sb.append(str);
|
||||||
|
}
|
||||||
|
return sb.toString();
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -3,6 +3,8 @@ package com.eactive.apim.portal.config;
|
|||||||
import com.eactive.apim.portal.apps.login.constants.LoginConstants;
|
import com.eactive.apim.portal.apps.login.constants.LoginConstants;
|
||||||
import com.eactive.apim.portal.apps.user.service.PortalUserLogService;
|
import com.eactive.apim.portal.apps.user.service.PortalUserLogService;
|
||||||
import com.eactive.apim.portal.common.exception.UserNotFoundException;
|
import com.eactive.apim.portal.common.exception.UserNotFoundException;
|
||||||
|
import com.eactive.apim.portal.common.util.HttpRequestUtil;
|
||||||
|
import com.eactive.apim.portal.common.util.StringRepeatUtil;
|
||||||
import com.eactive.apim.portal.portaluser.entity.PortalUser;
|
import com.eactive.apim.portal.portaluser.entity.PortalUser;
|
||||||
import com.eactive.apim.portal.portaluser.repository.PortalUserRepository;
|
import com.eactive.apim.portal.portaluser.repository.PortalUserRepository;
|
||||||
import com.eactive.apim.portal.template.entity.MessageCode;
|
import com.eactive.apim.portal.template.entity.MessageCode;
|
||||||
@@ -23,6 +25,8 @@ import javax.servlet.http.HttpServletRequest;
|
|||||||
import javax.servlet.http.HttpServletResponse;
|
import javax.servlet.http.HttpServletResponse;
|
||||||
import javax.servlet.http.HttpSession;
|
import javax.servlet.http.HttpSession;
|
||||||
import java.io.IOException;
|
import java.io.IOException;
|
||||||
|
import java.time.LocalDateTime;
|
||||||
|
import java.time.format.DateTimeFormatter;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Created by Sungpil Hyun
|
* Created by Sungpil Hyun
|
||||||
@@ -32,6 +36,9 @@ import java.io.IOException;
|
|||||||
public class PortalAuthenticationFailureHandler implements AuthenticationFailureHandler {
|
public class PortalAuthenticationFailureHandler implements AuthenticationFailureHandler {
|
||||||
|
|
||||||
private static final Logger logger = LoggerFactory.getLogger(PortalAuthenticationFailureHandler.class);
|
private static final Logger logger = LoggerFactory.getLogger(PortalAuthenticationFailureHandler.class);
|
||||||
|
private static final Logger sessionLogger = LoggerFactory.getLogger("eapim.portal.session");
|
||||||
|
private static final DateTimeFormatter formatter = DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss.SSS");
|
||||||
|
|
||||||
private final PortalUserRepository portalUserRepository;
|
private final PortalUserRepository portalUserRepository;
|
||||||
private final PortalUserLogService userLogService;
|
private final PortalUserLogService userLogService;
|
||||||
private final MessageHandlerService messageHandlerService;
|
private final MessageHandlerService messageHandlerService;
|
||||||
@@ -82,10 +89,39 @@ public class PortalAuthenticationFailureHandler implements AuthenticationFailure
|
|||||||
|
|
||||||
userLogService.logFailure(username, ip, sessionId);
|
userLogService.logFailure(username, ip, sessionId);
|
||||||
|
|
||||||
|
// 로그인 실패 시 세션 정보 로깅
|
||||||
|
logLoginFailure(request, username, exception);
|
||||||
|
|
||||||
HttpSession session = request.getSession();
|
HttpSession session = request.getSession();
|
||||||
session.setAttribute(LoginConstants.LOGIN_MESSAGE, exception.getLocalizedMessage());
|
session.setAttribute(LoginConstants.LOGIN_MESSAGE, exception.getLocalizedMessage());
|
||||||
session.setAttribute("loginId", username);
|
session.setAttribute("loginId", username);
|
||||||
String contextPath = request.getContextPath();
|
String contextPath = request.getContextPath();
|
||||||
response.sendRedirect(contextPath + "/login");
|
response.sendRedirect(contextPath + "/login");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private void logLoginFailure(HttpServletRequest request, String username, AuthenticationException exception) {
|
||||||
|
StringBuilder logMessage = new StringBuilder();
|
||||||
|
logMessage.append("\n");
|
||||||
|
logMessage.append(StringRepeatUtil.repeat('=', 80)).append("\n");
|
||||||
|
logMessage.append("USER LOGIN FAILURE\n");
|
||||||
|
logMessage.append(StringRepeatUtil.repeat('=', 80)).append("\n");
|
||||||
|
logMessage.append("Username: ").append(username).append("\n");
|
||||||
|
logMessage.append("Session ID: ").append(request.getSession().getId()).append("\n");
|
||||||
|
logMessage.append("Failed At: ").append(LocalDateTime.now().format(formatter)).append("\n");
|
||||||
|
logMessage.append("Failure Reason: ").append(exception.getLocalizedMessage()).append("\n");
|
||||||
|
logMessage.append("\n");
|
||||||
|
logMessage.append(StringRepeatUtil.repeat('-', 80)).append("\n");
|
||||||
|
logMessage.append("REQUEST INFORMATION\n");
|
||||||
|
logMessage.append(StringRepeatUtil.repeat('-', 80)).append("\n");
|
||||||
|
logMessage.append("Client IP Address: ").append(HttpRequestUtil.getClientIpAddress(request)).append("\n");
|
||||||
|
logMessage.append("Client Host: ").append(HttpRequestUtil.getClientHost(request)).append("\n");
|
||||||
|
logMessage.append("Is Proxied: ").append(HttpRequestUtil.isProxied(request)).append("\n");
|
||||||
|
logMessage.append("Remote Address (Direct): ").append(request.getRemoteAddr()).append("\n");
|
||||||
|
logMessage.append("Remote Host (Direct): ").append(request.getRemoteHost()).append("\n");
|
||||||
|
logMessage.append("Request URI: ").append(request.getRequestURI()).append("\n");
|
||||||
|
logMessage.append("User-Agent: ").append(request.getHeader("User-Agent")).append("\n");
|
||||||
|
logMessage.append(StringRepeatUtil.repeat('=', 80)).append("\n");
|
||||||
|
|
||||||
|
sessionLogger.warn(logMessage.toString());
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -2,6 +2,8 @@ package com.eactive.apim.portal.config;
|
|||||||
|
|
||||||
import com.eactive.apim.portal.apps.user.repository.PortalOrgRepository;
|
import com.eactive.apim.portal.apps.user.repository.PortalOrgRepository;
|
||||||
import com.eactive.apim.portal.apps.user.service.PortalUserLogService;
|
import com.eactive.apim.portal.apps.user.service.PortalUserLogService;
|
||||||
|
import com.eactive.apim.portal.common.util.HttpRequestUtil;
|
||||||
|
import com.eactive.apim.portal.common.util.StringRepeatUtil;
|
||||||
import com.eactive.apim.portal.invitation.entity.UserInvitation;
|
import com.eactive.apim.portal.invitation.entity.UserInvitation;
|
||||||
import com.eactive.apim.portal.invitation.entity.UserInvitationEnums.InvitationStatus;
|
import com.eactive.apim.portal.invitation.entity.UserInvitationEnums.InvitationStatus;
|
||||||
import com.eactive.apim.portal.invitation.repository.UserInvitationRepository;
|
import com.eactive.apim.portal.invitation.repository.UserInvitationRepository;
|
||||||
@@ -15,6 +17,8 @@ import com.eactive.apim.portal.template.entity.MessageCode;
|
|||||||
import com.eactive.apim.portal.template.entity.MessageRequest;
|
import com.eactive.apim.portal.template.entity.MessageRequest;
|
||||||
import com.eactive.apim.portal.template.repository.MessageRequestRepository;
|
import com.eactive.apim.portal.template.repository.MessageRequestRepository;
|
||||||
import lombok.RequiredArgsConstructor;
|
import lombok.RequiredArgsConstructor;
|
||||||
|
import org.slf4j.Logger;
|
||||||
|
import org.slf4j.LoggerFactory;
|
||||||
import org.springframework.security.core.Authentication;
|
import org.springframework.security.core.Authentication;
|
||||||
import org.springframework.security.web.authentication.AuthenticationSuccessHandler;
|
import org.springframework.security.web.authentication.AuthenticationSuccessHandler;
|
||||||
import org.springframework.stereotype.Service;
|
import org.springframework.stereotype.Service;
|
||||||
@@ -26,6 +30,7 @@ import javax.servlet.http.HttpServletResponse;
|
|||||||
import javax.servlet.http.HttpSession;
|
import javax.servlet.http.HttpSession;
|
||||||
import java.io.IOException;
|
import java.io.IOException;
|
||||||
import java.time.LocalDateTime;
|
import java.time.LocalDateTime;
|
||||||
|
import java.time.format.DateTimeFormatter;
|
||||||
import java.util.Optional;
|
import java.util.Optional;
|
||||||
|
|
||||||
|
|
||||||
@@ -34,6 +39,9 @@ import java.util.Optional;
|
|||||||
@RequiredArgsConstructor
|
@RequiredArgsConstructor
|
||||||
public class PortalAuthenticationSuccessHandler implements AuthenticationSuccessHandler {
|
public class PortalAuthenticationSuccessHandler implements AuthenticationSuccessHandler {
|
||||||
|
|
||||||
|
private static final Logger sessionLogger = LoggerFactory.getLogger("eapim.portal.session");
|
||||||
|
private static final DateTimeFormatter formatter = DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss.SSS");
|
||||||
|
|
||||||
private final PortalUserRepository portalUserRepository;
|
private final PortalUserRepository portalUserRepository;
|
||||||
private final PortalProperties portalProperties;
|
private final PortalProperties portalProperties;
|
||||||
private final PortalUserLogService userLogService;
|
private final PortalUserLogService userLogService;
|
||||||
@@ -104,6 +112,9 @@ public class PortalAuthenticationSuccessHandler implements AuthenticationSuccess
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// 로그인 성공 시 세션 정보 로깅
|
||||||
|
logLoginSuccess(request, session, username);
|
||||||
|
|
||||||
String decisionToken = (String) request.getSession().getAttribute("decisionToken");
|
String decisionToken = (String) request.getSession().getAttribute("decisionToken");
|
||||||
if (decisionToken != null) {
|
if (decisionToken != null) {
|
||||||
response.sendRedirect(contextPath + "/signup/decision_process");
|
response.sendRedirect(contextPath + "/signup/decision_process");
|
||||||
@@ -112,6 +123,31 @@ public class PortalAuthenticationSuccessHandler implements AuthenticationSuccess
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private void logLoginSuccess(HttpServletRequest request, HttpSession session, String username) {
|
||||||
|
StringBuilder logMessage = new StringBuilder();
|
||||||
|
logMessage.append("\n");
|
||||||
|
logMessage.append(StringRepeatUtil.repeat('=', 80)).append("\n");
|
||||||
|
logMessage.append("USER LOGIN SUCCESS\n");
|
||||||
|
logMessage.append(StringRepeatUtil.repeat('=', 80)).append("\n");
|
||||||
|
logMessage.append("Username: ").append(username).append("\n");
|
||||||
|
logMessage.append("Session ID: ").append(session.getId()).append("\n");
|
||||||
|
logMessage.append("Login At: ").append(LocalDateTime.now().format(formatter)).append("\n");
|
||||||
|
logMessage.append("\n");
|
||||||
|
logMessage.append(StringRepeatUtil.repeat('-', 80)).append("\n");
|
||||||
|
logMessage.append("REQUEST INFORMATION\n");
|
||||||
|
logMessage.append(StringRepeatUtil.repeat('-', 80)).append("\n");
|
||||||
|
logMessage.append("Client IP Address: ").append(HttpRequestUtil.getClientIpAddress(request)).append("\n");
|
||||||
|
logMessage.append("Client Host: ").append(HttpRequestUtil.getClientHost(request)).append("\n");
|
||||||
|
logMessage.append("Is Proxied: ").append(HttpRequestUtil.isProxied(request)).append("\n");
|
||||||
|
logMessage.append("Remote Address (Direct): ").append(request.getRemoteAddr()).append("\n");
|
||||||
|
logMessage.append("Remote Host (Direct): ").append(request.getRemoteHost()).append("\n");
|
||||||
|
logMessage.append("Request URI: ").append(request.getRequestURI()).append("\n");
|
||||||
|
logMessage.append("User-Agent: ").append(request.getHeader("User-Agent")).append("\n");
|
||||||
|
logMessage.append(StringRepeatUtil.repeat('=', 80)).append("\n");
|
||||||
|
|
||||||
|
sessionLogger.info(logMessage.toString());
|
||||||
|
}
|
||||||
|
|
||||||
private boolean isPasswordChangeRequired(PortalUser user) {
|
private boolean isPasswordChangeRequired(PortalUser user) {
|
||||||
// 가장 최근 비밀번호 변경 이력 조회
|
// 가장 최근 비밀번호 변경 이력 조회
|
||||||
Optional<UserPasswordHistory> latestHistory = passwordHistoryRepository
|
Optional<UserPasswordHistory> latestHistory = passwordHistoryRepository
|
||||||
|
|||||||
@@ -1,5 +1,6 @@
|
|||||||
package com.eactive.apim.portal.config;
|
package com.eactive.apim.portal.config;
|
||||||
|
|
||||||
|
import com.eactive.apim.portal.common.util.HttpRequestUtil;
|
||||||
import com.eactive.apim.portal.common.util.SecurityUtil;
|
import com.eactive.apim.portal.common.util.SecurityUtil;
|
||||||
import org.aspectj.lang.JoinPoint;
|
import org.aspectj.lang.JoinPoint;
|
||||||
import org.aspectj.lang.annotation.Aspect;
|
import org.aspectj.lang.annotation.Aspect;
|
||||||
@@ -29,7 +30,7 @@ public class PortalConfigLog {
|
|||||||
String method = request.getMethod();
|
String method = request.getMethod();
|
||||||
String path = request.getRequestURI();
|
String path = request.getRequestURI();
|
||||||
String query = request.getQueryString();
|
String query = request.getQueryString();
|
||||||
String ip = request.getRemoteAddr();
|
String ip = HttpRequestUtil.getClientIpAddress(request);
|
||||||
|
|
||||||
String user = SecurityUtil.getCurrentLoginId();
|
String user = SecurityUtil.getCurrentLoginId();
|
||||||
|
|
||||||
|
|||||||
@@ -31,13 +31,17 @@ public class PortalConfigSecurity {
|
|||||||
|
|
||||||
private final PortalAuthenticationManager portalAuthenticationManager;
|
private final PortalAuthenticationManager portalAuthenticationManager;
|
||||||
|
|
||||||
|
private final PortalLogoutSuccessHandler logoutSuccessHandler;
|
||||||
|
|
||||||
@Autowired
|
@Autowired
|
||||||
public PortalConfigSecurity(PortalAuthenticationFailureHandler authenticationFailureHandler,
|
public PortalConfigSecurity(PortalAuthenticationFailureHandler authenticationFailureHandler,
|
||||||
PortalAuthenticationSuccessHandler authenticationSuccessHandler,
|
PortalAuthenticationSuccessHandler authenticationSuccessHandler,
|
||||||
PortalAuthenticationManager portalAuthenticationManager) {
|
PortalAuthenticationManager portalAuthenticationManager,
|
||||||
|
PortalLogoutSuccessHandler logoutSuccessHandler) {
|
||||||
this.authenticationFailureHandler = authenticationFailureHandler;
|
this.authenticationFailureHandler = authenticationFailureHandler;
|
||||||
this.authenticationSuccessHandler = authenticationSuccessHandler;
|
this.authenticationSuccessHandler = authenticationSuccessHandler;
|
||||||
this.portalAuthenticationManager = portalAuthenticationManager;
|
this.portalAuthenticationManager = portalAuthenticationManager;
|
||||||
|
this.logoutSuccessHandler = logoutSuccessHandler;
|
||||||
}
|
}
|
||||||
|
|
||||||
@Bean
|
@Bean
|
||||||
@@ -71,7 +75,7 @@ public class PortalConfigSecurity {
|
|||||||
.successHandler(authenticationSuccessHandler))
|
.successHandler(authenticationSuccessHandler))
|
||||||
.logout(logout -> logout
|
.logout(logout -> logout
|
||||||
.logoutRequestMatcher(new AntPathRequestMatcher("/actionLogout.do", "GET"))
|
.logoutRequestMatcher(new AntPathRequestMatcher("/actionLogout.do", "GET"))
|
||||||
.logoutSuccessUrl("/"))
|
.logoutSuccessHandler(logoutSuccessHandler))
|
||||||
.csrf(csrf -> csrf
|
.csrf(csrf -> csrf
|
||||||
.csrfTokenRepository(CookieCsrfTokenRepository.withHttpOnlyFalse())
|
.csrfTokenRepository(CookieCsrfTokenRepository.withHttpOnlyFalse())
|
||||||
.ignoringRequestMatchers(new AntPathRequestMatcher("/_proxy/**/*"))
|
.ignoringRequestMatchers(new AntPathRequestMatcher("/_proxy/**/*"))
|
||||||
|
|||||||
@@ -0,0 +1,87 @@
|
|||||||
|
package com.eactive.apim.portal.config;
|
||||||
|
|
||||||
|
import com.eactive.apim.portal.common.util.HttpRequestUtil;
|
||||||
|
import com.eactive.apim.portal.common.util.StringRepeatUtil;
|
||||||
|
import org.slf4j.Logger;
|
||||||
|
import org.slf4j.LoggerFactory;
|
||||||
|
import org.springframework.security.core.Authentication;
|
||||||
|
import org.springframework.security.web.authentication.logout.LogoutSuccessHandler;
|
||||||
|
import org.springframework.stereotype.Component;
|
||||||
|
|
||||||
|
import javax.servlet.ServletException;
|
||||||
|
import javax.servlet.http.HttpServletRequest;
|
||||||
|
import javax.servlet.http.HttpServletResponse;
|
||||||
|
import javax.servlet.http.HttpSession;
|
||||||
|
import java.io.IOException;
|
||||||
|
import java.time.LocalDateTime;
|
||||||
|
import java.time.format.DateTimeFormatter;
|
||||||
|
import java.util.Enumeration;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 로그아웃 성공 시 세션 정보를 로깅하는 핸들러
|
||||||
|
*/
|
||||||
|
@Component
|
||||||
|
public class PortalLogoutSuccessHandler implements LogoutSuccessHandler {
|
||||||
|
|
||||||
|
private static final Logger sessionLogger = LoggerFactory.getLogger("eapim.portal.session");
|
||||||
|
private static final DateTimeFormatter formatter = DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss.SSS");
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onLogoutSuccess(HttpServletRequest request, HttpServletResponse response,
|
||||||
|
Authentication authentication) throws IOException, ServletException {
|
||||||
|
HttpSession session = request.getSession(false);
|
||||||
|
|
||||||
|
if (session != null) {
|
||||||
|
StringBuilder logMessage = new StringBuilder();
|
||||||
|
logMessage.append("\n");
|
||||||
|
logMessage.append(StringRepeatUtil.repeat('=', 80)).append("\n");
|
||||||
|
logMessage.append("USER LOGOUT\n");
|
||||||
|
logMessage.append(StringRepeatUtil.repeat('=', 80)).append("\n");
|
||||||
|
logMessage.append("Session ID: ").append(session.getId()).append("\n");
|
||||||
|
logMessage.append("Logout At: ").append(LocalDateTime.now().format(formatter)).append("\n");
|
||||||
|
|
||||||
|
if (authentication != null) {
|
||||||
|
logMessage.append("Username: ").append(authentication.getName()).append("\n");
|
||||||
|
logMessage.append("Authenticated: ").append(authentication.isAuthenticated()).append("\n");
|
||||||
|
}
|
||||||
|
|
||||||
|
logMessage.append("\n");
|
||||||
|
logMessage.append(StringRepeatUtil.repeat('-', 80)).append("\n");
|
||||||
|
logMessage.append("REQUEST INFORMATION\n");
|
||||||
|
logMessage.append(StringRepeatUtil.repeat('-', 80)).append("\n");
|
||||||
|
logMessage.append("Client IP Address: ").append(HttpRequestUtil.getClientIpAddress(request)).append("\n");
|
||||||
|
logMessage.append("Client Host: ").append(HttpRequestUtil.getClientHost(request)).append("\n");
|
||||||
|
logMessage.append("Is Proxied: ").append(HttpRequestUtil.isProxied(request)).append("\n");
|
||||||
|
logMessage.append("Remote Address (Direct): ").append(request.getRemoteAddr()).append("\n");
|
||||||
|
logMessage.append("Remote Host (Direct): ").append(request.getRemoteHost()).append("\n");
|
||||||
|
logMessage.append("Request URI: ").append(request.getRequestURI()).append("\n");
|
||||||
|
logMessage.append("User-Agent: ").append(request.getHeader("User-Agent")).append("\n");
|
||||||
|
|
||||||
|
logMessage.append("\n");
|
||||||
|
logMessage.append(StringRepeatUtil.repeat('-', 80)).append("\n");
|
||||||
|
logMessage.append("SESSION ATTRIBUTES (Before Invalidation)\n");
|
||||||
|
logMessage.append(StringRepeatUtil.repeat('-', 80)).append("\n");
|
||||||
|
|
||||||
|
try {
|
||||||
|
Enumeration<String> attributeNames = session.getAttributeNames();
|
||||||
|
while (attributeNames.hasMoreElements()) {
|
||||||
|
String attrName = attributeNames.nextElement();
|
||||||
|
Object attrValue = session.getAttribute(attrName);
|
||||||
|
String valueStr = attrValue != null ? attrValue.toString() : "null";
|
||||||
|
if (valueStr.length() > 100) {
|
||||||
|
valueStr = valueStr.substring(0, 97) + "...";
|
||||||
|
}
|
||||||
|
logMessage.append(String.format(" %-30s : %s\n", attrName, valueStr));
|
||||||
|
}
|
||||||
|
} catch (Exception e) {
|
||||||
|
logMessage.append(" Unable to retrieve session attributes\n");
|
||||||
|
}
|
||||||
|
|
||||||
|
logMessage.append(StringRepeatUtil.repeat('=', 80)).append("\n");
|
||||||
|
|
||||||
|
sessionLogger.info(logMessage.toString());
|
||||||
|
}
|
||||||
|
|
||||||
|
response.sendRedirect(request.getContextPath() + "/");
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,134 @@
|
|||||||
|
package com.eactive.apim.portal.config;
|
||||||
|
|
||||||
|
import com.eactive.apim.portal.common.util.HttpRequestUtil;
|
||||||
|
import com.eactive.apim.portal.common.util.StringRepeatUtil;
|
||||||
|
import lombok.extern.slf4j.Slf4j;
|
||||||
|
import org.slf4j.Logger;
|
||||||
|
import org.slf4j.LoggerFactory;
|
||||||
|
import org.springframework.stereotype.Component;
|
||||||
|
|
||||||
|
import javax.servlet.http.HttpServletRequest;
|
||||||
|
import javax.servlet.http.HttpSession;
|
||||||
|
import javax.servlet.http.HttpSessionEvent;
|
||||||
|
import javax.servlet.http.HttpSessionListener;
|
||||||
|
import java.time.LocalDateTime;
|
||||||
|
import java.time.format.DateTimeFormatter;
|
||||||
|
import java.util.Enumeration;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* HttpSession 생성 시 요청 헤더 정보를 로깅하는 리스너
|
||||||
|
*/
|
||||||
|
@Component
|
||||||
|
public class SessionLoggingListener implements HttpSessionListener {
|
||||||
|
|
||||||
|
private static final Logger sessionLogger = LoggerFactory.getLogger("eapim.portal.session");
|
||||||
|
private static final DateTimeFormatter formatter = DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss.SSS");
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void sessionCreated(HttpSessionEvent se) {
|
||||||
|
HttpSession session = se.getSession();
|
||||||
|
|
||||||
|
// HttpServletRequest 가져오기
|
||||||
|
HttpServletRequest request = (HttpServletRequest) session.getAttribute("org.springframework.web.context.request.RequestContextListener.REQUEST_ATTRIBUTES");
|
||||||
|
|
||||||
|
// Spring의 RequestContextHolder를 통해 현재 요청 가져오기
|
||||||
|
try {
|
||||||
|
org.springframework.web.context.request.RequestAttributes requestAttributes =
|
||||||
|
org.springframework.web.context.request.RequestContextHolder.getRequestAttributes();
|
||||||
|
|
||||||
|
if (requestAttributes instanceof org.springframework.web.context.request.ServletRequestAttributes) {
|
||||||
|
request = ((org.springframework.web.context.request.ServletRequestAttributes) requestAttributes).getRequest();
|
||||||
|
}
|
||||||
|
} catch (Exception e) {
|
||||||
|
// RequestContextHolder를 통해 요청을 가져올 수 없는 경우 무시
|
||||||
|
}
|
||||||
|
|
||||||
|
StringBuilder logMessage = new StringBuilder();
|
||||||
|
logMessage.append("\n");
|
||||||
|
logMessage.append(StringRepeatUtil.repeat('=', 80)).append("\n");
|
||||||
|
logMessage.append("NEW HTTP SESSION CREATED\n");
|
||||||
|
logMessage.append(StringRepeatUtil.repeat('=', 80)).append("\n");
|
||||||
|
logMessage.append("Session ID: ").append(session.getId()).append("\n");
|
||||||
|
logMessage.append("Created At: ").append(LocalDateTime.now().format(formatter)).append("\n");
|
||||||
|
logMessage.append("Max Inactive Interval: ").append(session.getMaxInactiveInterval()).append(" seconds\n");
|
||||||
|
|
||||||
|
if (request != null) {
|
||||||
|
logMessage.append("\n");
|
||||||
|
logMessage.append(StringRepeatUtil.repeat('-', 80)).append("\n");
|
||||||
|
logMessage.append("REQUEST INFORMATION\n");
|
||||||
|
logMessage.append(StringRepeatUtil.repeat('-', 80)).append("\n");
|
||||||
|
logMessage.append("Client IP Address: ").append(HttpRequestUtil.getClientIpAddress(request)).append("\n");
|
||||||
|
logMessage.append("Client Host: ").append(HttpRequestUtil.getClientHost(request)).append("\n");
|
||||||
|
logMessage.append("Is Proxied: ").append(HttpRequestUtil.isProxied(request)).append("\n");
|
||||||
|
logMessage.append("Remote Address (Direct): ").append(request.getRemoteAddr()).append("\n");
|
||||||
|
logMessage.append("Remote Host (Direct): ").append(request.getRemoteHost()).append("\n");
|
||||||
|
logMessage.append("Remote Port: ").append(request.getRemotePort()).append("\n");
|
||||||
|
logMessage.append("Request Method: ").append(request.getMethod()).append("\n");
|
||||||
|
logMessage.append("Request URI: ").append(request.getRequestURI()).append("\n");
|
||||||
|
logMessage.append("Request URL: ").append(request.getRequestURL()).append("\n");
|
||||||
|
logMessage.append("Query String: ").append(request.getQueryString()).append("\n");
|
||||||
|
logMessage.append("Protocol: ").append(request.getProtocol()).append("\n");
|
||||||
|
logMessage.append("Scheme: ").append(request.getScheme()).append("\n");
|
||||||
|
logMessage.append("Server Name: ").append(request.getServerName()).append("\n");
|
||||||
|
logMessage.append("Server Port: ").append(request.getServerPort()).append("\n");
|
||||||
|
logMessage.append("Context Path: ").append(request.getContextPath()).append("\n");
|
||||||
|
|
||||||
|
logMessage.append("\n");
|
||||||
|
logMessage.append(StringRepeatUtil.repeat('-', 80)).append("\n");
|
||||||
|
logMessage.append("REQUEST HEADERS\n");
|
||||||
|
logMessage.append(StringRepeatUtil.repeat('-', 80)).append("\n");
|
||||||
|
|
||||||
|
Enumeration<String> headerNames = request.getHeaderNames();
|
||||||
|
while (headerNames.hasMoreElements()) {
|
||||||
|
String headerName = headerNames.nextElement();
|
||||||
|
Enumeration<String> headerValues = request.getHeaders(headerName);
|
||||||
|
while (headerValues.hasMoreElements()) {
|
||||||
|
String headerValue = headerValues.nextElement();
|
||||||
|
logMessage.append(String.format(" %-30s : %s\n", headerName, headerValue));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
logMessage.append("\n");
|
||||||
|
logMessage.append("WARNING: HttpServletRequest not available\n");
|
||||||
|
}
|
||||||
|
|
||||||
|
logMessage.append(StringRepeatUtil.repeat('=', 80)).append("\n");
|
||||||
|
|
||||||
|
sessionLogger.info(logMessage.toString());
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void sessionDestroyed(HttpSessionEvent se) {
|
||||||
|
HttpSession session = se.getSession();
|
||||||
|
|
||||||
|
StringBuilder logMessage = new StringBuilder();
|
||||||
|
logMessage.append("\n");
|
||||||
|
logMessage.append(StringRepeatUtil.repeat('=', 80)).append("\n");
|
||||||
|
logMessage.append("HTTP SESSION DESTROYED\n");
|
||||||
|
logMessage.append(StringRepeatUtil.repeat('=', 80)).append("\n");
|
||||||
|
logMessage.append("Session ID: ").append(session.getId()).append("\n");
|
||||||
|
logMessage.append("Destroyed At: ").append(LocalDateTime.now().format(formatter)).append("\n");
|
||||||
|
logMessage.append("Max Inactive Interval: ").append(session.getMaxInactiveInterval()).append(" seconds\n");
|
||||||
|
|
||||||
|
// 세션 속성 정보 로깅
|
||||||
|
try {
|
||||||
|
logMessage.append("\n");
|
||||||
|
logMessage.append(StringRepeatUtil.repeat('-', 80)).append("\n");
|
||||||
|
logMessage.append("SESSION ATTRIBUTES\n");
|
||||||
|
logMessage.append(StringRepeatUtil.repeat('-', 80)).append("\n");
|
||||||
|
|
||||||
|
java.util.Enumeration<String> attributeNames = session.getAttributeNames();
|
||||||
|
while (attributeNames.hasMoreElements()) {
|
||||||
|
String attrName = attributeNames.nextElement();
|
||||||
|
Object attrValue = session.getAttribute(attrName);
|
||||||
|
logMessage.append(String.format(" %-30s : %s\n", attrName, attrValue));
|
||||||
|
}
|
||||||
|
} catch (Exception e) {
|
||||||
|
logMessage.append(" Unable to retrieve session attributes\n");
|
||||||
|
}
|
||||||
|
|
||||||
|
logMessage.append(StringRepeatUtil.repeat('=', 80)).append("\n");
|
||||||
|
|
||||||
|
sessionLogger.info(logMessage.toString());
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -46,6 +46,17 @@
|
|||||||
</encoder>
|
</encoder>
|
||||||
</appender>
|
</appender>
|
||||||
|
|
||||||
|
<appender name="HTTP_SESSION" class="ch.qos.logback.core.rolling.RollingFileAppender">
|
||||||
|
<file>${LOG_PATH}/http-session.log</file>
|
||||||
|
<rollingPolicy class="ch.qos.logback.core.rolling.TimeBasedRollingPolicy">
|
||||||
|
<fileNamePattern>${LOG_PATH}/backup/http-session.%d{yyyy-MM-dd}.log</fileNamePattern>
|
||||||
|
<maxHistory>30</maxHistory>
|
||||||
|
</rollingPolicy>
|
||||||
|
<encoder>
|
||||||
|
<pattern>%d{yyyy-MM-dd HH:mm:ss.SSS} [%thread] %-5level - %msg%n</pattern>
|
||||||
|
</encoder>
|
||||||
|
</appender>
|
||||||
|
|
||||||
|
|
||||||
<root level="DEBUG">
|
<root level="DEBUG">
|
||||||
<appender-ref ref="FILE"/>
|
<appender-ref ref="FILE"/>
|
||||||
@@ -79,4 +90,8 @@
|
|||||||
<logger name="com.atomikos" level="DEBUG" additivity="false">
|
<logger name="com.atomikos" level="DEBUG" additivity="false">
|
||||||
<appender-ref ref="FILE_HIBERNATE" />
|
<appender-ref ref="FILE_HIBERNATE" />
|
||||||
</logger>
|
</logger>
|
||||||
|
|
||||||
|
<logger name="eapim.portal.session" level="INFO" additivity="false">
|
||||||
|
<appender-ref ref="HTTP_SESSION" />
|
||||||
|
</logger>
|
||||||
</configuration>
|
</configuration>
|
||||||
@@ -30,6 +30,17 @@
|
|||||||
</encoder>
|
</encoder>
|
||||||
</appender>
|
</appender>
|
||||||
|
|
||||||
|
<appender name="HTTP_SESSION" class="ch.qos.logback.core.rolling.RollingFileAppender">
|
||||||
|
<file>${LOG_PATH}/http-session.log</file>
|
||||||
|
<rollingPolicy class="ch.qos.logback.core.rolling.TimeBasedRollingPolicy">
|
||||||
|
<fileNamePattern>${LOG_PATH}/backup/http-session.%d{yyyy-MM-dd}.log</fileNamePattern>
|
||||||
|
<maxHistory>30</maxHistory>
|
||||||
|
</rollingPolicy>
|
||||||
|
<encoder>
|
||||||
|
<pattern>%d{yyyy-MM-dd HH:mm:ss.SSS} [%thread] %-5level - %msg%n</pattern>
|
||||||
|
</encoder>
|
||||||
|
</appender>
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
@@ -65,4 +76,8 @@
|
|||||||
<logger name="com.atomikos" level="DEBUG" additivity="false">
|
<logger name="com.atomikos" level="DEBUG" additivity="false">
|
||||||
<appender-ref ref="FILE_HIBERNATE" />
|
<appender-ref ref="FILE_HIBERNATE" />
|
||||||
</logger>
|
</logger>
|
||||||
|
|
||||||
|
<logger name="eapim.portal.session" level="INFO" additivity="false">
|
||||||
|
<appender-ref ref="HTTP_SESSION" />
|
||||||
|
</logger>
|
||||||
</configuration>
|
</configuration>
|
||||||
@@ -38,6 +38,17 @@
|
|||||||
</encoder>
|
</encoder>
|
||||||
</appender>
|
</appender>
|
||||||
|
|
||||||
|
<appender name="HTTP_SESSION" class="ch.qos.logback.core.rolling.RollingFileAppender">
|
||||||
|
<file>${LOG_PATH}/http-session.log</file>
|
||||||
|
<rollingPolicy class="ch.qos.logback.core.rolling.TimeBasedRollingPolicy">
|
||||||
|
<fileNamePattern>${LOG_PATH}/backup/http-session.%d{yyyy-MM-dd}.log</fileNamePattern>
|
||||||
|
<maxHistory>30</maxHistory>
|
||||||
|
</rollingPolicy>
|
||||||
|
<encoder>
|
||||||
|
<pattern>%d{yyyy-MM-dd HH:mm:ss.SSS} [%thread] %-5level - %msg%n</pattern>
|
||||||
|
</encoder>
|
||||||
|
</appender>
|
||||||
|
|
||||||
|
|
||||||
<root level="INFO">
|
<root level="INFO">
|
||||||
<appender-ref ref="FILE"/>
|
<appender-ref ref="FILE"/>
|
||||||
@@ -79,4 +90,8 @@
|
|||||||
<appender-ref ref="FILE_REPORT" />
|
<appender-ref ref="FILE_REPORT" />
|
||||||
</logger>
|
</logger>
|
||||||
|
|
||||||
|
<logger name="eapim.portal.session" level="INFO" additivity="false">
|
||||||
|
<appender-ref ref="HTTP_SESSION" />
|
||||||
|
</logger>
|
||||||
|
|
||||||
</configuration>
|
</configuration>
|
||||||
@@ -46,6 +46,17 @@
|
|||||||
</encoder>
|
</encoder>
|
||||||
</appender>
|
</appender>
|
||||||
|
|
||||||
|
<appender name="HTTP_SESSION" class="ch.qos.logback.core.rolling.RollingFileAppender">
|
||||||
|
<file>${LOG_PATH}/http-session.log</file>
|
||||||
|
<rollingPolicy class="ch.qos.logback.core.rolling.TimeBasedRollingPolicy">
|
||||||
|
<fileNamePattern>${LOG_PATH}/backup/http-session.%d{yyyy-MM-dd}.log</fileNamePattern>
|
||||||
|
<maxHistory>30</maxHistory>
|
||||||
|
</rollingPolicy>
|
||||||
|
<encoder>
|
||||||
|
<pattern>%d{yyyy-MM-dd HH:mm:ss.SSS} [%thread] %-5level - %msg%n</pattern>
|
||||||
|
</encoder>
|
||||||
|
</appender>
|
||||||
|
|
||||||
<appender name="CONSOLE" class="ch.qos.logback.core.ConsoleAppender">
|
<appender name="CONSOLE" class="ch.qos.logback.core.ConsoleAppender">
|
||||||
<encoder><Pattern>${CONSOLE_PATTERN}</Pattern></encoder>
|
<encoder><Pattern>${CONSOLE_PATTERN}</Pattern></encoder>
|
||||||
</appender>
|
</appender>
|
||||||
@@ -55,6 +66,10 @@
|
|||||||
<appender-ref ref="NEED_FIX" />
|
<appender-ref ref="NEED_FIX" />
|
||||||
</logger>
|
</logger>
|
||||||
|
|
||||||
|
<logger name="eapim.portal.session" level="INFO" additivity="false">
|
||||||
|
<appender-ref ref="HTTP_SESSION" />
|
||||||
|
</logger>
|
||||||
|
|
||||||
|
|
||||||
<root level="INFO">
|
<root level="INFO">
|
||||||
<appender-ref ref="ROLLING"/>
|
<appender-ref ref="ROLLING"/>
|
||||||
|
|||||||
Reference in New Issue
Block a user