Feat: 모듈 리팩토링/auth 모듈 추가#30
Conversation
Walkthrough프로젝트를 멀티모듈 구조로 재편성하고, 기존 api 모듈을 제거했다. auth, member, security, contracts-auth, platform-api 등의 모듈을 신설/이동했으며, JWT 발급·검증과 Spring Security 구성을 libs/security로 분리했다. 개발용 로그인 흐름과 회원 조회 포트를 재배치하고, 관련 컨트롤러·서비스·어댑터·테스트를 플랫폼 하위로 이동/추가했다. Changes
Sequence Diagram(s)sequenceDiagram
autonumber
actor Dev as Developer
participant API as DevelopmentLoginController
participant App as DevelopmentLoginService
participant MQF as MemberQueryFacade (member-app)
participant MFA as MemberFacadeAdapter (auth-adapter)
participant TK as TokenAdapter
participant JI as JwtIssuer (libs/security)
Dev->>API: POST /api/v1/dev/auth/login {oauthId}
API->>App: login(oauthId)
App->>MFA: loadMemberByOAuth(oauthId, GOOGLE)
MFA->>MQF: findAuthMemberByOAuth(oauthId, "GOOGLE")
MQF-->>MFA: AuthMemberView
MFA-->>App: AuthMember
App->>TK: issue(AuthMember)
TK->>JI: issue({ sub: member.id })
JI-->>TK: JWT
TK-->>App: JWT
App-->>API: JWT
API-->>Dev: 200 {accessToken}
sequenceDiagram
autonumber
actor User
participant Sec as Spring Security (libs/security)
participant Dec as JwtDecoder
participant SCm as SecurityCurrentMember
participant Ctl as MemberController
participant Svc as MemberQueryService
User->>Sec: GET /api/v1/member/me (Authorization: Bearer ...)
Sec->>Dec: decode(token)
Dec-->>Sec: claims (sub)
Sec->>SCm: resolve current member id
SCm-->>Sec: id (Long)
Sec->>Ctl: forward request (authenticated)
Ctl->>Svc: getMyInfo()
Svc-->>Ctl: {"id": memberId}
Ctl-->>User: 200 {"id": ...}
Estimated code review effort🎯 4 (Complex) | ⏱️ ~75 minutes Poem
Pre-merge checks and finishing touches❌ Failed checks (1 warning)
✅ Passed checks (4 passed)
✨ Finishing touches
🧪 Generate unit tests
Tip 👮 Agentic pre-merge checks are now available in preview!Pro plan users can now enable pre-merge checks in their settings to enforce checklists before merging PRs.
Please see the documentation for more information. Example: reviews:
pre_merge_checks:
custom_checks:
- name: "Undocumented Breaking Changes"
mode: "warning"
instructions: |
Pass/fail criteria: All breaking changes to public APIs, CLI flags, environment variables, configuration keys, database schemas, or HTTP/GraphQL endpoints must be documented in the "Breaking Change" section of the PR description and in CHANGELOG.md. Exclude purely internal or private changes (e.g., code not exported from package entry points or explicitly marked as internal).Please share your feedback with us on this Discord post. 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. Comment |
There was a problem hiding this comment.
Actionable comments posted: 16
Caution
Some comments are outside the diff and can’t be posted inline due to platform limitations.
⚠️ Outside diff range comments (4)
platform/services/api/src/main/resources/application.yml (1)
18-18: JWT 만료 속성 키 불일치 — 로컬 오버라이드가 적용되지 않습니다platform/services/api/src/main/resources/application.yml: 기본 프로파일(18행)에는
access-token-expiration: 1h, 로컬 프로파일(29행)에는access-ttl: 1d가 있어 로컬 값이 적용되지 않습니다. 키를 통일하세요.auth: jwt: - access-ttl: 1d + access-token-expiration: 1dplatform/auth/adapter/src/main/kotlin/app/cardcapture/auth/adapter/inbound/web/dto/DevelopmentLoginRequest.kt (1)
3-5: 요청 DTO 유효성 검증 추가 필요(@notblank 등)입력 공백/누락을 방지하기 위해 Bean Validation을 붙이는 것이 안전합니다.
package app.cardcapture.auth.adapter.inbound.web.dto +import jakarta.validation.constraints.NotBlank + data class DevelopmentLoginRequest( - val oauthId: String, + @field:NotBlank(message = "oauthId must not be blank") + val oauthId: String, )platform/auth/adapter/src/main/kotlin/app/cardcapture/auth/adapter/outbound/jwt/JwtProperties.kt (1)
3-12: JWT 설정 강건화: @validated + 비밀키 최소 길이 검증ConfigurationProperties에 검증을 추가해 약한 키/오입력을 조기에 차단하세요(HS256 기준 32바이트 이상 권장).
package app.cardcapture.auth.adapter.outbound.jwt import org.springframework.boot.context.properties.ConfigurationProperties +import org.springframework.validation.annotation.Validated +import jakarta.validation.constraints.NotBlank +import jakarta.validation.constraints.Size import java.time.Duration -@ConfigurationProperties(prefix = "auth.jwt") +@Validated +@ConfigurationProperties(prefix = "auth.jwt") data class JwtProperties( - val issuer: String, + @field:NotBlank + val issuer: String, val accessTokenExpiration: Duration, - val secretKey: String + @field:NotBlank + @field:Size(min = 32, message = "HS256 secret must be >= 32 characters (256-bit).") + val secretKey: String )platform/member/adapter/src/main/kotlin/app/cardcapture/member/adapter/outbound/persistence/MemberJpaEntity.kt (1)
13-26: OAuth 식별자 유일성 보장(복합 Unique)과 인덱스가 필요합니다
(oauth_provider, oauth_id)조합은 논리적으로 유일해야 합니다. 현재 제약이 없어 중복 Member가 생성될 수 있습니다. DB 차원의 제약과 조회 성능을 위해 인덱스도 추가해 주세요.import jakarta.persistence.Table +import jakarta.persistence.Index +import jakarta.persistence.UniqueConstraint @Entity -@Table(name = "members") +@Table( + name = "members", + uniqueConstraints = [ + UniqueConstraint( + name = "uk_members_oauth_provider_id", + columnNames = ["oauth_provider", "oauth_id"] + ) + ], + indexes = [ + Index( + name = "idx_members_oauth_provider_id", + columnList = "oauth_provider, oauth_id" + ) + ] +) class MemberJpaEntity( @@ - @Column(nullable = false) + @Column(nullable = false) @Enumerated(EnumType.STRING) var oauthProvider: JpaOAuthProvider, - @Column(nullable = false) + // MySQL InnoDB의 prefix index 제약 등을 고려해 길이 지정을 권장합니다. + @Column(nullable = false, length = 191) var oauthId: String
♻️ Duplicate comments (1)
platform/member/domain/src/main/kotlin/app/cardcapture/member/domain/Member.kt (1)
4-4: 비-NULL id 설계 관련(중복 코멘트 안내)
IDENTITY전략과의 상호작용은 어댑터 코멘트(파일: MemberJpaEntity.kt)에서 자세히 요청드렸습니다. 거기 논의에 따르는 것이 좋겠습니다.
🧹 Nitpick comments (30)
platform/auth/adapter/src/test/kotlin/app/cardcapture/auth/adapter/inbound/web/AuthTestApplication.kt (2)
6-7: detekt EmptyClassBlock 경고 억제 권장빈 클래스 경고를 억제해 정적 분석 잡음 줄입시다.
적용 diff:
@SpringBootApplication -class AuthTestApplication { +@Suppress("EmptyClassBlock") +class AuthTestApplication { }
3-6: 테스트 컨텍스트 최소화로 부팅 시간 단축 제안DB를 사용하지 않는 테스트라면 자동 구성 제외로 속도/안정성 개선 가능합니다.
적용 예시:
-import org.springframework.boot.autoconfigure.SpringBootApplication +import org.springframework.boot.autoconfigure.SpringBootApplication +import org.springframework.boot.autoconfigure.jdbc.DataSourceAutoConfiguration -@SpringBootApplication +@SpringBootApplication(exclude = [DataSourceAutoConfiguration::class]) class AuthTestApplication { }필요 시 다른 불필요한 AutoConfiguration도 추가 제외 가능합니다. 원하시면 대상 목록 정리해드릴게요.
platform/services/api/src/main/resources/application.yml (1)
31-33: Spring Security DEBUG 로그 범위를 더 좁혀 주세요(노이즈·민감정보 노출 완화)
org.springframework.security: DEBUG는 로그가 매우 방대하고, 헤더/인증 관련 민감정보가 노출될 리스크가 있습니다. 로컬 프로필이라도 서브 패키지로 범위를 축소하는 편이 안전합니다.예: 웹 필터 체인/인증만 디버그하고, 나머지는 INFO 유지
logging: level: - org.springframework.security: DEBUG + org.springframework.security: INFO + org.springframework.security.web: DEBUG + org.springframework.security.authentication: DEBUGplatform/auth/adapter/src/main/kotlin/app/cardcapture/auth/adapter/outbound/jwt/JwtProperties.kt (1)
3-12: 비밀키 취급 개선(옵션): Base64 키 사용 또는 toString 노출 방지운영 로깅에서 비밀이 노출되지 않도록 Base64 인코딩 키를 구성에 저장하고, Bean 생성 시 디코드해서 사용하거나(예: SecretKeySpec), 민감 설정의 toString이 로그에 찍히지 않도록 주의하세요.
platform/auth/application/src/main/kotlin/app/cardcapture/auth/application/port/inbound/DevelopmentLoginUseCase.kt (1)
3-6: 포트 시그니처 확장성 개선(결과 DTO·커맨드 도입 고려)String 토큰 반환은 확장성/표현력이 낮습니다. 만료시각·타입 등 포함하는 Result DTO로 반환하고, 입력도 Command로 감싸면 추후 필드 추가가 용이합니다.
예) fun login(command: DevelopmentLoginCommand): DevelopmentLoginResult
platform/member/application/src/main/kotlin/app/cardcapture/member/application/port/inbound/dto/AuthMemberView.kt (2)
3-8: 불필요한 빈 클래스 블록 제거 (detekt 경고 해소).빈 본문 때문에 detekt EmptyClassBlock 경고가 납니다. 본문을 제거해 한 줄 선언으로 정리하세요.
-data class AuthMemberView ( - val id: Long, - val oauthId: String, - val oauthProvider: String, -){ -} +data class AuthMemberView( + val id: Long, + val oauthId: String, + val oauthProvider: String, +)
6-6: oauthProvider를 String 대신 enum으로 노출해 타입 안정성 강화.내부 애플리케이션 포트 DTO라면 도메인
OAuthProvider를 직접 사용해 문자열 오타/불일치를 막는 편이 안전합니다. 서비스 구현부의.name변환도 제거 가능합니다.-package app.cardcapture.member.application.port.inbound.dto +package app.cardcapture.member.application.port.inbound.dto +import app.cardcapture.member.domain.OAuthProvider @@ -data class AuthMemberView( +data class AuthMemberView( val id: Long, val oauthId: String, - val oauthProvider: String, + val oauthProvider: OAuthProvider, )호출부/매퍼에서 문자열 변환을 제거했는지 확인해 주세요. 필요 시 변경 사항 반영하도록 후속 PR도 도와드릴게요.
platform/member/adapter/src/main/kotlin/app/cardcapture/member/adapter/outbound/persistence/JpaOAuthProvider.kt (1)
6-20: 도메인 enum 이중화 최소화 검토.현재 어댑터 enum과 도메인 enum이 이름 매핑으로만 연결되어 있어 값 추가 시 양쪽 동시 변경이 필요합니다. 가능한 경우 JPA 필드를
@Enumerated(EnumType.STRING)로 도메인 enum에 직접 매핑하거나, DB에는 문자열만 저장하고 어그리게이트 변환에서 enum으로 매핑하는 방식으로 중복을 줄이는 것을 검토해 주세요. 최소한 잘못된 DB 값에 대한 가드(명확한 예외 메시지)도 고려할 수 있습니다.platform/auth/adapter/src/main/kotlin/app/cardcapture/auth/adapter/inbound/web/dto/LoginResponse.kt (2)
4-8: 빈 본문 제거로 간결화.본문이 비어 있으므로 한 줄 선언으로 정리하세요.
-data class LoginResponse( - val accessToken: String -) { - -} +data class LoginResponse(val accessToken: String)
4-6: 토큰 메타데이터 포함 고려.클라이언트 편의성을 위해
tokenType("Bearer"),expiresIn(초) 등을 함께 반환하면 후단/전단 결합이 느슨해집니다. 변경 시 컨트롤러/문서 동반 수정 필요합니다.platform/services/api/src/main/kotlin/app/cardcapture/api/platform/PlatformApiApplication.kt (1)
11-22: 스캔 범위 축소 또는 패키지 루트 이동 검토.
scanBasePackages = ["app.cardcapture"]와 개별@EntityScan,@EnableJpaRepositories는 동작엔 문제 없지만, 애플리케이션 클래스를 루트 패키지(app.cardcapture)로 이동하면 기본 스캔만으로 커버되어 설정이 단순해집니다. 모듈 구조가 안정되면 정리 검토 부탁드립니다.settings.gradle.kts (1)
26-31: libs 모듈 명세 명확화.향후 libs가 늘어날 경우를 대비해
include("libs:security")형태의 네임스페이스 도입이나, settings에서 공통 prefix를 상수로 관리하면 가독성이 좋아집니다. 지금은 선택 사항입니다.libs/security/src/main/kotlin/app/cardcapture/lib/security/SecurityConfig.kt (1)
65-76: CORS 허용 범위 프로퍼티화 권장.운영 환경에서
allowedOrigins = ["*"]는 과도합니다.auth.cors.allowed-origins같은 프로퍼티로 외부화해 서비스별로 조정 가능하게 해주세요. 필요한 경우 구현 예시 제공 가능합니다.libs/contracts-auth/src/main/kotlin/app/cardcapture/lib/contracts/auth/CurrentMember.kt (1)
3-11: 계약(KDoc)으로 동작 보증을 명확히 해주세요현재 인터페이스가
id만 노출하고 동작(인증 실패 시 동작, ID 제약 등)은 구현체에 암묵적으로 맡겨져 있습니다. KDoc으로 계약을 고정해 두면 호출측/구현측 혼선을 줄일 수 있습니다.package app.cardcapture.lib.contracts.auth -/* -* 현재 인증된 사용자의 컨텍스트 추상화 -* - 실제 구현체는 libs/security 모듈에서 제공한다 -*/ +/** + * 현재 인증된 사용자의 컨텍스트 추상화. + * - 구현체는 인증 실패 혹은 식별 불가 시 일관된 예외를 던져야 합니다(예: IllegalStateException). + * - [id]는 영속 Member의 PK(양수)여야 합니다. + * - 실제 구현체는 libs/security 모듈에서 제공합니다. + */ interface CurrentMember { val id: Long }platform/auth/domain/src/main/kotlin/app/cardcapture/auth/domain/AuthMember.kt (1)
3-8: DTO 성격이면 data class로 단순화동등성/디버깅/복사 편의성이 필요하다면 data class가 적합합니다. 도메인 엔티티가 아닌 인증 전용 전달 모델로 보입니다.
-class AuthMember ( +data class AuthMember( val id: Long, val oauthId: String, val oauthProvider: OAuthProvider, -){ -} +)platform/auth/application/src/main/kotlin/app/cardcapture/auth/application/port/outbound/IssueTokenPort.kt (1)
6-10: 토큰 메타데이터를 함께 반환하는 계약 고려만료시각/토큰타입/추가 클레임 등 메타가 필요해지는 순간이 많습니다. 문자열 하나보다는 값 객체 반환이 확장에 유리합니다.
interface IssueTokenPort { - fun issue(member: AuthMember): String + fun issue(member: AuthMember): IssuedToken }다른 파일(예: same package)에 아래 VO 추가를 제안합니다:
data class IssuedToken( val accessToken: String, val expiresAtEpochSeconds: Long, val tokenType: String = "Bearer" )platform/member/application/src/main/kotlin/app/cardcapture/member/application/port/outbound/LoadMemberPort.kt (1)
8-9: 사소한 서식 + 메서드 명확성
- 서식: 파라미터 쉼표 앞 공백 제거
- 네이밍:
findByOAuth→findByOAuthIdAndProvider가 검색 기준을 더 선명히 드러냅니다(선택)- fun findByOAuth(oauthId: String , oauthProvider: OAuthProvider): Member? + fun findByOAuth(oauthId: String, oauthProvider: OAuthProvider): Member?platform/member/adapter/src/main/kotlin/app/cardcapture/member/adapter/inbound/web/MemberController.kt (2)
3-7: 엔드포인트 접근 제어를 메서드 레벨로 명시글로벌 보안 설정이 있더라도, 민감 엔드포인트는 메서드 레벨에서
@PreAuthorize로 의도를 고정해 두는 것을 권장합니다.import app.cardcapture.member.application.port.inbound.MemberQueryFacade import org.springframework.http.ResponseEntity import org.springframework.web.bind.annotation.GetMapping import org.springframework.web.bind.annotation.RequestMapping import org.springframework.web.bind.annotation.RestController +import org.springframework.security.access.prepost.PreAuthorize
15-19: Map 대신 명시적 DTO를 반환하세요반환 스키마가 흔들리기 쉬운
Map<String, Any>보다는 DTO가 계약 안정성과 문서화(OpenAPI)에 유리합니다. 추후 변경 범위를 최소화합니다.- @GetMapping("/me") - fun findMyInfo(): ResponseEntity<Map<String, Any>> { - val response = memberQueryFacade.getMyInfo() - return ResponseEntity.ok(response) - } + @GetMapping("/me") + @PreAuthorize("isAuthenticated()") + fun findMyInfo(): ResponseEntity<MeResponse> { + val payload = memberQueryFacade.getMyInfo() + return ResponseEntity.ok(MeResponse(id = payload["id"] as Long)) + }다른 파일(예: same package)에 DTO 추가:
data class MeResponse(val id: Long)platform/auth/adapter/src/test/kotlin/app/cardcapture/auth/adapter/inbound/web/DevelopmentLoginControllerProdTest.kt (1)
12-27: 404 단언에 더해 컨트롤러 미등록을 검증해 테스트 신뢰도 향상현재 테스트는 “어떤 이유로든” 404면 통과합니다. prod 프로파일에서 Dev 로그인 컨트롤러가 로드되지 않았음을 함께 검증하면 의도가 명확해집니다.
import org.springframework.test.web.servlet.post +import org.springframework.context.ApplicationContext +import org.assertj.core.api.Assertions.assertThat @@ class DevelopmentLoginControllerProdTest( - @Autowired private val mockMvc: MockMvc + @Autowired private val mockMvc: MockMvc, + @Autowired private val ctx: ApplicationContext ) { @Test fun `prod 환경에서 로그인 실패(404)`() { + // Dev 전용 컨트롤러가 Bean으로 등록되지 않았음을 보장 + assertThat(ctx.getBeansOfType(DevelopmentLoginController::class.java)).isEmpty() + mockMvc.post("/api/v1/dev/auth/login") { contentType = MediaType.APPLICATION_JSON content = """{"oauthId":"test-user"}""" }.andExpect { status { isNotFound() } } } }platform/member/domain/src/main/kotlin/app/cardcapture/member/domain/Member.kt (1)
3-8: 도메인 불변식 명시
oauthId공백 금지 등 기본 불변식을init에서 보장해 두면 하위 계층에서의 방어 로직이 단순해집니다.class Member ( val id: Long, val oauthProvider: OAuthProvider, val oauthId : String, ) { + init { + require(oauthId.isNotBlank()) { "oauthId must not be blank" } + } }platform/auth/application/src/test/kotlin/app/cardcapture/auth/application/service/DevelopmentLoginServiceTest.kt (2)
28-29: stub을 검증값과 일치시키기검증에서
OAuthProvider.GOOGLE.name을 기대하므로 stub도 동일 값으로 고정해 플래키함을 줄이세요.- every { loadMemberPort.loadMemberByOAuth(oauthId, any()) } returns member + every { loadMemberPort.loadMemberByOAuth(oauthId, OAuthProvider.GOOGLE.name) } returns member(포트가 enum으로 변경되면
.name제거)
23-39: 미등록 사용자 케이스 테스트 추가 제안등록되지 않은 oauthId일 때 예외/반환정책(예: 404, 특정 Exception)을 검증하는 테스트를 하나 더 두세요.
원하시면 스켈레톤 테스트 코드를 만들어 드리겠습니다.
platform/member/adapter/src/main/kotlin/app/cardcapture/member/adapter/outbound/persistence/MemberPersistenceAdapter.kt (1)
13-19: 조회 트랜잭션 readOnly 지정 + 네이밍 일관성
- JPA 읽기 경로에
@Transactional(readOnly = true)를 권장합니다(여기 또는 서비스 계층).- 메서드/레포지토리 간
OAuth/Oauth표기 일관화를 추후 정리하면 검색성 향상에 도움이 됩니다.예시:
+import org.springframework.transaction.annotation.Transactional ... - override fun findByOAuth(oauthId: String, oauthProvider: OAuthProvider): Member? { + @Transactional(readOnly = true) + override fun findByOAuth(oauthId: String, oauthProvider: OAuthProvider): Member? {platform/auth/adapter/src/main/kotlin/app/cardcapture/auth/adapter/outbound/jwt/JwtTokenAdapter.kt (1)
18-28: jjwt 버전에 따른 signWith 명시 여부 확인jjwt 0.12+는 알고리즘을 키에서 유추하지만, 명시적으로 지정하는 것이 안전합니다. 사용 버전을 확인해 다음 중 하나를 적용하세요.
- 0.12+:
- .signWith(accessKey) + .signWith(accessKey, io.jsonwebtoken.Jwts.SIG.HS256)
- (< 0.11.x 환경이라면 기존 방식 유지 또는
SignatureAlgorithm.HS256명시)추가로
jti(토큰 ID) 부여와 필요 클레임(예: provider)을 검토해 주세요.platform/auth/adapter/src/test/kotlin/app/cardcapture/auth/adapter/inbound/web/DevelopmentLoginControllerDevTest.kt (1)
25-37: dev 프로파일 컨트롤러 테스트 구성 적절슬라이스 테스트로 서비스 목킹, 응답 구조 검증까지 깔끔합니다.
andExpect { content { contentTypeCompatibleWith(MediaType.APPLICATION_JSON) } }추가 권장.- 유효성 실패(필드 누락) 및 잘못된 프로파일(prod)에서의 404 테스트도 있으면 좋습니다.
platform/member/application/src/main/kotlin/app/cardcapture/member/application/port/service/MemberQueryService.kt (1)
26-29: Map 반환 대신 DTO 권장
getMyInfo()가Map<String, Any>를 반환하면 스키마가 느슨해집니다. 얇은 DTO(MyInfoView(val id: Long))로 교체를 권장합니다.원하시면 DTO 및 어댑터 계층(컨트롤러)의 시그니처 변경까지 포함한 패치를 제안드리겠습니다.
platform/member/adapter/build.gradle.kts (1)
16-19: starter-data-jpa 의존성 필요성 재확인웹 어댑터에서 JPA를 사용하지 않으면 제거 대상입니다. 추후 의존성 지도를 단순화합니다.
위 스크립트 결과에 따라 제거 여부 결정 바랍니다.
build.gradle.kts (2)
28-35: BOM 중복/드리프트 위험 — Boot 플러그인 버전과 BOM 버전 동기화Boot 플러그인을 쓰는 서브모듈은 이미
spring-boot-dependencies가 적용됩니다. 별도 BOM import는 중복 가능성이 있고, 플러그인 버전과 문자열 관리가 어긋나면 드리프트가 납니다. 공통 버전 소스(gradle.properties 또는 Version Catalog)로 일원화해 주세요.가능한 정리 방향:
- gradle.properties에
springBootVersion=3.5.4정의 후 플러그인/ BOM에서 모두 참조.- 또는 Boot 플러그인을 적용하는 모듈에서는 별도 BOM import 생략.
41-42: 빈 dependencies 블록 제거노이즈만 추가합니다. 삭제 권장.
-dependencies { -} +
📜 Review details
Configuration used: Path: .coderabbit.yaml
Review profile: CHILL
Plan: Pro
⛔ Files ignored due to path filters (2)
api/gradle/wrapper/gradle-wrapper.jaris excluded by!**/*.jargradle/wrapper/gradle-wrapper.jaris excluded by!**/*.jar
📒 Files selected for processing (57)
api/build.gradle.kts(0 hunks)api/gradle/wrapper/gradle-wrapper.properties(0 hunks)api/http/memberRequest.http(0 hunks)api/src/main/kotlin/app/api/ApiApplication.kt(0 hunks)api/src/main/kotlin/app/api/member/adapter/outbound/jwt/JwtConfig.kt(0 hunks)api/src/main/kotlin/app/api/member/application/port/outbound/IssueTokenPort.kt(0 hunks)api/src/main/kotlin/app/api/member/application/port/outbound/LoadMemberPort.kt(0 hunks)api/src/main/kotlin/app/api/member/application/service/DevelopmentLoginService.kt(0 hunks)api/src/test/kotlin/app/api/ApiApplicationTests.kt(0 hunks)api/src/test/kotlin/app/api/member/adapter/inbound/web/DevelopmentLoginControllerProfileTests.kt(0 hunks)api/src/test/kotlin/app/api/member/application/service/DevelopmentLoginServiceTest.kt(0 hunks)build.gradle.kts(1 hunks)gradlew(1 hunks)libs/contracts-auth/build.gradle.kts(1 hunks)libs/contracts-auth/src/main/kotlin/app/cardcapture/lib/contracts/auth/CurrentMember.kt(1 hunks)libs/security/build.gradle.kts(1 hunks)libs/security/src/main/kotlin/app/cardcapture/lib/security/CustomAuthenticationEntryPoint.kt(1 hunks)libs/security/src/main/kotlin/app/cardcapture/lib/security/SecurityConfig.kt(1 hunks)libs/security/src/main/kotlin/app/cardcapture/lib/security/SecurityCurrentMember.kt(1 hunks)platform/auth/adapter/build.gradle.kts(1 hunks)platform/auth/adapter/src/main/kotlin/app/cardcapture/auth/adapter/inbound/web/DevelopmentLoginController.kt(1 hunks)platform/auth/adapter/src/main/kotlin/app/cardcapture/auth/adapter/inbound/web/dto/DevelopmentLoginRequest.kt(1 hunks)platform/auth/adapter/src/main/kotlin/app/cardcapture/auth/adapter/inbound/web/dto/LoginResponse.kt(1 hunks)platform/auth/adapter/src/main/kotlin/app/cardcapture/auth/adapter/outbound/client/MemberFacadeAdapter.kt(1 hunks)platform/auth/adapter/src/main/kotlin/app/cardcapture/auth/adapter/outbound/jwt/JwtConfig.kt(1 hunks)platform/auth/adapter/src/main/kotlin/app/cardcapture/auth/adapter/outbound/jwt/JwtProperties.kt(1 hunks)platform/auth/adapter/src/main/kotlin/app/cardcapture/auth/adapter/outbound/jwt/JwtTokenAdapter.kt(2 hunks)platform/auth/adapter/src/test/kotlin/app/cardcapture/auth/adapter/inbound/web/AuthTestApplication.kt(1 hunks)platform/auth/adapter/src/test/kotlin/app/cardcapture/auth/adapter/inbound/web/DevelopmentLoginControllerDevTest.kt(1 hunks)platform/auth/adapter/src/test/kotlin/app/cardcapture/auth/adapter/inbound/web/DevelopmentLoginControllerProdTest.kt(1 hunks)platform/auth/application/build.gradle.kts(1 hunks)platform/auth/application/src/main/kotlin/app/cardcapture/auth/application/port/inbound/DevelopmentLoginUseCase.kt(1 hunks)platform/auth/application/src/main/kotlin/app/cardcapture/auth/application/port/outbound/IssueTokenPort.kt(1 hunks)platform/auth/application/src/main/kotlin/app/cardcapture/auth/application/port/outbound/LoadAuthMemberPort.kt(1 hunks)platform/auth/application/src/main/kotlin/app/cardcapture/auth/application/service/DevelopmentLoginService.kt(1 hunks)platform/auth/application/src/test/kotlin/app/cardcapture/auth/application/service/DevelopmentLoginServiceTest.kt(1 hunks)platform/auth/domain/build.gradle.kts(1 hunks)platform/auth/domain/src/main/kotlin/app/cardcapture/auth/domain/AuthMember.kt(1 hunks)platform/auth/domain/src/main/kotlin/app/cardcapture/auth/domain/OAuthProvider.kt(1 hunks)platform/member/adapter/build.gradle.kts(1 hunks)platform/member/adapter/src/main/kotlin/app/cardcapture/member/adapter/inbound/web/MemberController.kt(1 hunks)platform/member/adapter/src/main/kotlin/app/cardcapture/member/adapter/outbound/persistence/JpaOAuthProvider.kt(1 hunks)platform/member/adapter/src/main/kotlin/app/cardcapture/member/adapter/outbound/persistence/MemberJpaEntity.kt(2 hunks)platform/member/adapter/src/main/kotlin/app/cardcapture/member/adapter/outbound/persistence/MemberJpaRepository.kt(1 hunks)platform/member/adapter/src/main/kotlin/app/cardcapture/member/adapter/outbound/persistence/MemberPersistenceAdapter.kt(1 hunks)platform/member/application/build.gradle.kts(1 hunks)platform/member/application/src/main/kotlin/app/cardcapture/member/application/port/inbound/MemberQueryFacade.kt(1 hunks)platform/member/application/src/main/kotlin/app/cardcapture/member/application/port/inbound/dto/AuthMemberView.kt(1 hunks)platform/member/application/src/main/kotlin/app/cardcapture/member/application/port/outbound/LoadMemberPort.kt(1 hunks)platform/member/application/src/main/kotlin/app/cardcapture/member/application/port/service/MemberQueryService.kt(1 hunks)platform/member/domain/build.gradle.kts(1 hunks)platform/member/domain/src/main/kotlin/app/cardcapture/member/domain/Member.kt(1 hunks)platform/member/domain/src/main/kotlin/app/cardcapture/member/domain/OAuthProvider.kt(1 hunks)platform/services/api/build.gradle.kts(1 hunks)platform/services/api/src/main/kotlin/app/cardcapture/api/platform/PlatformApiApplication.kt(1 hunks)platform/services/api/src/main/resources/application.yml(1 hunks)settings.gradle.kts(1 hunks)
💤 Files with no reviewable changes (11)
- api/gradle/wrapper/gradle-wrapper.properties
- api/http/memberRequest.http
- api/src/test/kotlin/app/api/member/application/service/DevelopmentLoginServiceTest.kt
- api/src/test/kotlin/app/api/ApiApplicationTests.kt
- api/src/main/kotlin/app/api/member/adapter/outbound/jwt/JwtConfig.kt
- api/src/test/kotlin/app/api/member/adapter/inbound/web/DevelopmentLoginControllerProfileTests.kt
- api/src/main/kotlin/app/api/member/application/port/outbound/IssueTokenPort.kt
- api/build.gradle.kts
- api/src/main/kotlin/app/api/ApiApplication.kt
- api/src/main/kotlin/app/api/member/application/port/outbound/LoadMemberPort.kt
- api/src/main/kotlin/app/api/member/application/service/DevelopmentLoginService.kt
🧰 Additional context used
🧬 Code graph analysis (5)
platform/member/application/build.gradle.kts (4)
libs/contracts-auth/src/main/kotlin/app/cardcapture/lib/contracts/auth/CurrentMember.kt (1)
id(7-11)platform/auth/domain/src/main/kotlin/app/cardcapture/auth/domain/AuthMember.kt (1)
id(3-8)platform/member/application/src/main/kotlin/app/cardcapture/member/application/port/inbound/dto/AuthMemberView.kt (1)
id(3-8)platform/member/domain/src/main/kotlin/app/cardcapture/member/domain/Member.kt (1)
id(3-8)
libs/security/build.gradle.kts (2)
libs/contracts-auth/src/main/kotlin/app/cardcapture/lib/contracts/auth/CurrentMember.kt (1)
id(7-11)libs/security/src/main/kotlin/app/cardcapture/lib/security/SecurityCurrentMember.kt (1)
id(7-18)
platform/auth/application/build.gradle.kts (1)
platform/auth/domain/src/main/kotlin/app/cardcapture/auth/domain/AuthMember.kt (1)
id(3-8)
platform/member/adapter/build.gradle.kts (3)
platform/auth/domain/src/main/kotlin/app/cardcapture/auth/domain/AuthMember.kt (1)
id(3-8)platform/member/application/src/main/kotlin/app/cardcapture/member/application/port/inbound/dto/AuthMemberView.kt (1)
id(3-8)platform/member/domain/src/main/kotlin/app/cardcapture/member/domain/Member.kt (1)
id(3-8)
platform/services/api/build.gradle.kts (5)
libs/contracts-auth/src/main/kotlin/app/cardcapture/lib/contracts/auth/CurrentMember.kt (1)
id(7-11)libs/security/src/main/kotlin/app/cardcapture/lib/security/SecurityCurrentMember.kt (1)
id(7-18)platform/auth/domain/src/main/kotlin/app/cardcapture/auth/domain/AuthMember.kt (1)
id(3-8)platform/member/application/src/main/kotlin/app/cardcapture/member/application/port/inbound/dto/AuthMemberView.kt (1)
id(3-8)platform/member/domain/src/main/kotlin/app/cardcapture/member/domain/Member.kt (1)
id(3-8)
🪛 detekt (1.23.8)
platform/auth/adapter/src/test/kotlin/app/cardcapture/auth/adapter/inbound/web/AuthTestApplication.kt
[warning] 6-7: The class or object AuthTestApplication is empty.
(detekt.empty-blocks.EmptyClassBlock)
platform/member/application/src/main/kotlin/app/cardcapture/member/application/port/inbound/dto/AuthMemberView.kt
[warning] 7-8: The class or object AuthMemberView is empty.
(detekt.empty-blocks.EmptyClassBlock)
platform/auth/application/src/test/kotlin/app/cardcapture/auth/application/service/DevelopmentLoginServiceTest.kt
[warning] 7-8: The class or object AuthMember is empty.
(detekt.empty-blocks.EmptyClassBlock)
🔇 Additional comments (24)
platform/auth/adapter/src/test/kotlin/app/cardcapture/auth/adapter/inbound/web/AuthTestApplication.kt (1)
1-7: 테스트용 부트스트랩 클래스 추가 LGTM테스트 컨텍스트 부팅용 최소 구성으로 적절합니다.
gradlew (1)
4-4: Gradle Wrapper 수동 수정 지양 — JAR/URL 무결성 확인 필요gradle/wrapper/gradle-wrapper.properties의 distributionUrl이 다음으로 설정되어 있습니다: distributionUrl=https://services.gradle.org/distributions/gradle-8.14.3-bin.zip
sandbox에서 sha256sum이 없어 gradle/wrapper/gradle-wrapper.jar의 SHA‑256을 계산하지 못했습니다. 로컬에서 아래 명령 중 하나를 실행해 해시 결과를 붙여넣으세요:
- Linux: sha256sum gradle/wrapper/gradle-wrapper.jar
- macOS: shasum -a 256 gradle/wrapper/gradle-wrapper.jar
- 대안(OpenSSL): openssl dgst -sha256 gradle/wrapper/gradle-wrapper.jar
wrapper는 반드시 gradle wrapper 태스크로만 갱신하고, distributionUrl과 JAR SHA‑256이 공식 배포값과 일치하는지 확인하세요.
platform/member/domain/src/main/kotlin/app/cardcapture/member/domain/OAuthProvider.kt (1)
1-5: 중복 enum 정의로 인한 타입 단절 위험 — 단일 소스로 통합 필요 (검증 미완료)rg가 파일을 검색하지 않아 중복 정의 확인을 수행하지 못했습니다. 아래 수정된 검증 스크립트를 실행한 출력(rg 결과 또는 --debug 출력)을 코멘트에 첨부하세요.
위치: platform/member/domain/src/main/kotlin/app/cardcapture/member/domain/OAuthProvider.kt (1–5줄)
임시 완화(diff, 장기적으로는 공용 계약 모듈로 이동 권장):
-package app.cardcapture.member.domain - -enum class OAuthProvider { - GOOGLE -} +package app.cardcapture.member.domain + +typealias OAuthProvider = app.cardcapture.auth.domain.OAuthProvider검증 스크립트:
#!/bin/bash # rg가 .gitignore나 hidden 파일을 건너뛸 수 있으므로 --hidden/--no-ignore 사용 rg -n --hidden --no-ignore -C2 '^\s*enum\s+class\s+OAuthProvider\b' || true rg -n --hidden --no-ignore 'app\.cardcapture\.(member|auth)\.domain\.OAuthProvider' || true # 아무 결과도 없으면 이유 확인을 위해 --debug 출력 포함 rg --debug -n --hidden --no-ignore '^\s*enum\s+class\s+OAuthProvider\b' || true실행 결과를 코멘트에 첨부.
settings.gradle.kts (1)
6-13: 검증 완료 — include된 모듈의 디렉터리/매핑 모두 존재함settings.gradle.kts에 포함된 'payment'(루트의 payment 디렉터리 존재) 및 나머지 모듈들의 projectDir 매핑/디렉터리가 모두 확인되어 조치 불필요합니다.
platform/member/domain/build.gradle.kts (1)
1-12: 도메인 모듈 구성이 적절합니다.순수 도메인 모듈로서 Kotlin JVM만 사용하고 외부 의존성이 없는 것이 Domain Driven Design의 원칙에 부합합니다.
libs/security/build.gradle.kts (2)
10-11: contracts-auth 의존성이 올바르게 설정되었습니다.CurrentMember 인터페이스를 구현하기 위해 contracts-auth 모듈에 의존하는 것이 적절합니다.
14-17: 보안 관련 의존성이 적절하게 구성되었습니다.Spring Security와 OAuth2 리소스 서버 의존성이 인증 모듈 분리 목적에 맞게 잘 구성되어 있습니다.
platform/auth/application/build.gradle.kts (1)
12-15: 애플리케이션 계층 의존성이 적절합니다.auth-domain에 의존하고 Spring의 핵심 모듈들(context, tx, aop)만 사용하여 애플리케이션 계층의 역할에 맞게 구성되었습니다.
platform/member/application/build.gradle.kts (1)
12-19: 모듈 간 의존성이 올바르게 설정되었습니다.contracts-auth와 member-domain에 대한 의존성이 적절하고, Jackson과 Kotlin reflection 의존성도 필요한 기능을 위해 포함되었습니다.
platform/auth/adapter/src/main/kotlin/app/cardcapture/auth/adapter/inbound/web/DevelopmentLoginController.kt (1)
1-5: 패키지 구조 변경이 적절합니다.기존
app.api.member패키지에서app.cardcapture.auth로 이동하면서 auth 모듈로의 분리가 명확하게 이루어졌습니다.platform/member/adapter/src/main/kotlin/app/cardcapture/member/adapter/outbound/persistence/MemberJpaRepository.kt (1)
1-1: 패키지 구조 일관성 유지.member 모듈의 패키지 구조가
app.cardcapture.member로 일관되게 변경되었습니다.platform/services/api/build.gradle.kts (3)
14-16: 모듈 의존성 구조가 잘 설계되었습니다.API 서비스가 security, auth-adapter, member-adapter에 의존하는 구조로 각 모듈의 역할이 명확하게 분리되었습니다.
19-20: 필수 Spring Boot 의존성이 포함되었습니다.웹 애플리케이션과 JPA를 위한 기본 의존성이 적절하게 구성되었습니다.
25-25: MySQL 드라이버 의존성이 적절합니다.런타임에만 필요한 MySQL 드라이버가 올바르게 설정되었습니다.
platform/member/adapter/src/main/kotlin/app/cardcapture/member/adapter/outbound/persistence/MemberJpaEntity.kt (1)
32-37: 도메인 id(Long 비-NULL) + IDENTITY 전략 — 추가 확인 필요kotlin("plugin.jpa") 적용은 확인되었으나(platform/member/adapter/build.gradle.kts 등), 도메인 Member의 id 선언(Long vs Long?)과 MemberJpaEntity.fromDomain(신규 생성 시 id를 항상 요구하는지)을 확인하지 못했습니다. 현재 toDomain()에서 requireNotNull(this.id)를 사용하므로 신규 엔티티 생성 흐름을 검증해야 합니다.
빠른 확인(실행 또는 파일 제공):
- Member 도메인 선언과 매핑 코드를 보여 주세요 또는 실행:
- rg -n -S '\bdata class Member\b|\bclass Member\b' -g 'platform/**' -C2
- sed -n '1,240p' platform/member/adapter/src/main/kotlin/app/cardcapture/member/adapter/outbound/persistence/MemberJpaEntity.kt
가능한 대응 옵션(검토):
- 도메인 신규 생성 로직을 별도 Factory/Command로 분리(엔티티는 저장 후 toDomain만 생성)
- 도메인 id를 Long?으로 되돌려 저장 뒤 Not-Null 보장
- ID를 애플리케이션에서 할당하거나 JPA 전략 변경(IDENTITY → SEQUENCE/ASSIGNED)
platform/auth/application/src/main/kotlin/app/cardcapture/auth/application/service/DevelopmentLoginService.kt (1)
15-18: 하드코딩된 "GOOGLE" 제거 — OAuthProvider enum 사용 및 포트 시그니처 정리
- loadMemberByOAuth(oauthId, "GOOGLE")를 app.cardcapture.auth.domain.OAuthProvider.GOOGLE로 교체하고 관련 import 추가.
- loadMemberByOAuth 포트 시그니처를 (oauthId: String, provider: OAuthProvider)로 변경하고, 변경에 영향받는 모든 호출부(서비스/테스트)를 함께 갱신.
- 개발 전용 고정값이면 DEFAULT_DEV_PROVIDER 상수로 중앙화하거나 설정 프로퍼티로 분리.
- 위치: platform/auth/application/src/main/kotlin/app/cardcapture/auth/application/service/DevelopmentLoginService.kt (15–18)
코드베이스에 OAuthProvider 존재 여부 및 호출부 영향 범위를 직접 확인하세요.libs/security/src/main/kotlin/app/cardcapture/lib/security/SecurityCurrentMember.kt (1)
10-17: SecurityContextHolder 사용 문맥 및 JWT subject(숫자) 검증 필요rg 출력이 비어 있어 JWT subject가 숫자인지 확인되지 않음. 아래 항목을 확인하라:
JWT 발급부에서 subject가 ID(숫자)로 설정되는지 검사(레포 루트에서 실행):
rg -n -C2 -g '!/build/' -P '(setSubject(|\bsubject\s*(|JWTClaimsSet.Builder|.claim\s*(\s*["']sub["'])' .SecurityContextHolder는 ThreadLocal이므로 @Async/스케줄러/테스트 경로에서 비어 있을 수 있음 — 해당 모듈 호출 경로에 비동기/스케줄러 사용이 없는지 확인하거나 SecurityContext 전파(예: DelegatingSecurityContext*) 적용 여부 확인:
rg -n -C2 -g '!/build/' -P '(SecurityContextHolder|@async\b|@scheduled\b|ExecutorService|ThreadPoolTaskExecutor)' .authentication.name.toLong() 파싱이 안전한지(주체가 숫자가 아닐 경우 예외 발생) 확인:
rg -n -C2 -g '!/build/' -P 'authentication.name|toLong()' .위 명령 출력(또는 수동 검증 결과)을 첨부하라.
platform/auth/adapter/src/main/kotlin/app/cardcapture/auth/adapter/outbound/client/MemberFacadeAdapter.kt (1)
16-20: existMember → existingMember(또는 member)로 네이밍 변경 권고; OAuthProvider enum 일치 확인
네이밍: existMember → existingMember 또는 member로 변경 권고.
위치: platform/auth/adapter/src/main/kotlin/app/cardcapture/auth/adapter/outbound/client/MemberFacadeAdapter.kt (16–20행)신뢰원: 현재 DB에서 조회한 existMember.oauthProvider를 신뢰원으로 사용함. 요청 파라미터(authProvider)와 불일치 가능성이 있으면 불일치 발생 시 로깅/추적 추가 권고.
검증 결과: platform/auth/domain/src/main/kotlin/app/cardcapture/auth/domain/OAuthProvider.kt 및 platform/member/domain/src/main/kotlin/app/cardcapture/member/domain/OAuthProvider.kt 의 enum 항목이 동일(둘 다 GOOGLE).
platform/member/adapter/build.gradle.kts (1)
2-5: kotlin("plugin.jpa") 유지 필요platform/member/adapter 모듈에서 JPA 사용이 확인되었습니다 — 플러그인 제거하지 마세요.
- platform/member/adapter/src/main/kotlin/app/cardcapture/member/adapter/outbound/persistence/MemberJpaEntity.kt — @entity / @table 사용.
- platform/member/adapter/src/main/kotlin/app/cardcapture/member/adapter/outbound/persistence/MemberJpaRepository.kt — org.springframework.data.jpa.repository.JpaRepository 상속.
- platform/member/adapter/src/main/kotlin/app/cardcapture/member/adapter/outbound/persistence/MemberPersistenceAdapter.kt — MemberJpaRepository 주입 및 호출(findByOauthIdAndOauthProvider).
Likely an incorrect or invalid review comment.
platform/auth/adapter/build.gradle.kts (3)
31-31: 동적 버전(latest.release) 제거 — springmockk 4.0.2로 고정하세요platform/auth/adapter/build.gradle.kts:31
testImplementation("com.ninja-squad:springmockk:latest.release")재현성·공급망 안정성 문제로 dynamic 버전 제거 필요. 최신 안정 버전은 4.0.2(2023-03-09)이며 Spring Boot 3.x 대상으로 빌드되었고, 내부적으로는 Kotlin 1.7.21에 맞춰 빌드되어 있어 Kotlin 1.9/2.0 호환성은 공식적으로 명시되어 있지 않습니다. 루트 버전 카탈로그(libs.versions.toml) 또는 gradle.properties에 4.0.2로 고정하고, Kotlin 1.9/2.0 사용 시 빌드·테스트로 호환성 검증하세요.
24-26: 보안 확인 완료 — jjwt 0.12.7에 대한 공개 CVE 없음; 최신 안정판 0.13.0(2025-08-20)platform/auth/adapter/build.gradle.kts (L24–26)에 선언된 jjwt 0.12.7은 공개된 CVE가 없으며(CVE‑2024‑31033은 <=0.12.5 대상으로 0.12.7에 적용되지 않음). 즉시 보안 패치는 필요하지 않음 — 장기적으로 0.13.0(출시일: 2025-08-20)으로의 업그레이드 계획 수립 권장.
3-5: JPA 플러그인/Starter 사용 여부 확인 필요File: platform/auth/adapter/build.gradle.kts (Lines 3–5)
kotlin("plugin.spring") kotlin("plugin.jpa") id("io.spring.dependency-management")rg 출력이 비어 있어 모듈 내 JPA 사용 여부를 확인할 수 없습니다. 제거 전 아래 검증 명령 중 하나를 실행해 결과를 공유하세요.
검증 명령(예):
# JPA 어노테이션/인터페이스/임포트 검색 rg -n --hidden -S --glob 'platform/auth/adapter/**' -C2 -e '@Entity' -e '@Embeddable' -e '@MappedSuperclass' -e 'JpaRepository' -e 'CrudRepository' -e 'import (jakarta|javax)\.persistence' -e 'org\.springframework\.data\.jpa' # 모듈 build 파일 직접 확인 sed -n '1,240p' platform/auth/adapter/build.gradle.kts rg -n --hidden -S -e 'spring-boot-starter-data-jpa' -e 'kotlin\("plugin.jpa"\)'build.gradle.kts (2)
13-15: 저장소 구성(LTS) 합리적모든 프로젝트에 mavenCentral만 노출하는 현재 설정은 깔끔합니다. 추가 리포 필요 시 모듈별 override 권장.
2-6: 오타 수정 — build.gradle.kts (라인 6):apply false→apply false버전 정합성: Spring Boot 3.5.4는 Kotlin 1.7 이상을 권장/지원하고 BOM으로 kotlin 버전을 관리하므로 kotlin 1.9.25 사용은 문제없습니다; Kotlin 2.0 요구 주장은 부정확합니다.
- id("io.spring.dependency-management") version "1.1.7" apply false + id("io.spring.dependency-management") version "1.1.7" apply falseLikely an incorrect or invalid review comment.
jwt sign 방식 통일 oauth type 통일 jwt issuer auth adpater-> security 모듈로 변경
There was a problem hiding this comment.
Actionable comments posted: 5
♻️ Duplicate comments (1)
platform/auth/application/src/main/kotlin/app/cardcapture/auth/application/port/outbound/LoadAuthMemberPort.kt (1)
8-8: OAuthProvider enum 적용 확인 — LGTM이전 코멘트에서 제안한 String→enum 전환이 반영되었습니다. 타입 안정성 측면에서 적절합니다.
🧹 Nitpick comments (16)
platform/member/domain/build.gradle.kts (2)
6-9: Kotlin 도구체인·explicit API 설정 적절JDK 21 고정과
explicitApi()채택 좋습니다. 강제 수준을 의도대로 맞추려면explicitApiWarning()또는explicitApiStrict()로 명시하는 것도 고려해 주세요.
11-13: 도메인 독립성 유지 확인의존성 비움으로 순수 도메인 유지가 명확합니다. 변동 없으면 detekt/ktlint에 도메인 모듈 예외 규칙(스프링 금지 등)도 추가해 두면 재발 방지에 좋습니다.
libs/security/src/main/kotlin/app/cardcapture/lib/jwt/JwtProperties.kt (1)
7-12: JwtProperties 바인딩/키 안전성 점검
- 바인딩: 라이브러리 모듈이므로 소비측에서
@EnableConfigurationProperties(JwtProperties::class)또는@ConfigurationPropertiesScan이 활성화되어 있는지 확인해 주세요. 누락 시 프로퍼티가 주입되지 않습니다.- 키: 대칭키(HS256/HS512)를 쓴다면 최소 길이(Base64 기준)와 알고리즘 일치 여부를 검증하고,
SecretKey변환은 빈에서 처리해 문자열 취급 범위를 최소화하는 편이 안전합니다. 또한spring-boot-configuration-processor를compileOnly로 추가해 IDE 메타데이터를 제공해 주세요.적용 예(별도 build.gradle.kts에):
+dependencies { + compileOnly("org.springframework.boot:spring-boot-configuration-processor") +}libs/security/src/main/kotlin/app/cardcapture/lib/jwt/JwtIssuer.kt (1)
3-6: SAM 친화 + 주체 중심 API 제공 제안
fun interface로 SAM 사용을 허용하고, 주체(subject) 오버로드를 기본 제공하면 호출 측 가독성이 좋아집니다.-package app.cardcapture.lib.jwt - -interface JwtIssuer { - - fun issue(claims: Map<String, Any>): String -} +package app.cardcapture.lib.jwt + +fun interface JwtIssuer { + fun issue(claims: Map<String, Any>): String + + // 편의 오버로드: sub를 자동 삽입 + fun issue(subject: String, claims: Map<String, Any> = emptyMap()): String = + issue(claims + ("sub" to subject)) +}platform/member/domain/src/main/kotlin/app/cardcapture/member/domain/Member.kt (1)
3-8: 값 객체는 data class로 단순화현재 빈 클래스 바디(detekt 경고)입니다. 불변 값 홀더라면
data class로 전환하고 바디를 제거하세요. 필요 시require로 불변식을 추가하세요.-public class Member( - public val id: Long, - public val oauthProvider: OAuthProvider, - public val oauthId: String, -) { -} +public data class Member( + val id: Long, + val oauthProvider: OAuthProvider, + val oauthId: String, +)platform/member/adapter/build.gradle.kts (1)
22-26: allOpen 설정 적절JPA 애노테이션 3종 지정 OK입니다. 엔티티 패키지 분산 시 패키지 기반으로도 열어야 하면 추가를 검토하세요.
platform/auth/application/build.gradle.kts (1)
15-17: 테스트 의존성 슬림화 적절MockK 사용 위해 Mockito 제외 처리 👍 필요 시 junit-bom을 전역으로 쓰고 버전 관리를 단일화해도 좋습니다(선택).
platform/auth/domain/src/main/kotlin/app/cardcapture/auth/domain/AuthMember.kt (1)
3-8: 값 객체 단순화 + OAuthProvider 중복 논의 리마인드
data class로 전환해 불변 값 홀더 의도를 드러내면 좋습니다(빈 바디 제거).OAuthProvider가 member 도메인에도 존재한다면(중복), 공유 컨트랙트로 승격하거나 명시적 매핑 계층을 두는 방향을 팀 합의대로 정리해 주세요.-public class AuthMember( - public val id: Long, - public val oauthId: String, - public val oauthProvider: OAuthProvider, -) { -} +public data class AuthMember( + val id: Long, + val oauthId: String, + val oauthProvider: OAuthProvider, +)platform/auth/adapter/src/main/kotlin/app/cardcapture/auth/adapter/outbound/jwt/TokenAdapter.kt (1)
13-18: 클레임 생성: 불변 Map 사용 + 표준 claim 키 상수화 권장불변
mapOf로 충분하며,"sub"매직 스트링은 공용 상수(예: security 모듈에JwtStandardClaims.SUB)로 제공하면 오타/변경 위험이 줄어듭니다.- val claims = mutableMapOf<String, Any>( - "sub" to member.id.toString(), - ) + val claims = mapOf( + "sub" to member.id.toString(), + )platform/auth/application/src/main/kotlin/app/cardcapture/auth/application/port/outbound/LoadAuthMemberPort.kt (1)
6-10: 포트 계약 명확화: 존재하지 않을 때의 동작을 문서/시그니처로 고정
load가 미존재 시 예외를 던지는 계약이라면 Javadoc/KDoc로 명시하고 도메인 예외 타입을 고정하세요. Nullable을 허용하려면find...(): AuthMember?로 분리하는 것도 옵션입니다. 구현체/호출부 전반의 일관성 확인 필요.platform/auth/adapter/build.gradle.kts (2)
15-18: spring-boot-starter-data-jpa 의존성 점검이 모듈에서 JPA를 사용하지 않는다면 제거해 클래스패스/기동 시간을 줄이세요.
- implementation("org.springframework.boot:spring-boot-starter-data-jpa")
22-26: 테스트 의존성 버전 고정
latest.release는 재현성을 해칩니다. 명시 버전(또는 버전 카탈로그)으로 고정해 주세요.- testImplementation("com.ninja-squad:springmockk:latest.release") + testImplementation("com.ninja-squad:springmockk:<pin-explicit-version>")libs/security/src/main/kotlin/app/cardcapture/lib/jwt/JwtIssuerImpl.kt (1)
8-11: 시간 소스 주입으로 테스트 안정성 개선
Instant.now()대신Clock를 주입(기본값 UTC)하면 만료·발급 시각 테스트가 쉬워집니다. 구성 클래스 변경 없이 기본값으로도 동작합니다.-import java.time.Instant +import java.time.Clock +import java.time.Instant @@ -class JwtIssuerImpl( - private val jwtProperties: JwtProperties, - private val accessKey: SecretKey -) : JwtIssuer { +class JwtIssuerImpl( + private val jwtProperties: JwtProperties, + private val accessKey: SecretKey, + private val clock: Clock = Clock.systemUTC(), +) : JwtIssuer { @@ - val now = Instant.now() + val now = Instant.now(clock)libs/security/src/main/kotlin/app/cardcapture/lib/security/SecurityConfig.kt (3)
48-51: 403 응답 일관성 확보 — AccessDeniedHandler 추가 권장401은
AuthenticationEntryPoint, 403은AccessDeniedHandler로 분리해 JSON 포맷을 일관되게 반환하세요.적용 diff:
.oauth2ResourceServer { rs -> rs.jwt { it.decoder(jwtDecoder) } rs.authenticationEntryPoint(authEntryPoint) + rs.accessDeniedHandler( + org.springframework.security.oauth2.server.resource.web.access.BearerTokenAccessDeniedHandler() + ) }
55-67: CORS 보완: PATCH/HEAD 허용, WWW-Authenticate 노출, preflight 캐시API 현실에 맞게 메서드/헤더/캐시를 보완하세요. 운영에선 origin은 별도 프로퍼티로 제한 권장.
적용 diff:
val cfg = CorsConfiguration().apply { allowedOrigins = listOf("*") - allowedMethods = listOf("GET", "POST", "PUT", "DELETE", "OPTIONS") + allowedMethods = listOf("GET", "POST", "PUT", "PATCH", "DELETE", "OPTIONS", "HEAD") allowedHeaders = listOf("*") - exposedHeaders = listOf("Authorization") + exposedHeaders = listOf("Authorization", "WWW-Authenticate") allowCredentials = false + maxAge = 3600 }운영 팁:
allowedOrigins는*대신 환경 프로퍼티(예:cardcapture.security.cors.allowed-origins)로 관리해 주세요.
18-19: 불필요한 빈 생성자 제거(detekt 경고 해소)클래스 헤더의 빈 생성자 괄호는 제거 가능합니다.
적용 diff:
-class SecurityConfig( -) -{ +class SecurityConfig {
📜 Review details
Configuration used: Path: .coderabbit.yaml
Review profile: CHILL
Plan: Pro
📒 Files selected for processing (27)
build.gradle.kts(1 hunks)libs/contracts-auth/build.gradle.kts(1 hunks)libs/contracts-auth/src/main/kotlin/app/cardcapture/lib/contracts/auth/CurrentMember.kt(1 hunks)libs/security/build.gradle.kts(1 hunks)libs/security/src/main/kotlin/app/cardcapture/lib/jwt/JwtConfig.kt(1 hunks)libs/security/src/main/kotlin/app/cardcapture/lib/jwt/JwtIssuer.kt(1 hunks)libs/security/src/main/kotlin/app/cardcapture/lib/jwt/JwtIssuerImpl.kt(1 hunks)libs/security/src/main/kotlin/app/cardcapture/lib/jwt/JwtProperties.kt(1 hunks)libs/security/src/main/kotlin/app/cardcapture/lib/security/CustomAuthenticationEntryPoint.kt(1 hunks)libs/security/src/main/kotlin/app/cardcapture/lib/security/SecurityConfig.kt(1 hunks)libs/security/src/main/kotlin/app/cardcapture/lib/security/SecurityCurrentMember.kt(1 hunks)platform/auth/adapter/build.gradle.kts(1 hunks)platform/auth/adapter/src/main/kotlin/app/cardcapture/auth/adapter/outbound/client/MemberFacadeAdapter.kt(1 hunks)platform/auth/adapter/src/main/kotlin/app/cardcapture/auth/adapter/outbound/jwt/TokenAdapter.kt(1 hunks)platform/auth/application/build.gradle.kts(1 hunks)platform/auth/application/src/main/kotlin/app/cardcapture/auth/application/port/outbound/LoadAuthMemberPort.kt(1 hunks)platform/auth/application/src/main/kotlin/app/cardcapture/auth/application/service/DevelopmentLoginService.kt(1 hunks)platform/auth/domain/build.gradle.kts(1 hunks)platform/auth/domain/src/main/kotlin/app/cardcapture/auth/domain/AuthMember.kt(1 hunks)platform/auth/domain/src/main/kotlin/app/cardcapture/auth/domain/OAuthProvider.kt(1 hunks)platform/member/adapter/build.gradle.kts(1 hunks)platform/member/application/build.gradle.kts(1 hunks)platform/member/application/src/main/kotlin/app/cardcapture/member/application/port/service/MemberQueryService.kt(1 hunks)platform/member/domain/build.gradle.kts(1 hunks)platform/member/domain/src/main/kotlin/app/cardcapture/member/domain/Member.kt(1 hunks)platform/member/domain/src/main/kotlin/app/cardcapture/member/domain/OAuthProvider.kt(1 hunks)platform/services/api/build.gradle.kts(1 hunks)
🚧 Files skipped from review as they are similar to previous changes (13)
- libs/contracts-auth/src/main/kotlin/app/cardcapture/lib/contracts/auth/CurrentMember.kt
- libs/security/build.gradle.kts
- platform/auth/domain/src/main/kotlin/app/cardcapture/auth/domain/OAuthProvider.kt
- platform/member/domain/src/main/kotlin/app/cardcapture/member/domain/OAuthProvider.kt
- platform/auth/domain/build.gradle.kts
- libs/contracts-auth/build.gradle.kts
- platform/member/application/build.gradle.kts
- platform/auth/application/src/main/kotlin/app/cardcapture/auth/application/service/DevelopmentLoginService.kt
- libs/security/src/main/kotlin/app/cardcapture/lib/security/CustomAuthenticationEntryPoint.kt
- platform/auth/adapter/src/main/kotlin/app/cardcapture/auth/adapter/outbound/client/MemberFacadeAdapter.kt
- platform/member/application/src/main/kotlin/app/cardcapture/member/application/port/service/MemberQueryService.kt
- platform/services/api/build.gradle.kts
- libs/security/src/main/kotlin/app/cardcapture/lib/security/SecurityCurrentMember.kt
🧰 Additional context used
🧠 Learnings (2)
📓 Common learnings
Learnt from: InHyeok-J
PR: SW-rocket-dan/card-capture-server#30
File: platform/auth/adapter/src/main/kotlin/app/cardcapture/auth/adapter/outbound/jwt/JwtConfig.kt:0-0
Timestamp: 2025-09-13T12:43:39.981Z
Learning: InHyeok-J prefers to move JWT issuance functionality to the security module and use interface-based approach in auth-adapter for better separation of concerns and maintainability.
Learnt from: InHyeok-J
PR: SW-rocket-dan/card-capture-server#30
File: platform/auth/domain/src/main/kotlin/app/cardcapture/auth/domain/OAuthProvider.kt:1-5
Timestamp: 2025-09-13T12:03:17.469Z
Learning: InHyeok-J prefers team discussion for architectural decisions involving module structure and naming conventions, especially when changes affect multiple domains like the OAuthProvider enum duplication issue.
📚 Learning: 2025-09-13T12:43:39.981Z
Learnt from: InHyeok-J
PR: SW-rocket-dan/card-capture-server#30
File: platform/auth/adapter/src/main/kotlin/app/cardcapture/auth/adapter/outbound/jwt/JwtConfig.kt:0-0
Timestamp: 2025-09-13T12:43:39.981Z
Learning: InHyeok-J prefers to move JWT issuance functionality to the security module and use interface-based approach in auth-adapter for better separation of concerns and maintainability.
Applied to files:
libs/security/src/main/kotlin/app/cardcapture/lib/jwt/JwtIssuer.ktplatform/auth/adapter/src/main/kotlin/app/cardcapture/auth/adapter/outbound/jwt/TokenAdapter.ktlibs/security/src/main/kotlin/app/cardcapture/lib/jwt/JwtIssuerImpl.kt
🧬 Code graph analysis (2)
platform/auth/adapter/build.gradle.kts (5)
libs/contracts-auth/src/main/kotlin/app/cardcapture/lib/contracts/auth/CurrentMember.kt (1)
id(7-11)libs/security/src/main/kotlin/app/cardcapture/lib/security/SecurityCurrentMember.kt (1)
id(9-26)platform/auth/domain/src/main/kotlin/app/cardcapture/auth/domain/AuthMember.kt (1)
id(3-8)platform/member/domain/src/main/kotlin/app/cardcapture/member/domain/Member.kt (1)
id(3-8)platform/member/application/src/main/kotlin/app/cardcapture/member/application/port/inbound/dto/AuthMemberView.kt (1)
id(3-8)
platform/auth/application/build.gradle.kts (2)
platform/auth/domain/src/main/kotlin/app/cardcapture/auth/domain/AuthMember.kt (1)
id(3-8)platform/member/domain/src/main/kotlin/app/cardcapture/member/domain/Member.kt (1)
id(3-8)
🪛 detekt (1.23.8)
libs/security/src/main/kotlin/app/cardcapture/lib/security/SecurityConfig.kt
[warning] 18-19: An empty default constructor can be removed.
(detekt.empty-blocks.EmptyDefaultConstructor)
platform/member/domain/src/main/kotlin/app/cardcapture/member/domain/Member.kt
[warning] 7-8: The class or object Member is empty.
(detekt.empty-blocks.EmptyClassBlock)
platform/auth/domain/src/main/kotlin/app/cardcapture/auth/domain/AuthMember.kt
[warning] 7-8: The class or object AuthMember is empty.
(detekt.empty-blocks.EmptyClassBlock)
🔇 Additional comments (7)
platform/member/adapter/build.gradle.kts (1)
8-11: 경로 수정 불필요 — settings.gradle.kts가 프로젝트 이름을 매핑함settings.gradle.kts에 project(":member-application").projectDir = file("platform/member/application") (line 19) 및 project(":member-domain").projectDir = file("platform/member/domain") (line 18)가 정의되어 있으므로 platform/member/adapter/build.gradle.kts의 implementation(project(":member-application")) / implementation(project(":member-domain")) 참조는 올바릅니다.
Likely an incorrect or invalid review comment.
build.gradle.kts (2)
37-44: BOM 가져오기 위치 적절의존성 정합성을 위해 모든 서브모듈에서 동일 BOM을 사용하도록 한 점 좋습니다. 라이브러리 모듈은 Boot 플러그인 없이도 이 구성이 충분합니다.
2-6: 확인 — Kotlin 1.9.25는 Spring Boot 3.5.x의 최소 요구(Kotlin 1.7.x)를 충족합니다.
현재 build.gradle.kts의 Kotlin 플러그인/stdlib 버전(1.9.25)은 호환되며 즉시 2.0.x로 상향할 필요가 없습니다.platform/auth/adapter/build.gradle.kts (2)
9-13: 확인 완료 — :security 모듈 좌표 일치settings.gradle.kts에 project(":security").projectDir = file("libs/security")로 매핑되어 있으며, platform/auth/adapter와 platform/services/api의 build.gradle.kts는 implementation(project(":security"))를 사용합니다. 불일치 없음.
위치: settings.gradle.kts:31, platform/auth/adapter/build.gradle.kts:12, platform/services/api/build.gradle.kts:10
1-6: kotlin("plugin.jpa") 제거 검토 — JPA 사용 여부 재확인 필요어댑터(platform/auth/adapter)에 JPA 엔티티/리포지토리가 없다면 kotlin("plugin.jpa") 제거 권장(불필요한 플러그인은 빌드 시간 증가). 제출한 검색 스크립트가 빈 출력이라 자동 확인 불가. 아래 명령으로 jakarta/javax 및 Spring Data JPA 패턴을 포함해 재검색하거나 수동 확인:
rg -nI -S -C1 \ -e 'import\s+jakarta\.persistence' \ -e 'import\s+javax\.persistence' \ -e '@Entity\b' -e '@Table\b' -e '@Embeddable\b' -e '@MappedSuperclass\b' -e '@Id\b' \ -e 'JpaRepository\b' -e 'CrudRepository\b' -e 'PagingAndSortingRepository\b' \ -e 'org\.springframework\.data\.jpa' -e 'EntityManager\b' -- platform/auth/adapter위치: platform/auth/adapter/build.gradle.kts (plugins 블록) — JPA 무사용이 확인되면 kotlin("plugin.jpa") 제거.
libs/security/src/main/kotlin/app/cardcapture/lib/jwt/JwtConfig.kt (1)
20-27: 키 디코딩/길이 검증 훌륭함 — LGTMBase64 디코딩과 256-bit 이상 길이 검증으로 HS256 최소 요구사항을 충족합니다.
libs/security/src/main/kotlin/app/cardcapture/lib/security/SecurityConfig.kt (1)
28-33: 기본 EntryPoint를 오버라이드 가능하게 한 점 좋습니다
@ConditionalOnMissingBean적용으로 라이브러리-앱 결합을 낮춘 점 👍
| @Bean | ||
| fun jwtDecoder(accessKey: SecretKey): JwtDecoder = | ||
| NimbusJwtDecoder.withSecretKey(accessKey) | ||
| .macAlgorithm(org.springframework.security.oauth2.jose.jws.MacAlgorithm.HS256) | ||
| .build() |
There was a problem hiding this comment.
🛠️ Refactor suggestion
JWT 디코더에 issuer 검증 추가 필요
현재 서명만 검증되고 iss는 기본 검증 대상이 아닙니다. JwtValidators.createDefaultWithIssuer(props.issuer)로 발급자 검증을 활성화하세요.
- fun jwtDecoder(accessKey: SecretKey): JwtDecoder =
- NimbusJwtDecoder.withSecretKey(accessKey)
- .macAlgorithm(org.springframework.security.oauth2.jose.jws.MacAlgorithm.HS256)
- .build()
+ fun jwtDecoder(accessKey: SecretKey): JwtDecoder {
+ val decoder = NimbusJwtDecoder.withSecretKey(accessKey)
+ .macAlgorithm(org.springframework.security.oauth2.jose.jws.MacAlgorithm.HS256)
+ .build()
+ decoder.setJwtValidator(
+ org.springframework.security.oauth2.jwt.JwtValidators.createDefaultWithIssuer(props.issuer)
+ )
+ return decoder
+ }📝 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.
| @Bean | |
| fun jwtDecoder(accessKey: SecretKey): JwtDecoder = | |
| NimbusJwtDecoder.withSecretKey(accessKey) | |
| .macAlgorithm(org.springframework.security.oauth2.jose.jws.MacAlgorithm.HS256) | |
| .build() | |
| @Bean | |
| fun jwtDecoder(accessKey: SecretKey): JwtDecoder { | |
| val decoder = NimbusJwtDecoder.withSecretKey(accessKey) | |
| .macAlgorithm(org.springframework.security.oauth2.jose.jws.MacAlgorithm.HS256) | |
| .build() | |
| decoder.setJwtValidator( | |
| org.springframework.security.oauth2.jwt.JwtValidators.createDefaultWithIssuer(props.issuer) | |
| ) | |
| return decoder | |
| } |
🤖 Prompt for AI Agents
In libs/security/src/main/kotlin/app/cardcapture/lib/jwt/JwtConfig.kt around
lines 34-38, the JwtDecoder currently only verifies signatures; add issuer
validation by creating a JwtDecoder instance (as you already do) and then call
setJwtValidator(JwtValidators.createDefaultWithIssuer(props.issuer)) on it
before returning. Ensure the issuer value (props.issuer) is available in this
config (inject or pass your Jwt properties if not already) and import
org.springframework.security.oauth2.jwt.JwtValidators; return the configured
decoder with the issuer-aware validator applied.
There was a problem hiding this comment.
@InHyeok-J 네, 이해했습니다! JWT issuer 검증은 나중에 추가하셔도 됩니다.
혹시 이 개선사항을 추적하기 위해 별도 이슈를 생성해드릴까요? 아니면 팀 논의 후에 다루실 계획이신가요?
🧠 Learnings used
Learnt from: InHyeok-J
PR: SW-rocket-dan/card-capture-server#30
File: platform/auth/adapter/src/main/kotlin/app/cardcapture/auth/adapter/outbound/jwt/JwtConfig.kt:0-0
Timestamp: 2025-09-13T12:43:39.981Z
Learning: InHyeok-J prefers to move JWT issuance functionality to the security module and use interface-based approach in auth-adapter for better separation of concerns and maintainability.
Learnt from: InHyeok-J
PR: SW-rocket-dan/card-capture-server#30
File: platform/auth/domain/src/main/kotlin/app/cardcapture/auth/domain/OAuthProvider.kt:1-5
Timestamp: 2025-09-13T12:03:17.469Z
Learning: InHyeok-J prefers team discussion for architectural decisions involving module structure and naming conventions, especially when changes affect multiple domains like the OAuthProvider enum duplication issue.
There was a problem hiding this comment.
🧩 Analysis chain
@InHyeok-J JWT issuer 검증 개선사항에 대한 이슈를 생성했습니다! 나중에 팀에서 다룰 때 참고하실 수 있도록 관련 내용과 링크를 포함했습니다.
🏁 Script executed:
#!/bin/bash
# Create GitHub issue for JWT issuer validation enhancement
gh issue create \
--title "JWT 디코더에 issuer 검증 추가" \
--body "## 개요
JWT 디코더 설정에서 현재 서명만 검증되고 있어, issuer 검증을 추가해야 합니다.
## 상세 내용
libs/security/src/main/kotlin/app/cardcapture/lib/jwt/JwtConfig.kt 파일의 jwtDecoder 메서드에서 JwtValidators.createDefaultWithIssuer(props.issuer)를 사용하여 발급자 검증을 활성화해야 합니다.
## 개선 방안
\`\`\`kotlin
@Bean
fun jwtDecoder(accessKey: SecretKey): JwtDecoder {
val decoder = NimbusJwtDecoder.withSecretKey(accessKey)
.macAlgorithm(org.springframework.security.oauth2.jose.jws.MacAlgorithm.HS256)
.build()
decoder.setJwtValidator(
org.springframework.security.oauth2.jwt.JwtValidators.createDefaultWithIssuer(props.issuer)
)
return decoder
}
\`\`\`
## 관련 링크
- PR: https://github.com/SW-rocket-dan/card-capture-server/pull/30
- Comment: https://github.com/SW-rocket-dan/card-capture-server/pull/30#discussion_r2346573402
- 요청자: @InHyeok-J" \
--assignee InHyeok-J
Length of output: 62
@InHyeok-J 완료했습니다! JWT issuer 검증 개선사항에 대한 이슈가 생성되었습니다: #31
이슈에는 현재 상황, 개선 방안, 그리고 관련 PR/댓글 링크를 포함했고, 담당자로 지정해드렸습니다. 나중에 팀에서 보안 개선 작업을 진행하실 때 참고하시면 됩니다!
🧠 Learnings used
Learnt from: InHyeok-J
PR: SW-rocket-dan/card-capture-server#30
File: platform/auth/adapter/src/main/kotlin/app/cardcapture/auth/adapter/outbound/jwt/JwtConfig.kt:0-0
Timestamp: 2025-09-13T12:43:39.981Z
Learning: InHyeok-J prefers to move JWT issuance functionality to the security module and use interface-based approach in auth-adapter for better separation of concerns and maintainability.
Learnt from: InHyeok-J
PR: SW-rocket-dan/card-capture-server#30
File: platform/auth/domain/src/main/kotlin/app/cardcapture/auth/domain/OAuthProvider.kt:1-5
Timestamp: 2025-09-13T12:03:17.469Z
Learning: InHyeok-J prefers team discussion for architectural decisions involving module structure and naming conventions, especially when changes affect multiple domains like the OAuthProvider enum duplication issue.
| import org.springframework.boot.autoconfigure.condition.ConditionalOnClass | ||
| import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean | ||
| import org.springframework.context.annotation.Bean | ||
| import org.springframework.context.annotation.Configuration | ||
| import org.springframework.http.HttpMethod | ||
| import org.springframework.security.config.annotation.web.builders.HttpSecurity | ||
| import org.springframework.security.oauth2.jwt.JwtDecoder | ||
| import org.springframework.security.web.AuthenticationEntryPoint | ||
| import org.springframework.security.web.SecurityFilterChain | ||
| import org.springframework.web.cors.CorsConfiguration | ||
| import org.springframework.web.cors.CorsConfigurationSource | ||
| import org.springframework.web.cors.UrlBasedCorsConfigurationSource |
There was a problem hiding this comment.
🛠️ Refactor suggestion
dev 전용 화이트리스트는 프로필(local/dev) 기반으로만 허용 + JwtDecoder 존재 시에만 체인 구성
운영 안전성과 결합도 완화를 위해 필터 체인을 JwtDecoder 존재 시에만 등록하고, dev 경로는 프로필로 제어하세요. 또한 SessionCreationPolicy는 import로 간결화합니다.
적용 diff(Imports):
import org.springframework.boot.autoconfigure.condition.ConditionalOnClass
import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean
+import org.springframework.boot.autoconfigure.condition.ConditionalOnBean
import org.springframework.context.annotation.Bean
import org.springframework.context.annotation.Configuration
import org.springframework.http.HttpMethod
+import org.springframework.core.env.Environment
+import org.springframework.core.env.Profiles
import org.springframework.security.config.annotation.web.builders.HttpSecurity
+import org.springframework.security.config.http.SessionCreationPolicy
import org.springframework.security.oauth2.jwt.JwtDecoder
import org.springframework.security.web.AuthenticationEntryPoint
import org.springframework.security.web.SecurityFilterChain적용 diff(filterChain):
- fun filterChain(http: HttpSecurity, jwtDecoder: JwtDecoder, authEntryPoint : AuthenticationEntryPoint): SecurityFilterChain {
+ @ConditionalOnBean(JwtDecoder::class)
+ fun filterChain(
+ http: HttpSecurity,
+ jwtDecoder: JwtDecoder,
+ authEntryPoint: AuthenticationEntryPoint,
+ environment: Environment
+ ): SecurityFilterChain {
- http
+ val wl = if (environment.acceptsProfiles(Profiles.of("local", "dev"))) {
+ WHITE_LIST + "/api/v1/dev/auth/**"
+ } else {
+ WHITE_LIST
+ }
+ http
.csrf { it.disable() }
.cors { }
.formLogin { it.disable() }
.httpBasic { it.disable() }
- .sessionManagement { it.sessionCreationPolicy(org.springframework.security.config.http.SessionCreationPolicy.STATELESS) }
+ .sessionManagement { it.sessionCreationPolicy(SessionCreationPolicy.STATELESS) }
.authorizeHttpRequests {
- it.requestMatchers(*WHITE_LIST).permitAll()
+ it.requestMatchers(*wl).permitAll()
it.requestMatchers(HttpMethod.OPTIONS, "/**").permitAll()
it.anyRequest().authenticated()
}Also applies to: 35-47
🤖 Prompt for AI Agents
In libs/security/src/main/kotlin/app/cardcapture/lib/security/SecurityConfig.kt
(lines 3-14, and also apply analogous changes to 35-47), the dev-only whitelist
and filter chain are too broadly applied; update the configuration so the
SecurityFilterChain bean is only created when a JwtDecoder bean is present and
the dev whitelist is enabled only for the local/dev profiles. Annotate the
SecurityFilterChain-producing bean or configuration class with
@ConditionalOnBean(JwtDecoder::class) (or wrap the bean with a check for
JwtDecoder), move the dev-only CORS/whitelist logic behind a profile annotation
like @Profile("local","dev") (or ConditionalOnExpression/ConditionalOnProperty
tied to profiles), and simplify session policy usage by importing
org.springframework.security.config.http.SessionCreationPolicy and using
SessionCreationPolicy.STATELESS in the HttpSecurity configuration. Ensure these
changes are applied both to the imports section and the filter chain bean region
(lines ~35-47).
There was a problem hiding this comment.
@InHyeok-J, 이해했습니다! 현재 기능 구현에 집중하시고, 운영 환경에서 필요할 때 추가하시는 것이 합리적인 접근 방식이네요. 제안드린 프로필 기반 설정은 나중에 필요할 때 언제든 적용하실 수 있습니다.
🧠 Learnings used
Learnt from: InHyeok-J
PR: SW-rocket-dan/card-capture-server#30
File: platform/auth/adapter/src/main/kotlin/app/cardcapture/auth/adapter/outbound/jwt/JwtConfig.kt:0-0
Timestamp: 2025-09-13T12:43:39.981Z
Learning: InHyeok-J prefers to move JWT issuance functionality to the security module and use interface-based approach in auth-adapter for better separation of concerns and maintainability.
Learnt from: InHyeok-J
PR: SW-rocket-dan/card-capture-server#30
File: platform/auth/domain/src/main/kotlin/app/cardcapture/auth/domain/OAuthProvider.kt:1-5
Timestamp: 2025-09-13T12:03:17.469Z
Learning: InHyeok-J prefers team discussion for architectural decisions involving module structure and naming conventions, especially when changes affect multiple domains like the OAuthProvider enum duplication issue.
PR 변경된 내용
auth모듈과 member 모듈이 분리됨에 따라 auth-adapter가 member-application in port을 호출해 필요한 정보를 받아옵니다.
libs/security, libs/contract-auth 모듈 추가
추가 내용
참조
Closes #27
Summary by CodeRabbit
신기능
리팩터링
작업
테스트