Spring Boot · 2026-04-23

Spring Boot API 로그와 PII 마스킹

Spring Boot 애플리케이션에서 API 요청·응답을 로깅하면서 개인정보(PII) 필드를 안전하게 마스킹하고 운영 환경에 맞춰 로그를 관리하는 설정과 구현 방법 설명

작성일 : 2026-04-23 ㆍ 작성자 : 관리자
post
목차

개요

서비스 운영 중 API 요청과 응답을 기록하면 문제 분석과 모니터링에 큰 도움이 된다. 다만 로그에 개인정보(PII)가 포함되면 규제와 보안 리스크가 발생한다. 이 글에서는 spring boot request response logging 관점에서 안전하게 로그를 남기고, pii 마스킹 spring boot 방식으로 민감정보를 숨기는 구현 흐름을 단계별로 설명한다.

왜 요청/응답 로깅과 PII 마스킹이 필요한가

로그는 디버깅과 감사에 필수다. 그러나 이메일, 전화번호, 주민등록번호 같은 민감정보가 노출되면 법적·평판 리스크가 생긴다. 따라서 api 로그 민감정보 마스킹을 적용해 민감 필드를 처리해야 한다. 핵심 원칙은 다음과 같다.

  • 필요한 최소한만 기록한다.
  • 민감 필드는 마스킹하거나 생략한다.
  • 운영과 개발 환경에서 로그 레벨과 보존 정책을 분리한다.

구현 전략 개요

주요 방법은 필터(Filter)나 인터셉터(HandlerInterceptor)를 사용해 요청과 응답을 가로채는 것이다. 요청 바디와 응답 바디를 읽어 로그로 남기기 위해서는 바디를 캐시하는 래퍼가 필요하다. Spring에서 제공하는 ContentCachingRequestWrapper와 ContentCachingResponseWrapper를 활용하면 비교적 간단하게 구현할 수 있다.

핵심 흐름

  • 요청 도착: 원본을 ContentCachingRequestWrapper로 감싼다.
  • 응답 처리: ContentCachingResponseWrapper로 감싼 뒤 체인 실행.
  • 체인 완료 후: 캐시된 바디를 읽어 마스킹 로직 적용.
  • 마스킹된 문자열을 로그에 남기고, 응답은 원래대로 클라이언트에게 전송.

샘플 필터 구현

아래 코드는 OncePerRequestFilter를 사용한 예시다. 바디를 읽고 마스킹한 뒤 로그를 남긴다. 실제 환경에서는 예외 처리와 대용량 바디 처리(파일 업로드 등)를 고려해야 한다.

package com.example.logging;

import org.springframework.web.filter.OncePerRequestFilter;
import org.springframework.web.util.ContentCachingRequestWrapper;
import org.springframework.web.util.ContentCachingResponseWrapper;
import javax.servlet.FilterChain;
import javax.servlet.ServletException;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;

public class RequestResponseLoggingFilter extends OncePerRequestFilter {

    @Override
    protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain filterChain)
            throws ServletException, IOException {
        ContentCachingRequestWrapper wrappedRequest = new ContentCachingRequestWrapper(request);
        ContentCachingResponseWrapper wrappedResponse = new ContentCachingResponseWrapper(response);

        long start = System.currentTimeMillis();
        filterChain.doFilter(wrappedRequest, wrappedResponse);
        long duration = System.currentTimeMillis() - start;

        String requestBody = getRequestBody(wrappedRequest);
        String responseBody = getResponseBody(wrappedResponse);

        // 마스킹 적용
        String maskedRequest = MaskUtils.maskPII(requestBody);
        String maskedResponse = MaskUtils.maskPII(responseBody);

        // 로그 출력 (예: Logger 사용)
        logger.info("[API] {} {}ms request={} response={}", request.getRequestURI(), duration, maskedRequest, maskedResponse);

        // 응답을 클라이언트로 복원
        wrappedResponse.copyBodyToResponse();
    }

    private String getRequestBody(ContentCachingRequestWrapper request) throws IOException {
        byte[] buf = request.getContentAsByteArray();
        if (buf.length == 0) return "";
        return new String(buf, 0, buf.length, request.getCharacterEncoding());
    }

    private String getResponseBody(ContentCachingResponseWrapper response) throws IOException {
        byte[] buf = response.getContentAsByteArray();
        if (buf.length == 0) return "";
        return new String(buf, 0, buf.length, response.getCharacterEncoding());
    }
}

마스킹 유틸리티 예시

마스킹은 필드 기반 또는 패턴 기반으로 수행할 수 있다. JSON 파싱을 통해 특정 키를 마스킹하면 더 안전하다. 다음은 간단한 패턴 기반 마스킹 예시다.

package com.example.logging;

import java.util.regex.Pattern;

public class MaskUtils {
    private static final Pattern EMAIL = Pattern.compile("([A-Za-z0-9._%+-]+)@([A-Za-z0-9.-]+\\.[A-Za-z]{2,})");
    private static final Pattern PHONE = Pattern.compile("(\\d{2,3})[- ]?(\\d{3,4})[- ]?(\\d{4})");
    private static final Pattern SSN = Pattern.compile("\\b(\\d{6})[- ]?(\\d{7})\\b");

    public static String maskPII(String text) {
        if (text == null || text.isEmpty()) return text;

        String result = EMAIL.matcher(text).replaceAll("$1@****");
        result = PHONE.matcher(result).replaceAll("$1-****-$3");
        result = SSN.matcher(result).replaceAll("******-*******");
        return result;
    }
}

운영 고려사항

실무에서 주의할 점은 다음과 같다.

  • 로그 레벨과 저장 기간을 환경별로 분리한다. 개발에서는 상세 로그, 운영에서는 제한된 로그를 권장한다.
  • 바이너리나 대용량 페이로드는 로깅에서 제외한다.
  • 민감 필드 목록을 구성 파일로 관리하면 필드 추가·삭제가 쉬워진다.
  • 성능 영향: 바디 캐싱은 메모리 사용을 늘리므로 요청 크기 제한을 설정한다.

설정 예시

application.yml에 로그 관련 설정과 마스킹 대상 키를 두어 관리하면 운영 변경이 편리하다.

logging:
  level:
    com.example: INFO
app:
  logging:
    mask-fields:
      - password
      - ssn

마무리

spring boot request response logging은 문제 해결에 유용하지만, pii 마스킹 spring boot 전략을 병행해야 안전하다. api 로그 민감정보 마스킹은 규제 준수와 사용자 신뢰를 지키는 중요한 작업이다. 구현 시에는 패턴 기반과 필드 기반을 조합하고, 환경별 정책과 성능을 함께 고려하는 것이 바람직하다.

spring boot request response logging pii 마스킹 spring boot api 로그 민감정보 마스킹 로그 마스킹 개인정보 보호 Spring Boot RequestResponseLogging 로그 보안