Compare commits
	
		
			2 Commits
		
	
	
		
			072f782652
			...
			e1283792c3
		
	
	| Author | SHA1 | Date | |
|---|---|---|---|
| e1283792c3 | |||
| ff7d69f0b6 | 
@@ -11,106 +11,144 @@ import org.springframework.web.util.ContentCachingRequestWrapper;
 | 
				
			|||||||
import org.springframework.web.util.ContentCachingResponseWrapper;
 | 
					import org.springframework.web.util.ContentCachingResponseWrapper;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
import java.io.IOException;
 | 
					import java.io.IOException;
 | 
				
			||||||
import java.util.HashMap;
 | 
					import java.nio.charset.Charset;
 | 
				
			||||||
import java.util.Map;
 | 
					import java.nio.charset.StandardCharsets;
 | 
				
			||||||
 | 
					import java.util.*;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
@Slf4j
 | 
					@Slf4j
 | 
				
			||||||
@Component
 | 
					@Component
 | 
				
			||||||
public class HttpLoggingFilter extends OncePerRequestFilter {
 | 
					public class HttpLoggingFilter extends OncePerRequestFilter {
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    private static final int MAX_LOG_BODY = 10 * 1024; // 10 KB
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    @Override
 | 
					    @Override
 | 
				
			||||||
    protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain filterChain)
 | 
					    protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain filterChain)
 | 
				
			||||||
            throws ServletException, IOException {
 | 
					            throws ServletException, IOException {
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        long startTime = System.currentTimeMillis();
 | 
					        long startTime = System.currentTimeMillis();
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        // 요청 정보 로깅 (IP 정보 제거)
 | 
					        ContentCachingRequestWrapper wrappedRequest =
 | 
				
			||||||
        log.info("********************************************************************************");
 | 
					                (request instanceof ContentCachingRequestWrapper)
 | 
				
			||||||
        log.info("* HTTP REQUEST START");
 | 
					                        ? (ContentCachingRequestWrapper) request
 | 
				
			||||||
        log.info("* Method: {} | URI: {}", request.getMethod(), request.getRequestURI());
 | 
					                        : new ContentCachingRequestWrapper(request);
 | 
				
			||||||
        log.info("* Headers: {}", getRequestHeaders(request));
 | 
					 | 
				
			||||||
        log.info("* Body: {}", getRequestBody(request));
 | 
					 | 
				
			||||||
        log.info("********************************************************************************");
 | 
					 | 
				
			||||||
 | 
					
 | 
				
			||||||
        ContentCachingRequestWrapper wrappedRequest = new ContentCachingRequestWrapper(request);
 | 
					        ContentCachingResponseWrapper wrappedResponse =
 | 
				
			||||||
        ContentCachingResponseWrapper wrappedResponse = new ContentCachingResponseWrapper(response);
 | 
					                (response instanceof ContentCachingResponseWrapper)
 | 
				
			||||||
 | 
					                        ? (ContentCachingResponseWrapper) response
 | 
				
			||||||
 | 
					                        : new ContentCachingResponseWrapper(response);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        log.info("********************************************************************************");
 | 
				
			||||||
 | 
					        log.info("* [START] HTTP LOGGING");
 | 
				
			||||||
 | 
					        log.info("* Method: {} | URI: {}", wrappedRequest.getMethod(), wrappedRequest.getRequestURI());
 | 
				
			||||||
 | 
					        log.info("* Headers: {}", getRequestHeaders(wrappedRequest));
 | 
				
			||||||
 | 
					        log.info("********************************************************************************");
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        try {
 | 
					        try {
 | 
				
			||||||
            filterChain.doFilter(wrappedRequest, wrappedResponse);
 | 
					            filterChain.doFilter(wrappedRequest, wrappedResponse);
 | 
				
			||||||
        } finally {
 | 
					        } finally {
 | 
				
			||||||
            long duration = System.currentTimeMillis() - startTime;
 | 
					            long duration = System.currentTimeMillis() - startTime;
 | 
				
			||||||
 | 
					            String reqBody = extractRequestBody(wrappedRequest);
 | 
				
			||||||
 | 
					            String resBody = extractResponseBody(wrappedResponse);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
            // 응답 정보 로깅
 | 
					 | 
				
			||||||
            log.info("********************************************************************************");
 | 
					            log.info("********************************************************************************");
 | 
				
			||||||
            log.info("* HTTP RESPONSE END");
 | 
					            log.info("* [END] HTTP LOGGING");
 | 
				
			||||||
            log.info("* Method: {} | URI: {}", request.getMethod(), request.getRequestURI());
 | 
					            log.info("* Method: {} | URI: {}", wrappedRequest.getMethod(), wrappedRequest.getRequestURI());
 | 
				
			||||||
            log.info("* Status: {} | Duration: {}ms", wrappedResponse.getStatus(), duration);
 | 
					            log.info("* Status: {} | Duration: {}ms", wrappedResponse.getStatus(), duration);
 | 
				
			||||||
            log.info("* Headers: {}", getResponseHeaders(wrappedResponse));
 | 
					            log.info("* Req Body: {}", maskSensitiveData(reqBody));
 | 
				
			||||||
            log.info("* Body: {}", getResponseBody(wrappedResponse));
 | 
					            log.info("* Res Headers: {}", getResponseHeaders(wrappedResponse));
 | 
				
			||||||
 | 
					            log.info("* Res Body: {}", maskSensitiveData(resBody));
 | 
				
			||||||
            log.info("********************************************************************************");
 | 
					            log.info("********************************************************************************");
 | 
				
			||||||
 | 
					
 | 
				
			||||||
            wrappedResponse.copyBodyToResponse();
 | 
					            wrappedResponse.copyBodyToResponse();
 | 
				
			||||||
        }
 | 
					        }
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    // 민감 정보 마스킹 메서드
 | 
					    private String extractRequestBody(ContentCachingRequestWrapper wrapper) {
 | 
				
			||||||
 | 
					        // multipart/form-data 등은 보통 로깅 제외
 | 
				
			||||||
 | 
					        String ct = Optional.ofNullable(wrapper.getContentType()).orElse("");
 | 
				
			||||||
 | 
					        if (ct.toLowerCase(Locale.ROOT).startsWith("multipart/")) {
 | 
				
			||||||
 | 
					            return "(multipart skipped)";
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        byte[] buf = wrapper.getContentAsByteArray();
 | 
				
			||||||
 | 
					        if (buf == null || buf.length == 0) return "";
 | 
				
			||||||
 | 
					        Charset cs = resolveCharset(wrapper.getCharacterEncoding());
 | 
				
			||||||
 | 
					        String s = new String(buf, cs);
 | 
				
			||||||
 | 
					        return truncate(s, MAX_LOG_BODY);
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    private String extractResponseBody(ContentCachingResponseWrapper wrapper) {
 | 
				
			||||||
 | 
					        byte[] buf = wrapper.getContentAsByteArray();
 | 
				
			||||||
 | 
					        if (buf == null || buf.length == 0) return "";
 | 
				
			||||||
 | 
					        
 | 
				
			||||||
 | 
					        // UTF-8로 고정하여 한글 깨짐 방지
 | 
				
			||||||
 | 
					        String s = new String(buf, StandardCharsets.UTF_8);
 | 
				
			||||||
 | 
					        return truncate(s, MAX_LOG_BODY);
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    private Charset resolveCharset(String enc) {
 | 
				
			||||||
 | 
					        try {
 | 
				
			||||||
 | 
					            return (enc != null) ? Charset.forName(enc) : StandardCharsets.UTF_8;
 | 
				
			||||||
 | 
					        } catch (Exception e) {
 | 
				
			||||||
 | 
					            return StandardCharsets.UTF_8;
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    private String truncate(String s, int max) {
 | 
				
			||||||
 | 
					        if (s == null) return "N/A";
 | 
				
			||||||
 | 
					        if (s.length() <= max) return s;
 | 
				
			||||||
 | 
					        return s.substring(0, max) + "...(truncated)";
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    // 민감 정보 마스킹 (간단/범용)
 | 
				
			||||||
    private String maskSensitiveData(String body) {
 | 
					    private String maskSensitiveData(String body) {
 | 
				
			||||||
        if (body == null || body.isEmpty()) return "N/A";
 | 
					        if (body == null || body.isEmpty()) return "N/A";
 | 
				
			||||||
        
 | 
					        // JSON 키 기준 토큰/비번류 마스킹 (대소문자 무시)
 | 
				
			||||||
        // 비밀번호, 토큰 등 민감 정보 마스킹
 | 
					        String masked = body
 | 
				
			||||||
        return body.replaceAll("\"password\"\\s*:\\s*\"[^\"]*\"", "\"password\":\"***\"")
 | 
					                .replaceAll("(?i)(\"password\"\\s*:\\s*\")([^\"]+)(\")", "$1***$3")
 | 
				
			||||||
                  .replaceAll("\"token\"\\s*:\\s*\"[^\"]*\"", "\"token\":\"***\"")
 | 
					                .replaceAll("(?i)(\"AccessToken\"\\s*:\\s*\")([^\"]+)(\")", "$1***$3")
 | 
				
			||||||
                  .replaceAll("\"refreshToken\"\\s*:\\s*\"[^\"]*\"", "\"refreshToken\":\"***\"");
 | 
					                .replaceAll("(?i)(\"RefreshToken\"\\s*:\\s*\")([^\"]+)(\")", "$1***$3")
 | 
				
			||||||
 | 
					                .replaceAll("(?i)(\"authorization\"\\s*:\\s*\")([^\"]+)(\")", "$1***$3");
 | 
				
			||||||
 | 
					        return masked;
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    private String getRequestHeaders(HttpServletRequest request) {
 | 
					    private String getRequestHeaders(HttpServletRequest request) {
 | 
				
			||||||
        // 주요 헤더만 (Content-Type, Authorization, User-Agent)
 | 
					        Map<String, String> headers = new LinkedHashMap<>();
 | 
				
			||||||
        Map<String, String> headers = new HashMap<>();
 | 
					        // 필요한 헤더만 추리거나, 전부 찍고 민감한 건 마스킹
 | 
				
			||||||
        headers.put("Content-Type", request.getContentType());
 | 
					        Collections.list(request.getHeaderNames()).forEach(name -> {
 | 
				
			||||||
        headers.put("User-Agent", request.getHeader("User-Agent"));
 | 
					            String value = request.getHeader(name);
 | 
				
			||||||
        
 | 
					            if ("authorization".equalsIgnoreCase(name) && value != null) {
 | 
				
			||||||
        // Authorization 헤더는 마스킹
 | 
					                headers.put(name, value.startsWith("Bearer ") ? "Bearer ***" : "***");
 | 
				
			||||||
        String auth = request.getHeader("Authorization");
 | 
					            } else {
 | 
				
			||||||
        if (auth != null) {
 | 
					                headers.put(name, value);
 | 
				
			||||||
            headers.put("Authorization", auth.startsWith("Bearer ") ? "Bearer ***" : "***");
 | 
					 | 
				
			||||||
            }
 | 
					            }
 | 
				
			||||||
        
 | 
					        });
 | 
				
			||||||
        return headers.toString();
 | 
					        return headers.toString();
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    private String getRequestBody(HttpServletRequest request) {
 | 
					 | 
				
			||||||
        try {
 | 
					 | 
				
			||||||
            ContentCachingRequestWrapper wrapper = (ContentCachingRequestWrapper) request;
 | 
					 | 
				
			||||||
            String body = new String(wrapper.getContentAsByteArray());
 | 
					 | 
				
			||||||
            return maskSensitiveData(body);
 | 
					 | 
				
			||||||
        } catch (Exception e) {
 | 
					 | 
				
			||||||
            return "N/A";
 | 
					 | 
				
			||||||
        }
 | 
					 | 
				
			||||||
    }
 | 
					 | 
				
			||||||
    
 | 
					 | 
				
			||||||
    private String getResponseHeaders(HttpServletResponse response) {
 | 
					    private String getResponseHeaders(HttpServletResponse response) {
 | 
				
			||||||
        // 응답 헤더 정보
 | 
					        Map<String, String> headers = new LinkedHashMap<>();
 | 
				
			||||||
        return "Content-Type: " + response.getContentType();
 | 
					        for (String name : response.getHeaderNames()) {
 | 
				
			||||||
 | 
					            if ("authorization".equalsIgnoreCase(name)) {
 | 
				
			||||||
 | 
					                headers.put(name, "***");
 | 
				
			||||||
 | 
					            } else {
 | 
				
			||||||
 | 
					                headers.put(name, String.join(",", response.getHeaders(name)));
 | 
				
			||||||
            }
 | 
					            }
 | 
				
			||||||
    
 | 
					 | 
				
			||||||
    private String getResponseBody(HttpServletResponse response) {
 | 
					 | 
				
			||||||
        try {
 | 
					 | 
				
			||||||
            ContentCachingResponseWrapper wrapper = (ContentCachingResponseWrapper) response;
 | 
					 | 
				
			||||||
            String body = new String(wrapper.getContentAsByteArray());
 | 
					 | 
				
			||||||
            return maskSensitiveData(body);
 | 
					 | 
				
			||||||
        } catch (Exception e) {
 | 
					 | 
				
			||||||
            return "N/A";
 | 
					 | 
				
			||||||
        }
 | 
					        }
 | 
				
			||||||
 | 
					        if (response.getContentType() != null) {
 | 
				
			||||||
 | 
					            headers.putIfAbsent("Content-Type", response.getContentType());
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					        return headers.toString();
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    @Override
 | 
					    @Override
 | 
				
			||||||
    protected boolean shouldNotFilter(HttpServletRequest request) {
 | 
					    protected boolean shouldNotFilter(HttpServletRequest request) {
 | 
				
			||||||
        String path = request.getRequestURI();
 | 
					        String path = request.getRequestURI();
 | 
				
			||||||
        return path.startsWith("/static/") || 
 | 
					        return path.startsWith("/static/")
 | 
				
			||||||
               path.startsWith("/css/") || 
 | 
					                || path.startsWith("/css/")
 | 
				
			||||||
               path.startsWith("/js/") || 
 | 
					                || path.startsWith("/js/")
 | 
				
			||||||
               path.startsWith("/images/") ||
 | 
					                || path.startsWith("/images/")
 | 
				
			||||||
               path.equals("/actuator/health") ||
 | 
					                || path.equals("/favicon.ico")
 | 
				
			||||||
               path.equals("/favicon.ico");
 | 
					                || path.startsWith("/actuator/health");
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 
 | 
				
			|||||||
		Reference in New Issue
	
	Block a user