[CHORE] 요청·응답 바디 로깅 및 민감정보 마스킹 적용 - #606
Merged
Merged
Conversation
개인정보처리방침상 수집 항목과 인증 크리덴셜을 로그에서 가리기 위한 @sensitivedata 어노테이션과 마스킹 정책을 추가한다. 마스킹 직렬화기는 SensitiveDataMasker가 직접 만든 전용 ObjectMapper에만 등록한다. ObjectMapper 타입 빈을 노출하면 Spring Boot 기본 ObjectMapper 자동 구성을 밀어내 실제 API 응답 직렬화까지 바뀌기 때문이다. 어노테이션 누락에 대비해 필드명 기반 안전망을 함께 두되, 닉네임처럼 다른 도메인 용어와 겹치는 이름은 작품명까지 가려버리므로 제외한다. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
개인정보처리방침 제1조 수집 항목을 기준으로 전수조사한 25개 DTO에 @sensitivedata를 부착한다. 이메일, 닉네임·이름, 성별·출생연도와 함께 유출 시 계정 탈취로 이어지는 토큰·인증코드·기기 식별자를 포함한다. 방침 제1조 5항에 따라 인종·사상·건강 등 인권침해 우려 정보는 수집하지 않으며 코드상에도 해당 필드가 없음을 확인했다. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
RequestBodyLoggingAdvice가 평문 대신 마스킹된 본문을 기록하도록 바꾸고, 로그 한 건이 지나치게 커지지 않도록 크기 상한과 truncated 플래그를 남긴다. 어노테이션 기반 마스킹은 record·POJO 프로퍼티에만 걸려 Map 본문이 그대로 노출되던 문제가 있어, 필드명 안전망은 직렬화 결과 트리를 순회하며 적용한다. 이미 가려진 값은 길이 정보가 어긋나므로 다시 마스킹하지 않는다. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
응답 본문을 값 없이 타입·필드명·길이로만 기록한다. 값을 직렬화하지 않아 개인정보가 로그로 흘러갈 수 없고, 목록은 원소를 순회하지 않아 크기와 무관하게 비용이 일정하다. 실측상 피드 20건 응답 기준 전량 직렬화는 10.122us가 드는 반면 구조 요약은 0.074us로, 목록 API에서 로그량과 지연을 모두 줄인다. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
@WebMvcTest 슬라이스 테스트는 @ControllerAdvice만 등록하고 일반 컴포넌트는 제외한다. 마스커와 요약기를 빈으로 두면 어드바이스가 의존성을 찾지 못해 컨트롤러 테스트 컨텍스트가 모두 깨지므로, 상태 없는 두 협력 객체를 빈에서 제외하고 어드바이스가 직접 생성한다. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
동기 appender는 요청 스레드가 직접 인코딩·write·flush를 수행해 16스레드 부하 실측에서 요청당 평균 538us, p99 7,209us가 로깅에 소요됐다. 로그 전송이 정체되면 파이프 버퍼 포화로 요청 스레드가 최대 501ms까지 멈추기도 했다. AsyncAppender를 적용해 평균 37us, p99 8.8us로 낮추고, 큐가 가득 차면 로그를 버리되 요청 처리는 막지 않도록 neverBlock을 켠다. INFO 로그까지 임의로 버리지 않도록 discardingThreshold는 0으로 둔다. 컨테이너 stdout은 Docker awslogs 드라이버가 수집하므로 파일 appender는 중복이라 제거한다. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Related Issue
Key Changes
요청·응답 본문을 로그에 남기되, 개인정보처리방침 제1조 수집 항목에 해당하는 민감정보는 마스킹 처리합니다. 기존
RequestBodyLoggingAdvice가 요청 본문을 평문으로 기록해refreshToken,authorizationCode,idToken,fcmToken, 이메일 등이 그대로 남던 문제를 해결합니다.0. 로그 포맷 JSON 구조화 —
1139596dev에 아직 반영되지 않은 선행 커밋으로, 이 PR에 함께 포함됩니다. 요청·응답 로그를 CloudWatch에서 검색 가능한 JSON 필드로 구조화하고
traceId로 묶습니다.1. 마스킹 기반 구성 —
3f03da3@SensitiveData어노테이션과 마스킹 정책(EMAIL,NAME,CREDENTIAL,FULL)을 정의했습니다.마스킹 직렬화기는
SensitiveDataMasker가 직접 만든 로그 전용 ObjectMapper에만 등록합니다.ObjectMapper타입 빈을 노출하면 Spring Boot 기본 ObjectMapper 자동 구성을 밀어내 실제 API 응답 직렬화까지 바뀌기 때문입니다. 실제 응답 본문은 변경되지 않습니다.어노테이션 누락에 대비해 필드명 기반 안전망(
SensitiveFieldNames)을 함께 뒀습니다. 닉네임처럼 다른 도메인 용어와 겹치는 이름은 작품명까지 가려버리므로 안전망에서 제외하고 어노테이션으로만 처리합니다.2. DTO 어노테이션 부착 —
efa669f전수조사한 25개 DTO에 어노테이션을 부착했습니다.
EMAILUserInfoGetResponse,AppleAuthResult,KakaoUserInfo.KakaoAccountNAMERegisterUserInfoRequest,UpdateMyProfileRequest,MyProfileResponse,ProfileGetResponse,UserBasicInfo,UserIdAndNicknameResponse,BlockGetResponse,PopularNovelGetResponse,CollectionOwnerGetResponse,CommentGetResponse,FeedGetResponse,FeedInfo,KakaoUserInfo.PropertiesFULLEditMyInfoRequest,RegisterUserInfoRequest,UserInfoGetResponse,UserIdAndNicknameResponseCREDENTIALAuthResponse,ReissueRequest,ReissueResponse,LoginResponse,LogoutRequest,AppleLoginRequest,AppleIdUpdateRequest,AppleTokenResponse,AppleAuthResultCREDENTIALFCMTokenRequest,LogoutRequest토큰·인증코드·기기 식별자는 개인정보는 아니지만 유출 시 계정 탈취로 직결되어 함께 포함했습니다. 방침 제1조 5항에 따라 인종·사상·건강 등 인권침해 우려 정보는 수집하지 않으며, 코드상에도 해당 필드가 없음을 확인했습니다.
3. 요청 본문 마스킹 적용 —
55dbec1RequestBodyLoggingAdvice가 마스킹된 본문을 기록하도록 바꾸고, 로그 한 건이 커지지 않도록 크기 상한(4KB)과truncated플래그를 남깁니다.어노테이션 기반 마스킹은
BeanSerializerModifier가 record·POJO 프로퍼티에만 적용해서Map본문이 그대로 노출되는 문제가 있었습니다. 그래서 필드명 안전망은 직렬화 결과 트리를 순회하며 적용해 Map·중첩 구조까지 덮습니다. 이미 가려진 값은 길이 정보가 어긋나므로 다시 마스킹하지 않습니다.{"event":"API_REQUEST_BODY","traceId":"4dd1b284","userId":"10034","body":{"authorizationCode":"***(len=18)","idToken":"***(len=100)"},"truncated":false} {"event":"API_REQUEST_BODY","traceId":"4dd1b284","userId":"10034","body":{"novelTitle":"전지적 독자 시점","rating":4.5,"email":"re***@we***"},"truncated":false}4. 응답 본문 구조 요약 로깅 —
e0e6053응답은 값을 남기지 않고 타입·필드명·길이만 기록합니다. 값을 직렬화하지 않아 개인정보가 흘러갈 수 없고, 목록은 원소를 순회하지 않아 크기와 무관하게 비용이 일정합니다.
{"event":"API_RESPONSE_BODY","body":{"type":"UserInfoGetResponse","fields":{"email":"String(len=17)","gender":"String(len=4)","birth":"Integer"}}} {"event":"API_RESPONSE_BODY","body":{"type":"List","size":20,"elementType":"FeedGetResponse"}}피드 20건 응답 기준 전량 직렬화는 10.122μs, 구조 요약은 0.074μs로 137배 저렴합니다.
5. 슬라이스 테스트 호환 —
d463787@WebMvcTest는@ControllerAdvice만 등록하고 일반 컴포넌트는 제외합니다. 마스커와 요약기를 빈으로 두면 어드바이스가 의존성을 찾지 못해 컨트롤러 테스트 컨텍스트가 전부 깨졌습니다(148건 실패). 상태 없는 두 협력 객체를 빈에서 제외하고 어드바이스가 직접 생성하도록 했습니다.6. 로그 출력 비동기 전환 —
e4bc2f3기존에는 Console·File 두 appender가 모두 동기이고
immediateFlush=true라 요청 스레드가 직접 인코딩·write·flush를 수행했습니다. 16스레드 부하 실측 결과입니다.부하 증가분은 바디 로깅 추가(+7%)가 아니라 기존 동기 appender 구성에 있었습니다. stdout 소비자(awslogs 드라이버)가 정체된 조건에서는 파이프 버퍼 포화로 요청 스레드가 최대 501ms까지 블로킹됐고, Async 적용 시 37ms로 줄었습니다.
File appender는 Docker가 stdout을 수집하므로 중복이라 제거했습니다.
To Reviewers
doesNotAffectApiResponseSerialization)를 넣어뒀습니다.neverBlock=true는 큐가 가득 차면 로그를 버립니다. 요청 스레드 블로킹보다 낫다고 판단했지만, 이 트레이드오프가 괜찮은지 봐주세요. 관련해서 deploy.sh에--log-opt mode=non-blocking --log-opt max-buffer-size=4m를 함께 넣는 것을 권장합니다. awslogs 드라이버 기본값이 blocking이라, CloudWatch 전송이 밀리면 컨테이너 stdout이 막히고 그 상태에서 로그가 조용히 버려질 수 있습니다.NAME(첫 글자만 노출)으로 잡았습니다. 처리방침상 수집 항목이라 가렸지만 로그에서 사용자 추적이 어려워지는 면이 있어, 정책 조정이 필요하면 알려주세요.테스트
wss-service-support신규 테스트 통과 (마스킹 8, 요청 본문 2, 응답 요약 4, logback 구성 3)11395964에서 동일한 13건이 같은 방식으로 실패함을 확인했습니다 (로컬 Redis 미구동에 따른 컨텍스트 로드 실패 +RecentSearchService:52기존 NPE).References