[HTTP 로깅 개선] HttpLoggingFilter에서 password 파라미터 및 cookie, Set-Cookie 헤더의 민감 정보를 마스킹하는 기능을 추가하여 보안성을 강화

This commit is contained in:
2025-09-01 16:31:51 +09:00
parent 35cf8203ba
commit 24934e4a54

View File

@@ -112,6 +112,8 @@ public class HttpLoggingFilter extends OncePerRequestFilter {
.replaceAll("(?i)(\"AccessToken\"\\s*:\\s*\")([^\"]+)(\")", "$1***$3") .replaceAll("(?i)(\"AccessToken\"\\s*:\\s*\")([^\"]+)(\")", "$1***$3")
.replaceAll("(?i)(\"RefreshToken\"\\s*:\\s*\")([^\"]+)(\")", "$1***$3") .replaceAll("(?i)(\"RefreshToken\"\\s*:\\s*\")([^\"]+)(\")", "$1***$3")
.replaceAll("(?i)(\"authorization\"\\s*:\\s*\")([^\"]+)(\")", "$1***$3"); .replaceAll("(?i)(\"authorization\"\\s*:\\s*\")([^\"]+)(\")", "$1***$3");
// x-www-form-urlencoded 형태의 password 파라미터 마스킹
masked = masked.replaceAll("(?i)(^|[&])password=([^&]*)", "$1password=***");
return masked; return masked;
} }
@@ -120,11 +122,18 @@ public class HttpLoggingFilter extends OncePerRequestFilter {
// 필요한 헤더만 추리거나, 전부 찍고 민감한 건 마스킹 // 필요한 헤더만 추리거나, 전부 찍고 민감한 건 마스킹
Collections.list(request.getHeaderNames()).forEach(name -> { Collections.list(request.getHeaderNames()).forEach(name -> {
String value = request.getHeader(name); String value = request.getHeader(name);
if ("authorization".equalsIgnoreCase(name) && value != null) { if (value == null) {
headers.put(name, value.startsWith("Bearer ") ? "Bearer ***" : "***"); headers.put(name, null);
} else { return;
headers.put(name, value);
} }
if ("cookie".equalsIgnoreCase(name)) {
headers.put(name, maskCookieHeader(value));
return;
}
// 기타 헤더는 그대로 기록 (요구사항에 따라 최소 마스킹 적용)
headers.put(name, value);
}); });
return headers.toString(); return headers.toString();
} }
@@ -132,11 +141,17 @@ public class HttpLoggingFilter extends OncePerRequestFilter {
private String getResponseHeaders(HttpServletResponse response) { private String getResponseHeaders(HttpServletResponse response) {
Map<String, String> headers = new LinkedHashMap<>(); Map<String, String> headers = new LinkedHashMap<>();
for (String name : response.getHeaderNames()) { for (String name : response.getHeaderNames()) {
if ("authorization".equalsIgnoreCase(name)) { if ("set-cookie".equalsIgnoreCase(name)) {
headers.put(name, "***"); List<String> values = new ArrayList<>(response.getHeaders(name));
} else { List<String> masked = new ArrayList<>();
headers.put(name, String.join(",", response.getHeaders(name))); for (String v : values) {
masked.add(maskSetCookieHeader(v));
}
headers.put(name, String.join(",", masked));
continue;
} }
headers.put(name, String.join(",", response.getHeaders(name)));
} }
if (response.getContentType() != null) { if (response.getContentType() != null) {
headers.putIfAbsent("Content-Type", response.getContentType()); headers.putIfAbsent("Content-Type", response.getContentType());
@@ -144,6 +159,24 @@ public class HttpLoggingFilter extends OncePerRequestFilter {
return headers.toString(); return headers.toString();
} }
// Cookie 헤더: "a=1; AccessToken=xxx; RefreshToken=yyy" 형태 중 AccessToken/RefreshToken만 마스킹
private String maskCookieHeader(String cookieHeader) {
if (cookieHeader == null || cookieHeader.isEmpty()) return cookieHeader;
String masked = cookieHeader
.replaceAll("(?i)(^|;\\s*)(AccessToken)=([^;]*)", "$1$2=***")
.replaceAll("(?i)(^|;\\s*)(RefreshToken)=([^;]*)", "$1$2=***");
return masked;
}
// Set-Cookie: "AccessToken=xxx; Path=/; HttpOnly; ..." 형태 중 AccessToken/RefreshToken만 마스킹
private String maskSetCookieHeader(String setCookie) {
if (setCookie == null || setCookie.isEmpty()) return setCookie;
String masked = setCookie
.replaceAll("(?i)^(AccessToken)=([^;]*)", "$1=***")
.replaceAll("(?i)^(RefreshToken)=([^;]*)", "$1=***");
return masked;
}
@Override @Override
protected boolean shouldNotFilter(HttpServletRequest request) { protected boolean shouldNotFilter(HttpServletRequest request) {
String path = request.getRequestURI(); String path = request.getRequestURI();