-
Notifications
You must be signed in to change notification settings - Fork 1
feat: Spring Actuator 보안 강화 및 Prometheus 메트릭 수집 기능 구현 #107
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
Conversation
|
Caution Review failedThe pull request is closed. Walkthrough여러 모듈에서 Actuator 의존성을 재배치하고 게이트웨이에 Actuator 및 Prometheus 레지스트리를 추가했으며, 게이트웨이 보안은 Actuator base-path 기반 화이트리스트로 변경했습니다. 테스트 시크릿 파일 생성/주입 라인을 CI/CD 워크플로우와 설정에서 제거했고, 인프라 설정을 프로필별 YAML로 분리·정리했습니다. Changes
Sequence Diagram(s)sequenceDiagram
autonumber
participant Client as Client
participant GW as Gateway
participant Sec as SecurityFilterChain
participant Act as Actuator
rect rgba(235,245,220,0.35)
note right of GW: Actuator base-path (management.endpoints.web.base-path)는\napplication-web.yml에 설정됨
Client->>GW: GET {ACTUATOR_BASE_PATH}/prometheus
GW->>Sec: 인증/인가 검사 (whitelistUrls 포함)
alt 요청 경로가 whitelist에 포함
Sec-->>GW: 허용(permitAll)
GW->>Act: Actuator /prometheus 핸들러 호출
Act-->>Client: metrics 응답 (text/plain)
else 권한 필요
Sec-->>Client: 401/403
end
end
sequenceDiagram
autonumber
participant Config as Config Files
participant Context as Spring Context
participant GW as Gateway Beans
rect rgba(220,235,245,0.35)
note right of Config: application-jwt.yml / application-web.yml
Config-->>Context: jwt.*, management.endpoints.web.* 바인딩
Context-->>GW: JwtProperties, ActuatorProperties 빈 주입
GW->>GW: JwtConfig/JwtTokenProvider가 JwtProperties 사용
GW->>GW: SecurityConfig가 ActuatorProperties.basePath로 whitelist 생성
end
Estimated code review effort🎯 4 (Complex) | ⏱️ ~60 minutes Assessment against linked issues
Assessment against linked issues: Out-of-scope changes
Tip 🔌 Remote MCP (Model Context Protocol) integration is now available!Pro plan users can now connect to remote MCP servers from the Integrations page. Connect with popular remote MCPs such as Notion and Linear to add more context to your reviews and chats. 📜 Recent review detailsConfiguration used: .coderabbit.yaml 💡 Knowledge Base configuration:
You can enable these sources in your CodeRabbit configuration. 📒 Files selected for processing (7)
✨ Finishing Touches
🧪 Generate unit tests
Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out. 🪧 TipsChatThere are 3 ways to chat with CodeRabbit:
SupportNeed help? Create a ticket on our support page for assistance with any issues or questions. CodeRabbit Commands (Invoked using PR/Issue comments)Type Other keywords and placeholders
Status, Documentation and Community
|
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Actionable comments posted: 14
Caution
Some comments are outside the diff and can’t be posted inline due to platform limitations.
⚠️ Outside diff range comments (4)
apis/src/main/resources/application.yml (1)
31-32: multipart 용량 설정 재확인 및 지연 파싱(resolve-lazily) 제안현재 파일 최대용량 10MB, 요청 최대용량 30MB로 설정되어 있어, 다중 파일 업로드(예: 3개 × 10MB)는 허용되지만 단일 파일 20MB는 거부됩니다. 의도한 정책인지 확인 부탁드립니다. 대용량 업로드의 리소스 낭비를 줄이기 위해 지연 파싱도 고려해볼 수 있습니다.
지연 파싱 추가 예시:
servlet: multipart: max-file-size: 10MB max-request-size: 30MB + resolve-lazily: trueinfra/src/main/resources/application-crosscutting.yml (1)
1-28: 경고:application-crosscutting.yml가 자동 로드되지 않습니다 — 명시적 import 필요현재
infra/src/main/resources/application-crosscutting.yml파일은 Spring Boot가 기본으로 로드하지 않아, 안에 정의된logging.aop.*설정이 적용되지 않습니다.
다음 중 하나를 선택해 수정해주세요:• 메인
application.yml(또는 프로파일별 YAML)에 import 추가spring: config: import: "classpath:application-crosscutting.yml"• 파일명을
application.yml/application-<profile>.yml규칙으로 변경위치:
- infra/src/main/resources/application-crosscutting.yml
필요 시 어떤 파일에 import를 추가할지 함께 검토해 드리겠습니다.
gateway/src/main/kotlin/org/yapp/gateway/config/JwtConfig.kt (1)
67-71: SecretKeySpec 알고리즘 표준명 사용 및 macAlgorithm 명시 필요현재
SecretKeySpec(jwtProperties.secretKey.toByteArray(), SIGNATURE_ALGORITHM.name)에서SIGNATURE_ALGORITHM.name이 JOSE 표기인"HS256"을 반환하여, JCA 표준 알고리즘 명인"HmacSHA256"과 불일치합니다. 런타임에 따라 서명 검증 오류나 예기치 않은 동작을 유발할 수 있으므로 아래와 같이 수정해 주세요.
- 대상 파일:
gateway/src/main/kotlin/org/yapp/gateway/config/JwtConfig.kt- 대상 메서드:
jwtDecoder()수정 제안(diff):
- val secretKeySpec = SecretKeySpec(jwtProperties.secretKey.toByteArray(), SIGNATURE_ALGORITHM.name) - val decoder = NimbusJwtDecoder.withSecretKey(secretKeySpec).build() + val secretKeySpec = SecretKeySpec(jwtProperties.secretKey.toByteArray(), "HmacSHA256") + val decoder = NimbusJwtDecoder + .withSecretKey(secretKeySpec) + .macAlgorithm(SIGNATURE_ALGORITHM) // HS256 명시 + .build()추가 권장사항:
- HS256 사용 시 비밀키는 최소 32바이트 이상이어야 합니다. (
require(jwtProperties.secretKey.toByteArray().size >= 32))- 변경 후 통합 테스트를 통해 JWT 발급/검증 경로가 정상 동작하는지 확인해 주세요.
gateway/src/main/kotlin/org/yapp/gateway/jwt/JwtTokenProvider.kt (1)
26-33: 만료 시간 표현을 Duration 기반으로 전환 제안초 단위 Long보다 Duration 타입을 사용하면 설정값 표현(“15m”, “2h”)이 명확해지고 오입력(밀리초/초 혼동)을 줄일 수 있습니다. 아래와 같이 제안합니다.
- fun generateAccessToken(userId: UUID, roles: List<Role>): String { + fun generateAccessToken(userId: UUID, roles: List<Role>): String { val roleStrings = roles.map { it.key } val claims = mapOf( JwtConstants.ROLES_CLAIM to roleStrings, TOKEN_TYPE_CLAIM to ACCESS_TOKEN_TYPE ) - return generateToken(userId.toString(), jwtProperties.accessTokenExpiration, claims) + return generateToken(userId.toString(), jwtProperties.accessTokenExpiration, claims) } @@ - fun generateRefreshToken(userId: UUID): String { + fun generateRefreshToken(userId: UUID): String { val claims = mapOf(TOKEN_TYPE_CLAIM to REFRESH_TOKEN_TYPE) - return generateToken(userId.toString(), jwtProperties.refreshTokenExpiration, claims) + return generateToken(userId.toString(), jwtProperties.refreshTokenExpiration, claims) } @@ - private fun generateToken(subject: String, expirationSeconds: Long, claims: Map<String, Any>): String { - val now = Instant.now() - val expiry = now.plusSeconds(expirationSeconds) + private fun generateToken(subject: String, expiration: java.time.Duration, claims: Map<String, Any>): String { + val now = Instant.now() + val expiry = now.plus(expiration)추가로, JwtProperties의 타입도 Duration으로 맞춰야 합니다(파일 외 변경):
// gateway/src/main/kotlin/org/yapp/gateway/config/JwtProperties.kt @ConfigurationProperties(prefix = "jwt") data class JwtProperties( val secretKey: String, val accessTokenExpiration: Duration, val refreshTokenExpiration: Duration, )
📜 Review details
Configuration used: .coderabbit.yaml
Review profile: ASSERTIVE
Plan: Pro
💡 Knowledge Base configuration:
- MCP integration is disabled by default for public repositories
- Jira integration is disabled by default for public repositories
- Linear integration is disabled by default for public repositories
You can enable these sources in your CodeRabbit configuration.
📒 Files selected for processing (24)
.github/workflows/ci-pr.yml(0 hunks).github/workflows/dev-ci-cd.yml(0 hunks).github/workflows/prod-ci-cd.yml(0 hunks)admin/build.gradle.kts(0 hunks)apis/build.gradle.kts(0 hunks)apis/src/main/resources/application.yml(1 hunks)batch/build.gradle.kts(0 hunks)buildSrc/src/main/kotlin/Dependencies.kt(1 hunks)gateway/build.gradle.kts(1 hunks)gateway/src/main/kotlin/org/yapp/gateway/config/ActuatorProperties.kt(1 hunks)gateway/src/main/kotlin/org/yapp/gateway/config/JwtConfig.kt(4 hunks)gateway/src/main/kotlin/org/yapp/gateway/config/JwtProperties.kt(1 hunks)gateway/src/main/kotlin/org/yapp/gateway/jwt/JwtTokenProvider.kt(3 hunks)gateway/src/main/kotlin/org/yapp/gateway/security/SecurityConfig.kt(3 hunks)gateway/src/main/resources/application-jwt.yml(1 hunks)gateway/src/main/resources/application-web.yml(1 hunks)infra/src/main/kotlin/org/yapp/infra/aop/aspect/ControllerLoggingAspect.kt(1 hunks)infra/src/main/kotlin/org/yapp/infra/aop/aspect/ServiceLoggingAspect.kt(1 hunks)infra/src/main/kotlin/org/yapp/infra/config/internal/aop/AopConfig.kt(0 hunks)infra/src/main/kotlin/org/yapp/infra/config/internal/aop/LoggingAopProperties.kt(1 hunks)infra/src/main/resources/application-crosscutting.yml(1 hunks)infra/src/main/resources/application-external.yml(1 hunks)infra/src/main/resources/application-persistence.yml(1 hunks)infra/src/main/resources/application-redis.yml(1 hunks)
💤 Files with no reviewable changes (7)
- .github/workflows/prod-ci-cd.yml
- batch/build.gradle.kts
- .github/workflows/dev-ci-cd.yml
- admin/build.gradle.kts
- .github/workflows/ci-pr.yml
- infra/src/main/kotlin/org/yapp/infra/config/internal/aop/AopConfig.kt
- apis/build.gradle.kts
🧰 Additional context used
🪛 detekt (1.23.8)
gateway/src/main/kotlin/org/yapp/gateway/security/SecurityConfig.kt
[warning] 60-60: In most cases using a spread operator causes a full copy of the array to be created before calling a method. This may result in a performance penalty.
(detekt.performance.SpreadOperator)
🔇 Additional comments (17)
infra/src/main/kotlin/org/yapp/infra/config/internal/aop/LoggingAopProperties.kt (1)
1-1: 패키지 이동 영향 없음 — 참조 대상이 동일 패키지에 위치하여 import 불필요검증 결과
LoggingAopProperties.kt와AopConfig.kt가 동일한 패키지(org.yapp.infra.config.internal.aop)에 위치해 있어 별도 import 없이도 정상 참조되며, 다른 Aspect 클래스들(ServiceLoggingAspect,ControllerLoggingAspect)도 이미 새 패키지 경로를 올바르게 사용하고 있습니다. 컴파일 에러 발생 가능성은 없습니다.Likely an incorrect or invalid review comment.
apis/src/main/resources/application.yml (1)
8-29: profiles.group에 web·crosscutting 모듈 의존성은 확인되나, redis·external 프로필 리소스 미확인 — 의존성/리소스 포함 점검 요청아래 항목을 확인해 주세요. Spring Boot가
application-{profile}.yml을 classpath에서 로드하려면, 해당 파일이 존재하는 모듈을apis가 의존해야 합니다.• application-web.yml
– 위치:gateway/src/main/resources/application-web.yml
–apis/build.gradle.kts에project(Dependencies.Projects.GATEWAY)의존 확인됨• application-crosscutting.yml
– 위치:infra/src/main/resources/application-crosscutting.yml
–apis/build.gradle.kts에project(Dependencies.Projects.INFRA)의존 확인됨• application-redis.yml
– 파일 미발견
– 해당 파일을 포함하는 모듈과apis모듈의 의존 등록 여부 점검 필요• application-external.yml
– 파일 미발견
– 해당 파일을 포함하는 모듈과apis모듈의 의존 등록 여부 점검 필요위 리소스가 누락돼 있으면, 프로필을 지정해도 설정이 적용되지 않습니다. 파일 위치와
apis모듈의build.gradle(.kts)에 올바른implementation(project(...))의존을 추가해 주시기 바랍니다.infra/src/main/resources/application-external.yml (2)
15-33: 테스트 프로필에 더미 외부 연동 키 주입: 방향성 좋습니다실제 키/비밀을 제거하고 테스트 전용 더미 값을 주입하는 접근이 보안과 테스트 안정성 측면에서 적절합니다.
29-33: key-path 프로퍼티 미사용으로 인한 테스트 실패 우려 없음
현재 infra/src 아래에oauth.apple.key-path를 참조하거나.p8키를 즉시 로드하는 로직이 전혀 존재하지 않습니다. 테스트 부트스트랩 시점에 파일 경로 문제로 실패할 가능성도 없습니다. 해당 코멘트는 적용되지 않으므로 무시해 주세요.Likely an incorrect or invalid review comment.
gateway/src/main/resources/application-jwt.yml (1)
3-4: 만료 시간의 단위 일관성 확인(초 vs 밀리초) — 코드 사용부와 맞춰주세요YAML 주석상 값은 “초 단위”로 보입니다. JwtProperties는 Long 타입으로 정의되어 있는데, 토큰 생성부(JwtTokenProvider 등)에서 밀리초 기반 API를 사용한다면 곱셈(×1000) 누락으로 만료가 짧게/길게 적용될 수 있습니다. Duration 타입으로의 전환을 권장합니다(아래 JwtProperties.kt 코멘트 참고).
다음 키워드로 코드 내 사용 단위를 확인해 주세요: accessTokenExpiration, refreshTokenExpiration, plusSeconds, ofSeconds, toMillis, ×1000 등.
gateway/build.gradle.kts (2)
3-13: Configuration Properties 메타데이터(processor) 확인JwtProperties를 @ConfigurationProperties로 사용 중이므로 메타데이터 생성을 위해 configuration-processor가 annotation processor 경로에 포함되어 있는지 확인이 필요합니다. 다른 공통 플러그인에서 처리 중일 수 있으나, 누락 시 IDE 지원/메타데이터가 제한됩니다.
Gradle 구성에서 아래 항목들을 점검해 주세요:
- Kotlin 프로젝트라면 kapt(또는 ksp)로 processor 연결 여부
- 혹은 Java 소스가 포함되어 annotationProcessor로 처리되는지 여부
8-11: Actuator 보안/노출 설정 확인 필요현재 리소스 설정 파일(application.yml, application.properties 등)에 Actuator 보안 및 노출 정책 관련 프로퍼티가 전혀 정의되어 있지 않습니다. 아래 항목들을 적절히 추가·검토해 주세요.
management.endpoints.web.exposure.include(또는exclude)management.endpoints.web.base-pathmanagement.server.portmanagement.jmx.enabled=falsemanagement.endpoint.health.show-details및management.endpoint.health.probes설정 파일에 반영하신 뒤, 애플리케이션이 기대한 방식으로 동작하는지 재확인 부탁드립니다.
infra/src/main/kotlin/org/yapp/infra/aop/aspect/ControllerLoggingAspect.kt (1)
11-11: 패키지 경로 리팩터링 반영 확인(LGTM)LoggingAopProperties import 경로 변경이 일관되게 반영되었습니다. 동일 리팩터링이 참조처(ServiceLoggingAspect, AopConfig 등)에도 적용되었는지 최종 확인만 부탁드립니다.
infra/src/main/kotlin/org/yapp/infra/aop/aspect/ServiceLoggingAspect.kt (1)
11-11: 패키지 이동에 따른 import 변경 적합
LoggingAopProperties의 패키지 변경에 맞춘 import 경로 수정이 정확합니다. 런타임 동작에는 영향이 없으며, 동일한 프로퍼티 바인딩을 유지합니다.gateway/src/main/kotlin/org/yapp/gateway/config/JwtConfig.kt (1)
24-26: JwtProperties 기반 DI 전환 깔끔합니다설정값을
JwtProperties로 일원화한 점 좋습니다. 프로필별 오버라이드와 테스트 편의성 모두 개선됩니다.gateway/src/main/resources/application-web.yml (1)
19-29: 테스트 프로필 구성 적절테스트 프로필에서 고정 포트(8081)와 테스트용 base-path(
/actuator-test)로 오버라이드한 점 좋습니다. JMX 노출 차단도 의도에 부합합니다.gateway/src/main/kotlin/org/yapp/gateway/jwt/JwtTokenProvider.kt (2)
9-19: 구성 바인딩으로 전환 LGTM@value 분산 주입을 JwtProperties로 응집시킨 방향 좋습니다. 테스트/프로필별 오버라이드도 용이해졌습니다.
26-33: JWT 만료 설정 초 단위로 올바르게 지정됨
- gateway/src/main/resources/application-jwt.yml
- access-token-expiration: 1800 (초 단위), 테스트 프로필: 120 (초 단위)
- refresh-token-expiration: 1209600 (초 단위), 테스트 프로필: 3600 (초 단위)
- application-web.yml에는 JWT 만료 설정 없음
모든 만료 값이
plusSeconds호출과 일치하는 초(second) 단위로 설정되어 있음을 확인했습니다. 추가 검증이나 수정을 진행할 필요가 없습니다.gateway/src/main/kotlin/org/yapp/gateway/security/SecurityConfig.kt (4)
19-26: ActuatorProperties 바인딩/주입 적용 LGTM@configuration + @EnableConfigurationProperties로 안전하게 바인딩되고, 생성자 주입으로 테스트 편의성도 좋아졌습니다.
61-61: ADMIN 경로 권한 부여 규칙 유지 LGTM화이트리스트와 분리되어 있어 충돌 가능성 낮습니다.
28-36: favicon 공개 추가 LGTM문서/리소스 접근에서 404/불필요 인증 회피에 유용합니다.
46-66: 운영 보안 권고: Actuator 관리 엔드포인트 설정 미확인
현재application.yml또는application.properties어디에서도management.*설정이 발견되지 않아, 기본값(모든 엔드포인트 공개, 애플리케이션 포트와 동일한 management 포트)이 적용될 가능성이 높습니다. 운영 환경에서는 반드시 다음 보완을 수행해 주세요:
- 별도 관리 포트 사용
–management.server.port를 프라이빗 네트워크/클러스터 전용 포트로 분리- 엔드포인트 노출 제한
–management.endpoints.web.exposure.include=health,info,metrics,prometheus- 방화벽/IP 허용 목록 적용
– 인프라 레벨에서 관리 포트 접근을 최소화- 추가 권고(옵션)
– 요청당 rate limit 적용
– gzip 비활성화로 정보 노출 최소화설정 유무를 다시 확인하려면 아래 스크립트를 실행해 주세요:
#!/bin/bash echo "=== YAML 파일에서 management 설정 확인 ===" rg -n 'management\.' -g '*.yml' . echo "=== Properties 파일에서 management 설정 확인 ===" rg -n 'management\.' -g '*.properties' .
| implementation(Dependencies.Spring.BOOT_STARTER_ACTUATOR) | ||
|
|
||
| implementation(Dependencies.Prometheus.MICROMETER_PROMETHEUS_REGISTRY) | ||
|
|
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
🧹 Nitpick (assertive)
Prometheus Registry는 runtimeOnly 의존성으로 두는 편이 일반적입니다
다수의 예제/가이드에서는 micrometer-registry-prometheus를 runtimeOnly로 추가해 컴파일 클래스패스를 최소화합니다. 구현 코드에서 해당 라이브러리 API를 직접 참조하지 않는다면 runtimeOnly로 전환을 고려해 주세요.
- implementation(Dependencies.Spring.BOOT_STARTER_ACTUATOR)
-
- implementation(Dependencies.Prometheus.MICROMETER_PROMETHEUS_REGISTRY)
+ implementation(Dependencies.Spring.BOOT_STARTER_ACTUATOR)
+ runtimeOnly(Dependencies.Prometheus.MICROMETER_PROMETHEUS_REGISTRY)📝 Committable suggestion
‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.
| implementation(Dependencies.Spring.BOOT_STARTER_ACTUATOR) | |
| implementation(Dependencies.Prometheus.MICROMETER_PROMETHEUS_REGISTRY) | |
| implementation(Dependencies.Spring.BOOT_STARTER_ACTUATOR) | |
| runtimeOnly(Dependencies.Prometheus.MICROMETER_PROMETHEUS_REGISTRY) |
🤖 Prompt for AI Agents
In gateway/build.gradle.kts around lines 8 to 11, the micrometer Prometheus
registry is declared as an implementation dependency but is typically only
needed at runtime; change
implementation(Dependencies.Prometheus.MICROMETER_PROMETHEUS_REGISTRY) to
runtimeOnly(Dependencies.Prometheus.MICROMETER_PROMETHEUS_REGISTRY) so it is
excluded from the compile classpath (confirm no code references its API at
compile time and adjust if any compile-time usages exist).
| @ConfigurationProperties(prefix = "management.endpoints.web") | ||
| data class ActuatorProperties( | ||
| val basePath: String | ||
| ) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
🛠️ Refactor suggestion
basePath 바인딩의 안정성 강화(기본값/검증/정규화 제안)
환경변수 미설정 시 애플리케이션이 부팅 실패할 수 있고(필수 필드), 후속 보안 설정(화이트리스트 패턴)에서 선행/후행 슬래시 일관성 문제가 발생할 여지가 있습니다. 기본값, Bean Validation, 정규화된 접근자를 추가해 안정성을 높이는 것을 권장합니다.
적용 제안(diff):
package org.yapp.gateway.config
import org.springframework.boot.context.properties.ConfigurationProperties
+import org.springframework.validation.annotation.Validated
+import jakarta.validation.constraints.NotBlank
-@ConfigurationProperties(prefix = "management.endpoints.web")
-data class ActuatorProperties(
- val basePath: String
-)
+@ConfigurationProperties(prefix = "management.endpoints.web")
+@Validated
+data class ActuatorProperties(
+ @field:NotBlank
+ val basePath: String = "/actuator"
+) {
+ // 보안/화이트리스트 매칭 시 슬래시 일관성 보장
+ val normalizedBasePath: String =
+ basePath.let { if (!it.startsWith("/")) "/$it" else it }.removeSuffix("/")
+}이렇게 하면:
- 기본값 제공으로 설정 누락 시에도 안전하게 기동
- 빈 검증으로 오설정 방지
- 보안 설정에서
.../basePath/**패턴 매칭 일관성 보장
| @ConfigurationProperties(prefix = "jwt") | ||
| data class JwtProperties( | ||
| val secretKey: String, | ||
| val accessTokenExpiration: Long, | ||
| val refreshTokenExpiration: Long | ||
| ) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
💡 Verification agent
🧩 Analysis chain
Duration 타입과 Bean Validation(@validated) 적용을 권장합니다
- 만료 시간은 Long(초) 대신 Duration을 사용하면 단위 혼동을 제거하고, YAML에서 30m/2w 같은 직관적 표기가 가능합니다.
- 시크릿 키는 공백/누락 방지를 위해 Bean Validation을 붙이는 것이 안전합니다(운영에서 미설정 시 즉시 실패).
아래와 같이 변경을 제안합니다.
package org.yapp.gateway.config
import org.springframework.boot.context.properties.ConfigurationProperties
+import org.springframework.validation.annotation.Validated
+import jakarta.validation.constraints.NotBlank
+import java.time.Duration
-@ConfigurationProperties(prefix = "jwt")
-data class JwtProperties(
- val secretKey: String,
- val accessTokenExpiration: Long,
- val refreshTokenExpiration: Long
-)
+@ConfigurationProperties(prefix = "jwt")
+@Validated
+data class JwtProperties(
+ @field:NotBlank
+ val secretKey: String,
+ val accessTokenExpiration: Duration,
+ val refreshTokenExpiration: Duration,
+)참고:
- YAML 예시
- access-token-expiration: 30m
- refresh-token-expiration: 2w
- 클래스 변경 이후 토큰 생성부(JwtTokenProvider 등)에서 Duration을 직접 사용하거나 toSeconds()/toMillis()로 변환해 주세요.
JwtTokenProvider와 JwtConfig에서 JwtProperties 타입 변경 영향(초→Duration)이 문제 없는지 확인 부탁드립니다.
아래 스크립트로 JwtTokenProvider, JwtConfig 클래스 위치와 accessTokenExpiration/refreshTokenExpiration 사용처를 확인하겠습니다.
🏁 Script executed:
#!/bin/bash
set -e
echo "🔍 class JwtTokenProvider 위치:"
rg -n "class JwtTokenProvider" -C3
echo
echo "🔍 class JwtConfig 위치:"
rg -n "class JwtConfig" -C3
echo
echo "🔍 accessTokenExpiration 사용처:"
rg -n "accessTokenExpiration" -C3
echo
echo "🔍 refreshTokenExpiration 사용처:"
rg -n "refreshTokenExpiration" -C3Length of output: 4625
JwtProperties를 Duration으로 변경 시 JwtTokenProvider 수정 필요
- JwtProperties
Long→Duration@Validated+@field:NotBlank추가
- JwtTokenProvider
generateToken(subject, expirationSeconds: Long, …)호출부에서toSeconds()/toMillis()변환getRefreshTokenExpiration()반환 타입은Duration.toSeconds()호출로 유지
제안된 수정 예시:
--- a/gateway/src/main/kotlin/org/yapp/gateway/config/JwtProperties.kt
+++ b/gateway/src/main/kotlin/org/yapp/gateway/config/JwtProperties.kt
@@
-@ConfigurationProperties(prefix = "jwt")
-data class JwtProperties(
- val secretKey: String,
- val accessTokenExpiration: Long,
- val refreshTokenExpiration: Long
-)
+@ConfigurationProperties(prefix = "jwt")
+@Validated
+data class JwtProperties(
+ @field:NotBlank
+ val secretKey: String,
+ val accessTokenExpiration: Duration,
+ val refreshTokenExpiration: Duration
+)--- a/gateway/src/main/kotlin/org/yapp/gateway/jwt/JwtTokenProvider.kt
+++ b/gateway/src/main/kotlin/org/yapp/gateway/jwt/JwtTokenProvider.kt
@@ fun generateAccessToken(userId: UUID): String {
- return generateToken(userId.toString(), jwtProperties.accessTokenExpiration, claims)
+ return generateToken(userId.toString(), jwtProperties.accessTokenExpiration.toSeconds(), claims)
@@ fun generateRefreshToken(userId: UUID): String {
- return generateToken(userId.toString(), jwtProperties.refreshTokenExpiration, claims)
+ return generateToken(userId.toString(), jwtProperties.refreshTokenExpiration.toSeconds(), claims)
@@ fun getRefreshTokenExpiration(): Long {
- return jwtProperties.refreshTokenExpiration
+ return jwtProperties.refreshTokenExpiration.toSeconds()
}위 변경 후 JwtConfig는 기존대로 @EnableConfigurationProperties(JwtProperties::class)만으로 동작합니다.
🤖 Prompt for AI Agents
In gateway/src/main/kotlin/org/yapp/gateway/config/JwtProperties.kt around lines
5 to 10, change the property types from Long to java.time.Duration and add
validation annotations (@Validated on the class and @field:NotBlank on
secretKey) so configuration binds to Duration values; then update
JwtTokenProvider usages to accept Duration-based expirations (call
expiration.toSeconds()/toMillis() where a numeric value is required) and ensure
methods like getRefreshTokenExpiration() return a Long by calling
duration.toSeconds(); JwtConfig can remain using
@EnableConfigurationProperties(JwtProperties::class).
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
환경변수로 이부분들이 셋팅되지 않았을 때 확인할 수 있도록 해주는 에러문구가 나타나면 좋을 것 같아요 ex) validation check하는부분이라던지..?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
@minwoo1999 정확한 지적입니다! 환경변수 미설정으로 인한 문제를 조기에 발견하는 것이 매우 중요하네요.
Bean Validation을 추가하면 애플리케이션 시작 시점에 명확한 에러 메시지가 나타납니다:
package org.yapp.gateway.config
import org.springframework.boot.context.properties.ConfigurationProperties
import org.springframework.validation.annotation.Validated
import jakarta.validation.constraints.NotBlank
import jakarta.validation.constraints.Positive
import java.time.Duration
@ConfigurationProperties(prefix = "jwt")
@Validated
data class JwtProperties(
@field:NotBlank(message = "JWT secret key must not be blank. Check JWT_SECRET_KEY environment variable.")
val secretKey: String,
@field:Positive(message = "Access token expiration must be positive. Check JWT_ACCESS_TOKEN_EXPIRATION environment variable.")
val accessTokenExpiration: Duration,
@field:Positive(message = "Refresh token expiration must be positive. Check JWT_REFRESH_TOKEN_EXPIRATION environment variable.")
val refreshTokenExpiration: Duration
)이렇게 하면 환경변수가 누락되거나 잘못 설정되었을 때 애플리케이션 시작 단계에서 다음과 같은 명확한 에러가 출력됩니다:
***************************
APPLICATION FAILED TO START
***************************
Description:
Binding to target org.yapp.gateway.config.JwtProperties failed:
Property: jwt.secretKey
Value: ""
Reason: JWT secret key must not be blank. Check JWT_SECRET_KEY environment variable.
이는 배포 시 환경변수 설정 누락을 즉시 발견할 수 있게 해주어 운영 환경에서의 문제를 사전에 방지할 수 있습니다.
🐰
∩───∩
( ◕ ◕ )
\ ___ / validation으로 안전하게!
\ /
-----
| jwt: | ||
| secret-key: this-is-a-simple-key-for-testing-purpose-only | ||
| access-token-expiration: 120 # 2 minute (초 단위, 테스트용 짧게 설정) | ||
| refresh-token-expiration: 3600 # 1 hour (초 단위, 테스트용 짧게 설정) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
🧹 Nitpick (assertive)
테스트 시크릿 키 하드코딩 관리 방안 점검 필요
테스트 용도라 하더라도 하드코딩된 시크릿이 우발적으로 다른 환경에 주입되지 않도록 관리가 필요합니다. 최소한 test 프로필에서만 로드되도록 위 import/프로필 문제를 우선 해결해 주세요. 추가로, 테스트 전용 application-test.yml로 분리하거나 CI에서만 주입하는 환경 변수/ConfigMap로 대체하는 방안도 고려해 볼 만합니다.
테스트 실행 시 활성 프로필이 test로 지정되는지, 그리고 test 외 환경에서 해당 값이 노출되지 않는지 확인 부탁드립니다.
🤖 Prompt for AI Agents
In gateway/src/main/resources/application-jwt.yml around lines 12 to 15, the JWT
secret is hardcoded for testing and may be loaded into non-test environments;
remove the hardcoded value from this common file and instead (a) create
application-test.yml with the secret-key set there or (b) replace the value with
a placeholder that reads from an environment variable/ConfigMap (e.g.
${JWT_SECRET:}) so production configs never contain the test secret, update test
configuration to activate the "test" profile during test runs (e.g. test
framework or CI sets spring.profiles.active=test) and add a verification step to
CI or local test setup to assert the active profile is "test" and that
application-jwt.yml no longer exposes the secret in non-test runs.
| management: | ||
| server: | ||
| port: ${ACTUATOR_PORT} | ||
| endpoints: | ||
| jmx: | ||
| exposure: | ||
| exclude: "*" | ||
| web: | ||
| exposure: | ||
| include: info, health, metrics, prometheus | ||
| base-path: ${ACTUATOR_PATH} | ||
|
|
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
💡 Verification agent
🧩 Analysis chain
Actuator 포트/경로 보안 강화 제안(바인딩 기본값, 루프백 바인딩, 노출 축소)
현 설정은 의도대로 별도 포트/경로로 분리되어 좋습니다. 추가로 다음을 고려해 주세요.
- 루프백 바인딩: 외부 노출 최소화를 위해 기본 주소를 127.0.0.1로 제한
- 기본값 제공: 환경변수 미설정 시 기동 실패 방지
- 노출 엔드포인트 축소:
info는 빌드/환경 정보 유출 우려가 있을 수 있어 기본 제외 권장(필요 시 내부에서만 확인)
수정 제안(diff):
management:
server:
- port: ${ACTUATOR_PORT}
+ address: 127.0.0.1
+ port: ${ACTUATOR_PORT:8081}
endpoints:
jmx:
exposure:
exclude: "*"
web:
exposure:
include: info, health, metrics, prometheus
- base-path: ${ACTUATOR_PATH}
+ base-path: ${ACTUATOR_PATH:/actuator}선택적 보강:
- 운영에서
info제외:include: health, metrics, prometheus - readiness/liveness 프로브 필요 시:
management.endpoint.health.probes.enabled: true
다음 스크립트로 운영 포트와 충돌(Actuator 포트=애플리케이션 포트) 가능성을 점검해 주세요.
🏁 Script executed:
#!/bin/bash
# gateway 모듈 내 server.port 설정 확인
rg -n -C2 -i 'server:\s*\n\s*port\s*:\s*' gateway/src/main/resources
echo "Actuator port (default doc): ${ACTUATOR_PORT:-<env not set>}"Length of output: 357
아래 스크립트로 gateway/src/main/resources 내 server.port 설정 여부를 멀티라인 모드로 재확인하고, ACTUATOR_PORT/ACTUATOR_PATH 기본값도 함께 출력해 주세요.
🏁 Script executed:
#!/usr/bin/env bash
# gateway 모듈의 리소스(YAML)에서 server.port 설정 확인 (multiline 모드)
rg -U -n -C2 'server:\s*\n\s*port\s*:' gateway/src/main/resources/*.yml
# ACTUATOR_PORT, ACTUATOR_PATH 환경변수 기본값 확인
echo "ACTUATOR_PORT 기본값: ${ACTUATOR_PORT:-<env not set>}"
echo "ACTUATOR_PATH 기본값: ${ACTUATOR_PATH:-<env not set>}"Length of output: 963
아래 스크립트로 gateway/src/main/resources 내 애플리케이션 포트(server.port) 설정을 확인하고, 관리용 포트(8081)와 충돌 가능성을 점검해주세요.
🏁 Script executed:
#!/usr/bin/env bash
# 애플리케이션 server.port 설정 확인
rg -nP --type=yml 'server:\s*\n\s*port\s*:' gateway/src/main/resources/*.yml
# 혹은 dot 표기법 사용 여부 확인
rg -n --type=yml '\bserver\.port\b' -C2 gateway/src/main/resourcesLength of output: 264
아래 스크립트로 gateway/src/main/resources 내 모든 YAML 파일에서 애플리케이션용 server.port 설정이 8081(Actuator 포트)과 충돌하지 않는지 재확인해 주세요.
🏁 Script executed:
#!/usr/bin/env bash
# 멀티라인 모드로 `server:\n port:` 패턴 검색
rg -U -nP '^\s*server:\s*\n\s*port:' gateway/src/main/resources/*.yml
# dot 표기법(`server.port:`) 사용 여부 확인
rg -n 'server\.port:' -C2 gateway/src/main/resources/*.yml
# 결과 없을 시 기본값(8080) 사용 여부 안내
echo "애플리케이션 server.port 미설정 시 기본값: 8080"Length of output: 593
관리용 포트 이중 정의 확인 및 보안 설정 통합
application-web.yml에서 management.server.port가 두 번 정의되어(라인 3: ${ACTUATOR_PORT}, 라인 21: 8081) 의도한 환경변수 바인딩이 무력화되고 충돌이 발생할 수 있습니다. 중복 정의를 제거하고, 다음과 같이 보안 강화를 적용해 주세요.
- application-web.yml
- 중복된
management.server.port정의 제거 address: 127.0.0.1추가해 루프백 바인딩${ACTUATOR_PORT:8081}형태로 기본값 지정base-path: ${ACTUATOR_PATH:/actuator}형태로 기본값 지정- 기본 노출 엔드포인트 축소:
include: health, metrics, prometheus(info제외 권장)
- 중복된
management:
- server:
- port: ${ACTUATOR_PORT}
+ server:
+ address: 127.0.0.1
+ port: ${ACTUATOR_PORT:8081}
endpoints:
jmx:
exposure:
exclude: "*"
web:
exposure:
- include: info, health, metrics, prometheus
- base-path: ${ACTUATOR_PATH}
+ include: health, metrics, prometheus
+ base-path: ${ACTUATOR_PATH:/actuator}선택적 보강:
- 운영에서
info완전 제외 시include: health, metrics, prometheus - Readiness/Liveness 프로브 활성화:
management.endpoint.health.probes.enabled: true
🤖 Prompt for AI Agents
In gateway/src/main/resources/application-web.yml around lines 1 to 12, remove
the duplicate management.server.port declaration and consolidate to a single
entry using a defaulted environment variable (e.g. ${ACTUATOR_PORT:8081}); add
address: 127.0.0.1 to bind to loopback; change base-path to use a defaulted
variable (e.g. ${ACTUATOR_PATH:/actuator}); tighten exposed endpoints to include
only health, metrics, prometheus (remove info); optionally enable
readiness/liveness probes by adding management.endpoint.health.probes.enabled:
true.
| max-log-length: 1000 | ||
| controller: | ||
| enabled: true | ||
| - classpath:db/reed |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
💡 Verification agent
🧩 Analysis chain
Flyway locations 변경(db/reed) — 경로 존재 및 마이그레이션 충돌 여부 확인 필요
classpath:db/reed로 위치가 변경되었습니다. 실제 리소스 경로에 해당 디렉터리와 SQL 파일이 존재하는지, 그리고 db/migration과 버전 충돌(중복 버전)이 없는지 확인해 주세요. 다중 location 사용 시 파일명이 겹치면 이행 순서/충돌 문제가 발생할 수 있습니다.
확인 스크립트:
🏁 Script executed:
#!/bin/bash
set -euo pipefail
echo "1) db/reed 디렉터리 존재 여부"
fd -a -t d 'db/reed' || true
echo
echo "2) db/reed SQL 파일 나열"
fd -a -t f -g '**/resources/db/reed/*.sql' || true
echo
echo "3) db/migration SQL 파일 나열 (충돌 확인 대비)"
fd -a -t f -g '**/resources/db/migration/*.sql' || trueLength of output: 338
Flyway locations(db/reed) 경로 미존재 — 마이그레이션 로드 실패 예상
infra/src/main/resources/application-persistence.yml 25행에서 classpath:db/reed로 변경하였으나, 리포지토리 내에 해당 경로 및 SQL 파일이 존재하지 않습니다. 이대로 배포 시 Flyway가 마이그레이션을 찾지 못해 애플리케이션 실행이 실패할 수 있습니다.
- 디렉터리 미발견:
src/main/resources/db/reed없음 - SQL 파일 미발견:
src/main/resources/db/reed/*.sql없음 - 추가 확인:
src/main/resources/db/migration/*.sql에도 동일 버전 파일 없음
해결 방안:
db/reed디렉터리 생성 후 마이그레이션 SQL 파일 추가- 또는
application-persistence.yml의 locations 값을 실제 존재하는 경로로 수정
🤖 Prompt for AI Agents
infra/src/main/resources/application-persistence.yml around line 25: Flyway
locations points to non-existent classpath:db/reed so migrations will fail;
either create src/main/resources/db/reed and add your migration SQL files there
(matching Flyway naming/versioning), or update application-persistence.yml to
reference an existing migration folder (e.g., classpath:db/migration) and ensure
the referenced SQL files exist and follow Flyway naming conventions.
| --- | ||
| spring: | ||
| config: | ||
| activate: | ||
| on-profile: test | ||
| data: | ||
| redis: | ||
| host: localhost | ||
| port: 6379 | ||
| password: |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
💡 Verification agent
🧩 Analysis chain
CI/로컬 테스트에서 Redis 준비 여부 확인 필요
test 프로필이 로컬 Redis(127.0.0.1:6379, 비밀번호 없음)를 가정합니다. CI나 컨테이너 환경에서 Redis 인스턴스를 제공하지 않으면 통합/환경 테스트가 실패할 수 있습니다. 워크플로우에서 Redis 서비스를 띄우거나, Testcontainers/임베디드 Redis를 사용하도록 되어 있는지 확인해 주세요.
다음 스크립트로 워크플로우/코드에서 Redis 준비 과정을 점검할 수 있습니다:
🏁 Script executed:
#!/bin/bash
set -euo pipefail
echo "1) GitHub Actions 워크플로우에서 Redis 서비스 사용 여부 탐색"
rg -n -C2 -g '.github/workflows/**' -e 'redis' -e 'services:'
echo
echo "2) 테스트 코드에서 Redis Testcontainers/임베디드 사용 여부 탐색"
rg -n -C3 --type=kt --type=java -e 'testcontainers.*redis' -e 'GenericContainer\\(.*redis' -e 'EmbeddedRedis'Length of output: 327
아래 스크립트로 .github/workflows 디렉토리 존재 여부 및 워크플로우 내 Redis 서비스 설정, 테스트 코드에서 Testcontainers/Embedded Redis 사용 여부를 재확인해 주세요.
🏁 Script executed:
#!/usr/bin/env bash
set -euo pipefail
echo "=== .github/workflows 디렉토리 존재 여부 확인 ==="
if [ -d ".github/workflows" ]; then
echo ".github/workflows 디렉토리 발견"
echo
echo "=== 워크플로우 내 Redis 키워드 검색 ==="
rg -n 'redis' .github/workflows || echo "워크플로우에서 'redis' 키워드 미발견"
echo
echo "=== 워크플로우 내 services: 검색 ==="
rg -n 'services:' .github/workflows || echo "워크플로우에서 'services:' 미발견"
else
echo ".github/workflows 디렉토리가 없습니다."
fi
echo
echo "=== 테스트 코드 내 Testcontainers/Embedded Redis 사용 여부 검색 ==="
rg -n 'testcontainers.*redis' -g '*.java' -g '*.kt' . || echo "Testcontainers Redis 참조 미발견"
rg -n 'GenericContainer\s*\(\s*".*redis' -g '*.java' -g '*.kt' . || echo "GenericContainer(redis) 호출 미발견"
rg -n 'EmbeddedRedis' -g '*.java' -g '*.kt' . || echo "EmbeddedRedis 사용 미발견"Length of output: 1075
CI 환경에 Redis 서비스 추가 또는 Testcontainers/Embedded Redis 설정 필요
.github/workflows에 Redis 서비스 정의가 없고, 테스트 코드에도 Testcontainers/Embedded Redis 사용 로직이 없어 실제 Redis(127.0.0.1:6379)에 의존하고 있습니다. CI에서 통합/환경 테스트가 실패하지 않도록 아래 중 하나를 적용해 주세요:
.github/workflows/…yml파일의jobs.<job>.services에 Redis 서비스 추가services: redis: image: redis:6-alpine ports: - 6379:6379
- 테스트 코드에 Testcontainers 또는 Embedded Redis 설정 추가
@Testcontainers public class RedisIntegrationTest { @Container static GenericContainer<?> redis = new GenericContainer<>("redis:6") .withExposedPorts(6379); // ... }
위 조치 없이는 CI에서 테스트가 실패합니다.
🤖 Prompt for AI Agents
infra/src/main/resources/application-redis.yml around lines 8 to 17: tests are
currently configured to use a real Redis at localhost:6379 which CI does not
provide; update CI or tests so they don't rely on a host Redis — either add a
Redis service to the relevant .github/workflows/*.yml job services (define a
redis service using the official redis image and expose 6379) or modify the test
setup to start Redis via Testcontainers or an embedded Redis instance so tests
run in CI without a local Redis.
| host: localhost | ||
| port: 6379 | ||
| password: |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
🧹 Nitpick (assertive)
test 프로필에서 Redis 비밀번호가 null로 바인딩됩니다 — 빈 문자열로 명시 권장
YAML에서 password:에 값이 없으면 null로 바인딩됩니다. 일부 클라이언트/드라이버는 빈 문자열("")과 null을 다르게 취급할 수 있어, 테스트 환경에서의 혼선을 줄이려면 명시적으로 빈 문자열을 지정하는 편이 안전합니다.
적용 예시:
host: localhost
port: 6379
- password:
+ password: ""📝 Committable suggestion
‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.
| host: localhost | |
| port: 6379 | |
| password: | |
| host: localhost | |
| port: 6379 | |
| password: "" |
🤖 Prompt for AI Agents
infra/src/main/resources/application-redis.yml around lines 15 to 17: 현재
`password:`가 값 없이 남아 있어 YAML 파서는 null로 바인딩하므로 테스트 프로필에서 혼동이 발생할 수 있습니다; 빈 문자열으로
명시하려면 `password:`에 빈 문자열을 할당하도록 변경해 테스트 환경에서의 null/empty 차이를 제거하세요.
| "/swagger-ui/**", | ||
| "/v3/api-docs/**", | ||
| "/kakao-login.html/**", | ||
| "/favicon.ico", |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
favicon.ico를 열어준 이유가 궁금합니다~
minwoo1999
left a comment
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
이 부분은 적용하지 않았습니다. (민우님의 의견도 궁금해요!)
설정을 하면 보안상 이점이 있을 수 있지만, 이미 Actuator 포트와 경로를 환경변수로 처리했기 때문에 굳이 설정을 추가하지 않아도 될 것 같다고 판단했습니다.
만약 인증·권한 설정을 추가하면, 요청 시 토큰을 헤더에 담아야 하고, 만료일을 길게 설정하더라도 관리포인트가 늘어난다는 단점이 크게 느껴졌습니다.
-> 이부분에 대해서 동의합니다. 저희가 MVP를 빠르게 해서 출시해 내야하는 만큼 이부분은 trade off를 생각해서 인증없이 진행해도 좋아보입니다 고생하셨습니다
|


🔗 관련 이슈
📘 작업 유형
📙 작업 내역
https://techblog.woowahan.com/9232/
밑의 설명을 이해하기 위해서는 위의 블로그를 보고 오시는 것을 추천합니다.
1. actuator path 및 port 변경(Actuator는 서비스 운영에 사용되는 포트와 다른 포트를 사용한다. Actuator Default 경로를 사용하지 않고, 경로를 변경하여 운영한다.)
2. Actuator endpoint는 all disable 상태에서 필요한 것만 include하여 화이트리스트 형태로 운영
3. HTTP(WEB) 엔드포인트 설정
4. JMX형태로 Actuator 사용이 필요하지 않을 경우, 반드시 disable한다.
5. Actuator에 접근할 때에는, 인증되었으며 권한이 있는 사용자만이 접근가능하도록 제어한다?
🧪 테스트 내역
🎨 스크린샷 또는 시연 영상 (선택)
✅ PR 체크리스트
💬 추가 설명 or 리뷰 포인트 (선택)
Summary by CodeRabbit
신규 기능
변경
기타