-
Notifications
You must be signed in to change notification settings - Fork 0
chore/#23: MDC Filter #24
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Merged
Changes from all commits
Commits
Show all changes
12 commits
Select commit
Hold shift + click to select a range
37cc857
chore: logstach encoder 의존성
koosco 51ff9e2
chore: logback 설정
koosco ad7b95c
feat: 요청 MDC Filter
koosco 6ef5513
feat: 인증 MDC Filter
koosco 8868a54
chore: SecurityFilterChain MDC Filter 추가
koosco 38c3509
Merge branch 'staging' into feat/#23
koosco 74aa666
fix: RequestMdcFilter가 가장 먼저 오도록 순서 보장
koosco 8a4f57f
Merge branch 'staging' into feat/#23
koosco ff1e6de
Merge branch 'staging' into feat/#23
koosco f962e69
fix: AuthMdcFilter name 대신 userId 로깅
koosco 4398b97
fix: 로그에 Ip 정보 추가
koosco 5ed8c87
fix: 로그에 Ip 정보 삭제
koosco File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
There are no files selected for viewing
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
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
45 changes: 45 additions & 0 deletions
45
src/main/kotlin/com/yapp2app/auth/infra/security/filter/AuthMdcFilter.kt
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
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,45 @@ | ||
| package com.yapp2app.auth.infra.security.filter | ||
|
|
||
| import com.yapp2app.auth.infra.security.token.UserPrincipal | ||
| import jakarta.servlet.FilterChain | ||
| import jakarta.servlet.http.HttpServletRequest | ||
| import jakarta.servlet.http.HttpServletResponse | ||
| import org.slf4j.MDC | ||
| import org.springframework.security.core.context.SecurityContextHolder | ||
| import org.springframework.web.filter.OncePerRequestFilter | ||
|
|
||
| /** | ||
| * fileName : AuthMdcFilter | ||
| * author : koo | ||
| * date : 2025. 12. 31. | ||
| * description : 인증 후에 실행되어 인증된 사용자 정보를 MDC에 추가하는 필터 | ||
| * SecurityContext에서 인증 정보를 가져와 userId를 MDC에 설정 | ||
| * SecurityFilterChain 내부에서 JwtAuthenticationFilter 다음에 실행 | ||
| */ | ||
| class AuthMdcFilter : OncePerRequestFilter() { | ||
|
|
||
| companion object { | ||
| const val USER_ID = "userId" | ||
| } | ||
|
|
||
| override fun doFilterInternal( | ||
| request: HttpServletRequest, | ||
| response: HttpServletResponse, | ||
| filterChain: FilterChain, | ||
| ) { | ||
| // SecurityContext에서 인증 정보 가져오기 | ||
| val authentication = SecurityContextHolder.getContext().authentication | ||
|
|
||
| // 인증된 사용자인 경우 MDC에 userId 추가 | ||
| if (authentication != null && authentication.isAuthenticated && authentication.principal != "anonymousUser") { | ||
| val userPrincipal = authentication.principal as UserPrincipal | ||
|
|
||
| // DB상 name을 가져오는 코드 (택 1) | ||
| val userId = userPrincipal.id.toString() | ||
|
|
||
| MDC.put(USER_ID, userId) | ||
| } | ||
|
|
||
| filterChain.doFilter(request, response) | ||
| } | ||
| } | ||
86 changes: 86 additions & 0 deletions
86
src/main/kotlin/com/yapp2app/common/filter/RequestMdcFilter.kt
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
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,86 @@ | ||
| package com.yapp2app.common.filter | ||
|
|
||
| import jakarta.servlet.FilterChain | ||
| import jakarta.servlet.http.HttpServletRequest | ||
| import jakarta.servlet.http.HttpServletResponse | ||
| import org.slf4j.MDC | ||
| import org.springframework.web.filter.OncePerRequestFilter | ||
| import java.util.UUID | ||
|
|
||
| /** | ||
| * fileName : RequestMdcFilter | ||
| * author : koo | ||
| * date : 2025. 12. 31. | ||
| * description : 인증 전에 실행되어 요청별 기본 정보를 MDC에 설정하는 필터 | ||
| * Request ID, URI, Method, Client IP 등을 추가 | ||
| */ | ||
| class RequestMdcFilter : OncePerRequestFilter() { | ||
|
|
||
| companion object { | ||
| const val REQUEST_ID = "requestId" | ||
| const val REQUEST_URI = "requestUri" | ||
| const val REQUEST_METHOD = "requestMethod" | ||
| const val CLIENT_IP = "clientIp" | ||
| } | ||
|
|
||
| override fun doFilterInternal( | ||
| request: HttpServletRequest, | ||
| response: HttpServletResponse, | ||
| filterChain: FilterChain, | ||
| ) { | ||
| try { | ||
| // Request ID 설정 (헤더에서 가져오거나 새로 생성) | ||
| val requestId = request.getHeader("X-Request-ID") ?: generateRequestId() | ||
| MDC.put(REQUEST_ID, requestId) | ||
|
|
||
| // 요청 정보 설정 | ||
| MDC.put(REQUEST_URI, request.requestURI) | ||
| MDC.put(REQUEST_METHOD, request.method) | ||
| MDC.put(CLIENT_IP, getClientIp(request)) | ||
|
|
||
| // Response 헤더에 Request ID 추가 | ||
| response.setHeader("X-Request-ID", requestId) | ||
|
|
||
| filterChain.doFilter(request, response) | ||
| } finally { | ||
| // MDC 정리 (메모리 누수 방지) | ||
| // 가장 먼저 실행되고 가장 마지막에 종료되므로 여기서 전체 MDC 정리 | ||
| MDC.clear() | ||
| } | ||
| } | ||
|
|
||
| /** | ||
| * 고유한 Request ID 생성 | ||
| */ | ||
| private fun generateRequestId(): String = UUID.randomUUID().toString().replace("-", "") | ||
|
|
||
| /** | ||
| * 클라이언트 IP 주소 추출 | ||
| * 프록시나 로드밸런서를 거치는 경우 X-Forwarded-For 헤더에서 실제 클라이언트 IP를 추출 | ||
| */ | ||
| private fun getClientIp(request: HttpServletRequest): String { | ||
| val headers = listOf( | ||
| "X-Forwarded-For", | ||
| "Proxy-Client-IP", | ||
| "WL-Proxy-Client-IP", | ||
| "HTTP_X_FORWARDED_FOR", | ||
| "HTTP_X_FORWARDED", | ||
| "HTTP_X_CLUSTER_CLIENT_IP", | ||
| "HTTP_CLIENT_IP", | ||
| "HTTP_FORWARDED_FOR", | ||
| "HTTP_FORWARDED", | ||
| "HTTP_VIA", | ||
| "REMOTE_ADDR", | ||
| ) | ||
|
|
||
| for (header in headers) { | ||
| val ip = request.getHeader(header) | ||
| if (!ip.isNullOrBlank() && ip != "unknown") { | ||
| // X-Forwarded-For는 여러 IP가 콤마로 구분될 수 있음 (첫 번째가 실제 클라이언트 IP) | ||
| return ip.split(",").firstOrNull()?.trim() ?: ip | ||
| } | ||
| } | ||
|
|
||
| return request.remoteAddr ?: "unknown" | ||
| } | ||
| } |
25 changes: 25 additions & 0 deletions
25
src/main/kotlin/com/yapp2app/common/filter/ServletFilterConfig.kt
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
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,25 @@ | ||
| package com.yapp2app.common.filter | ||
|
|
||
| import org.springframework.boot.autoconfigure.security.SecurityProperties | ||
| import org.springframework.boot.web.servlet.FilterRegistrationBean | ||
| import org.springframework.context.annotation.Bean | ||
| import org.springframework.context.annotation.Configuration | ||
|
|
||
| /** | ||
| * fileName : ServletFilterConfig | ||
| * author : koo | ||
| * date : 2026. 1. 8. 오후 3:44 | ||
| * description : | ||
| */ | ||
| @Configuration | ||
| class ServletFilterConfig { | ||
|
|
||
| @Bean | ||
| fun requestMdcFilter(): RequestMdcFilter = RequestMdcFilter() | ||
|
|
||
| @Bean | ||
| fun requestMdcFilterRegistration(filter: RequestMdcFilter): FilterRegistrationBean<RequestMdcFilter> = | ||
| FilterRegistrationBean(filter).apply { | ||
| order = SecurityProperties.DEFAULT_FILTER_ORDER - 1 | ||
| } | ||
| } |
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
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,106 @@ | ||
| <?xml version="1.0" encoding="UTF-8"?> | ||
| <configuration> | ||
| <include resource="org/springframework/boot/logging/logback/defaults.xml"/> | ||
|
|
||
| <!-- 로그 패턴 정의 --> | ||
| <!-- 개발 환경 (local, test): 색상이 포함된 읽기 쉬운 형식 --> | ||
| <property name="CONSOLE_LOG_PATTERN_DEV" | ||
| value="%clr(%d{yyyy-MM-dd HH:mm:ss.SSS}){faint} %clr(${LOG_LEVEL_PATTERN:-%5p}) %clr([%15.15t]){faint} %clr(%-40.40logger{39}){cyan} %clr(:){faint} [%X{requestId:-NO_REQUEST_ID}] [%X{userId:-ANONYMOUS}] %m%n${LOG_EXCEPTION_CONVERSION_WORD:-%wEx}"/> | ||
|
|
||
| <!-- 개발 환경용 콘솔 출력 (사람이 읽기 좋은 형식) --> | ||
| <appender name="CONSOLE" class="ch.qos.logback.core.ConsoleAppender"> | ||
| <encoder> | ||
| <pattern>${CONSOLE_LOG_PATTERN_DEV}</pattern> | ||
| <charset>UTF-8</charset> | ||
| </encoder> | ||
| </appender> | ||
|
|
||
| <!-- 운영 환경용 JSON 콘솔 출력 (Loki 최적화) --> | ||
| <appender name="CONSOLE_JSON" class="ch.qos.logback.core.ConsoleAppender"> | ||
| <encoder class="net.logstash.logback.encoder.LogstashEncoder"> | ||
| <!-- 타임스탬프 형식 --> | ||
| <timestampPattern>yyyy-MM-dd'T'HH:mm:ss.SSSXXX</timestampPattern> | ||
|
|
||
| <!-- MDC 값을 JSON 필드로 포함 --> | ||
| <includeMdcKeyName>requestId</includeMdcKeyName> | ||
| <includeMdcKeyName>userId</includeMdcKeyName> | ||
| <includeMdcKeyName>requestMethod</includeMdcKeyName> | ||
| <includeMdcKeyName>requestUri</includeMdcKeyName> | ||
| <includeMdcKeyName>clientIp</includeMdcKeyName> | ||
|
|
||
| <!-- 기본 필드명 커스터마이징 --> | ||
| <fieldNames> | ||
| <timestamp>timestamp</timestamp> | ||
| <version>[ignore]</version> | ||
| <levelValue>[ignore]</levelValue> | ||
| </fieldNames> | ||
|
|
||
| <!-- 추가 정적 필드 --> | ||
| <customFields>{"application":"yapp"}</customFields> | ||
|
|
||
| <!-- 스택 트레이스 포함 --> | ||
| <throwableConverter class="net.logstash.logback.stacktrace.ShortenedThrowableConverter"> | ||
| <maxDepthPerThrowable>30</maxDepthPerThrowable> | ||
| <maxLength>2048</maxLength> | ||
| <shortenedClassNameLength>20</shortenedClassNameLength> | ||
| <exclude>^sun\.reflect\..*\.invoke</exclude> | ||
| <exclude>^net\.sf\.cglib\.proxy\.MethodProxy\.invoke</exclude> | ||
| <rootCauseFirst>true</rootCauseFirst> | ||
| </throwableConverter> | ||
| </encoder> | ||
| </appender> | ||
|
|
||
| <!-- 프로파일별 설정 --> | ||
|
|
||
| <!-- local 프로파일: 개발 환경 (색상 포함, 상세 로그) --> | ||
| <springProfile name="local"> | ||
| <root level="INFO"> | ||
| <appender-ref ref="CONSOLE"/> | ||
| </root> | ||
|
|
||
| <!-- 개발 시 디버그 로그 활성화 --> | ||
| <logger name="com.yapp2app" level="DEBUG"/> | ||
| <logger name="org.springframework.web" level="DEBUG"/> | ||
| <logger name="org.springframework.security" level="DEBUG"/> | ||
| </springProfile> | ||
|
|
||
| <!-- test 프로파일: 테스트 환경 --> | ||
| <springProfile name="test"> | ||
| <root level="INFO"> | ||
| <appender-ref ref="CONSOLE"/> | ||
| </root> | ||
|
|
||
| <logger name="com.yapp2app" level="DEBUG"/> | ||
| </springProfile> | ||
|
|
||
| <!-- staging 프로파일: 스테이징 환경 (k8s + Loki, JSON 로그) --> | ||
| <springProfile name="staging"> | ||
| <root level="INFO"> | ||
| <appender-ref ref="CONSOLE_JSON"/> | ||
| </root> | ||
|
|
||
| <logger name="com.yapp2app" level="INFO"/> | ||
| <logger name="org.springframework" level="WARN"/> | ||
| </springProfile> | ||
|
|
||
| <!-- production 프로파일: 운영 환경 (k8s + Loki, JSON 로그) --> | ||
| <springProfile name="prod,production"> | ||
| <root level="INFO"> | ||
| <appender-ref ref="CONSOLE_JSON"/> | ||
| </root> | ||
|
|
||
| <logger name="com.yapp2app" level="INFO"/> | ||
| <logger name="org.springframework" level="WARN"/> | ||
| <logger name="org.hibernate" level="WARN"/> | ||
| </springProfile> | ||
|
|
||
| <!-- 기본 설정 (프로파일 미지정) --> | ||
| <springProfile name="!local & !test & !staging & !prod & !production"> | ||
| <root level="INFO"> | ||
| <appender-ref ref="CONSOLE"/> | ||
| </root> | ||
|
|
||
| <logger name="com.yapp2app" level="DEBUG"/> | ||
| </springProfile> | ||
|
|
||
| </configuration> |
Oops, something went wrong.
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.
Uh oh!
There was an error while loading. Please reload this page.