Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 2 additions & 2 deletions .github/workflows/CD.yml
Original file line number Diff line number Diff line change
Expand Up @@ -138,7 +138,7 @@ jobs:
username: ${{ secrets.EC2_USER }}
key: ${{ secrets.EC2_KEY }}
script: |
sleep 5;
sleep 10;
echo "Health Check 시작...🩺"

for i in {1..10}
Expand All @@ -150,7 +150,7 @@ jobs:
fi

echo "⏳ 서버 기동 대기중... ($i/10)"
sleep 5
sleep 10
done

echo "❌ Health Check 실패"
Expand Down
117 changes: 117 additions & 0 deletions src/main/java/org/creditto/authserver/global/aop/LogAspect.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,117 @@
package org.creditto.authserver.global.aop;

import com.fasterxml.jackson.core.JsonProcessingException;
import com.fasterxml.jackson.databind.ObjectMapper;
import jakarta.servlet.http.HttpServletRequest;
import jakarta.servlet.http.HttpServletResponse;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.aspectj.lang.JoinPoint;
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.Around;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Before;
import org.aspectj.lang.annotation.Pointcut;
import org.creditto.authserver.global.response.BaseResponse;
import org.creditto.authserver.global.util.MaskingUtil;
import org.springframework.http.ResponseEntity;
import org.springframework.stereotype.Component;
import org.springframework.validation.BindingResult;
import org.springframework.web.context.request.RequestAttributes;
import org.springframework.web.context.request.RequestContextHolder;
import org.springframework.web.context.request.ServletRequestAttributes;

import java.util.Arrays;

@Aspect
@Slf4j
@Component
@RequiredArgsConstructor
public class LogAspect {

private final ObjectMapper objectMapper;

@Pointcut("execution(* org.creditto.authserver..controller..*(..))")
private void onRequest() {
}

@Pointcut("execution(* org.creditto.authserver..service..*(..))")
private void onService() {
}

@Around("onRequest()")
public Object logRequestAndResponse(final ProceedingJoinPoint joinPoint) throws Throwable {
final ServletRequestAttributes requestAttributes = getRequestAttributes();
final HttpServletRequest request = requestAttributes != null ? requestAttributes.getRequest() : null;

final String className = joinPoint.getTarget().getClass().getSimpleName();
final String requestUri = request != null ? request.getRequestURI() : joinPoint.getSignature().toShortString();
final String method = request != null ? request.getMethod() : "N/A";
final String requestIp = request != null ? request.getRemoteAddr() : "N/A";

log.info("[{}] Request IP : {} | Request URI : {} | Request Method : {}", className, requestIp, requestUri, method);

final Object[] sanitizedArgs = Arrays.stream(joinPoint.getArgs())
.filter(arg -> !(arg instanceof HttpServletRequest))
.filter(arg -> !(arg instanceof HttpServletResponse))
.filter(arg -> !(arg instanceof BindingResult))
.toArray();

if (sanitizedArgs.length > 0) {
final String argsAsString = MaskingUtil.maskSensitiveData(serialize(sanitizedArgs));
log.info("[{}] {} {} - RequestBody: {}", className, method, requestUri, argsAsString);
}

try {
final Object result = joinPoint.proceed();
Object responseBody = result;
if (result instanceof ResponseEntity<?> responseEntity) {
responseBody = responseEntity.getBody();
}
final Object dataForLog = responseBody instanceof BaseResponse<?> baseResponse
? baseResponse.getData()
: responseBody;
final String responseAsString = MaskingUtil.maskSensitiveData(serialize(dataForLog));
log.info("[{}] {} {} - ResponseData: {}", className, method, requestUri, responseAsString);
return result;
} catch (Throwable e) {
log.error("[{}] {} {} - Exception occurred : {}", className, method, requestUri, e.getMessage());
throw e;
}
}

@Before("onService()")
public void beforeServiceLog(final JoinPoint joinPoint) {
final String className = joinPoint.getTarget().getClass().getSimpleName();
final String methodName = joinPoint.getSignature().getName();
final Object[] args = joinPoint.getArgs();

log.info("[{}] {}() called", className, methodName);

if (args.length > 0) {
final String params = MaskingUtil.maskSensitiveData(serialize(args));
log.debug("[{}] Parameters: {}", className, params);
}
}

private ServletRequestAttributes getRequestAttributes() {
final RequestAttributes requestAttributes = RequestContextHolder.getRequestAttributes();
if (requestAttributes instanceof ServletRequestAttributes servletRequestAttributes) {
return servletRequestAttributes;
}
return null;
}

private String serialize(final Object value) {
if (value == null) {
return "null";
}

try {
return objectMapper.writeValueAsString(value);
} catch (JsonProcessingException e) {
log.debug("직렬화 실패: {}", e.getMessage());
return String.valueOf(value);
}
}
}
96 changes: 96 additions & 0 deletions src/main/java/org/creditto/authserver/global/util/MaskingUtil.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,96 @@
package org.creditto.authserver.global.util;

import java.util.List;
import java.util.regex.Matcher;
import java.util.regex.Pattern;

public final class MaskingUtil {

private static final List<String> SENSITIVE_KEYS = List.of(
"password",
"secret",
"token",
"key",
"certificateNumber"
);

private static final Pattern JSON_KEY_VALUE_PATTERN = Pattern.compile(
"(\"(?:" + String.join("|", SENSITIVE_KEYS) + ")\"\\s*:\\s*\")(.*?)(\")",
Pattern.CASE_INSENSITIVE
);

private static final Pattern KEY_VALUE_PATTERN = Pattern.compile(
"(?i)((?:password|secret|token|key[number]?|certificateNumber)\\s*[=:]\\s*)([^,\\s}\\]]+)"
);

private static final Pattern PHONE_PATTERN = Pattern.compile("(?<!\\d)(\\d{3})(\\d{3,4})(\\d{4})(?!\\d)");

private MaskingUtil() {
}

public static String maskSensitiveData(final String raw) {
if (raw == null || raw.isBlank()) {
return raw;
}

String masked = maskJsonSensitiveValues(raw);
masked = maskKeyValuePairs(masked);
masked = maskPhoneNumbers(masked);

return masked;
}

private static String maskJsonSensitiveValues(final String input) {
final Matcher matcher = JSON_KEY_VALUE_PATTERN.matcher(input);
final StringBuilder buffer = new StringBuilder();

while (matcher.find()) {
final String maskedValue = maskValue(matcher.group(2));
matcher.appendReplacement(buffer,
Matcher.quoteReplacement(matcher.group(1) + maskedValue + matcher.group(3)));
}
matcher.appendTail(buffer);
return buffer.toString();
}

private static String maskKeyValuePairs(final String input) {
final Matcher matcher = KEY_VALUE_PATTERN.matcher(input);
final StringBuilder buffer = new StringBuilder();

while (matcher.find()) {
final String maskedValue = maskValue(matcher.group(2));
matcher.appendReplacement(buffer,
Matcher.quoteReplacement(matcher.group(1) + maskedValue));
}
matcher.appendTail(buffer);
return buffer.toString();
}

private static String maskPhoneNumbers(final String input) {
final Matcher matcher = PHONE_PATTERN.matcher(input);
final StringBuilder buffer = new StringBuilder();

while (matcher.find()) {
final String masked = matcher.group(1) + "****" + matcher.group(3);
matcher.appendReplacement(buffer, masked);
}
matcher.appendTail(buffer);
return buffer.toString();
}

private static String maskValue(final String value) {
if (value == null || value.isBlank()) {
return value;
}

final String trimmed = value.trim();
if (trimmed.length() <= 2) {
return "*".repeat(trimmed.length());
}

final int length = trimmed.length();
return trimmed.charAt(0) +
"*".repeat(length - 2) +
trimmed.charAt(length - 1);
}
}