20260105 #3 spring security 기본 설정#4
Conversation
…oftDelete 된 회원 제외 #3
|
Note
|
| 코호트 / 파일(s) | 변경 요약 |
|---|---|
빌드·버전 카탈로그 & 설정 gradle/libs.versions.toml, settings.gradle.kts, CT-auth/build.gradle.kts, CT-common/build.gradle.kts, CT-redis/build.gradle.kts, Dockerfile, .github/workflows/spring-boot-cicd.yml |
버전·라이브러리 추가(jjwt, kotlin-logging), 의존성 추가/조정(CT-member, CT-redis, jjwt, sejong portal), CT-redis 모듈 포함, Docker COPY 경로 및 워크플로 트리거 수정 |
토큰 인터페이스·스토어 CT-auth/src/main/kotlin/.../core/token/TokenProvider.kt, .../TokenStore.kt |
TokenProvider, TokenStore 인터페이스 추가(토큰 생성·검증·저장 API 정의) |
JWT 구현·스토어·설정 CT-auth/.../infrastructure/jwt/JwtProvider.kt, .../JwtStore.kt, .../properties/JwtProperties.kt, .../config/JwtConfig.kt |
JJWT 기반 JwtProvider 구현(토큰 생성/검증/클레임 추출), Redis 기반 JwtStore 구현, JwtProperties 바인딩 및 SecretKey/JwtProvider/JwtStore 빈 등록 |
보안 필터·설정 CT-auth/.../infrastructure/filter/TokenAuthenticationFilter.kt, .../config/SecurityConfig.kt |
TokenAuthenticationFilter 추가(화이트리스트, 토큰 추출·검증, 권한 검사, 에러 응답), SecurityFilterChain 및 필터 빈 등록 |
상수·화이트리스트·유틸 CT-auth/.../infrastructure/constant/AuthConstants.kt, .../constant/SecurityUrls.kt, .../util/AuthUtil.kt |
인증 관련 상수, 인증 제외 URL 화이트리스트, 요청에서 토큰 추출 및 Redis 키 생성 유틸 추가 |
사용자 어댑터·서비스 CT-auth/.../core/user/UserPrincipal.kt, .../infrastructure/user/CustomUserDetails.kt, .../CustomUserDetailsService.kt, CT-member/.../application/MemberService.kt |
UserPrincipal 인터페이스, Member 기반 CustomUserDetails, UserDetailsService 구현, Member 조회 서비스 추가 |
회원 도메인·리포지토리·역할 CT-member/.../infrastructure/entity/Member.kt, .../infrastructure/repository/MemberRepository.kt, .../core/constant/Role.kt |
Member JPA 엔티티(팩토리·정규화·soft-delete 포함), 리포지토리 쿼리 메서드, Role enum 추가 |
공통 예외·응답 DTO CT-common/.../application/exception/CustomException.kt, .../ErrorCode.kt, .../ErrorResponse.kt |
CustomException, ErrorCode(enum), ErrorResponse DTO 추가(전역 예외 처리 연계) |
시간 추상화·JPA 베이스 CT-common/.../core/time/TimeProvider.kt, .../infrastructure/time/SystemTimeProvider.kt, .../infrastructure/config/TimeConfig.kt, .../infrastructure/persistence/BaseEntity.kt |
TimeProvider 추상화 및 구현, TimeConfig 빈, JPA 감사 필드·soft-delete가 포함된 BaseEntity 추가 |
Redis 구성·프로퍼티 CT-redis/.../infrastructure/config/RedisConfig.kt, .../infrastructure/properties/RedisProperties.kt |
LettuceConnectionFactory, RedisTemplate(GenericJackson2JsonRedisSerializer) 설정 및 RedisProperties 바인딩 추가 |
웹 설정·예외 처리·CORS·스캔·Swagger CT-web/.../CampusTableServerApplication.kt, .../application/exception/GlobalExceptionHandler.kt, .../infrastructure/config/CorsConfig.kt, .../infrastructure/config/ComponentScanConfig.kt, .../infrastructure/config/SwaggerConfig.kt |
@EnableJpaAuditing 추가, 전역 예외 핸들러, CORS 설정, ComponentScan 경로 조정, Swagger bean 이름 변경 |
Sequence Diagram(s)
sequenceDiagram
participant Client
participant Filter as TokenAuthenticationFilter
participant Provider as JwtProvider
participant MemberSvc as MemberService
participant Repo as MemberRepository
participant SecurityCtx as SecurityContext
Client->>Filter: 요청(Authorization 헤더 포함)
Filter->>Filter: 화이트리스트 검사
alt 화이트리스트 경로
Filter-->>Client: 인증 없이 요청 계속
else 보호 경로
Filter->>Filter: 토큰 추출 및 포맷 검사
Filter->>Provider: isValidToken(token)
Provider-->>Filter: 유효/무효 (만료 예외 가능)
alt 유효
Filter->>Provider: getMemberId(token)
Provider-->>Filter: memberId
Filter->>MemberSvc: findMemberById(memberId)
MemberSvc->>Repo: findByIdAndDeletedFalse(memberId)
Repo-->>MemberSvc: Member
MemberSvc-->>Filter: Member
Filter->>Filter: CustomUserDetails 생성 및 권한 검사
Filter->>SecurityCtx: Authentication 설정
Filter-->>Client: 요청 처리 계속
else 무효/만료
Filter-->>Client: 에러 응답(예: 401/권한 오류)
end
end
Estimated code review effort
🎯 4 (Complex) | ⏱️ ~60 minutes
Possibly related issues
- ⚙️ [기능추가][security] Spring Security 기본 설정 #3: Spring Security 기본 설정(토큰 필터, JwtProvider/JwtStore, UserDetailsService 등) 구현 목표와 직접 연관됨.
Possibly related PRs
- 20260102 #1 프로젝트 멀티모듈 구조 도입 #2: 모듈/의존성 초기 설정과 연계되어 있으며 본 PR의 의존성·모듈 확장과 직접적인 코드 수준 연관이 있음.
Poem
🐰 토끼가 말하네—
비밀키로 꿈을 잇고, 토큰 길을 닦아,
헤더 한 줄이면 문이 열리고,
Redis 굴속에 리프레시 숨겨두고,
작은 발로 안전을 지키네.
🚥 Pre-merge checks | ✅ 2 | ❌ 1
❌ Failed checks (1 warning)
| Check name | Status | Explanation | Resolution |
|---|---|---|---|
| Docstring Coverage | Docstring coverage is 39.34% which is insufficient. The required threshold is 80.00%. | Write docstrings for the functions missing them to satisfy the coverage threshold. |
✅ Passed checks (2 passed)
| Check name | Status | Explanation |
|---|---|---|
| Description Check | ✅ Passed | Check skipped - CodeRabbit’s high-level summary is enabled. |
| Title check | ✅ Passed | PR 제목이 변경 사항의 주요 내용과 부분적으로 관련됨. 'Spring Security 기본 설정'은 실제 변경의 일부이지만, JWT 인증, Redis 통합, 공통 예외 처리 등 더 광범위한 변경을 모두 포함하지는 않음. |
✏️ Tip: You can configure your own custom pre-merge checks in the settings.
📜 Recent review details
Configuration used: Path: .coderabbit.yaml
Review profile: CHILL
Plan: Pro
📒 Files selected for processing (1)
Dockerfile
🔇 Additional comments (1)
Dockerfile (1)
10-10: CT-web 모듈 JAR 경로 변경이 올바릅니다.Gradle 멀티 모듈 구조에서 CT-web 모듈의 Spring Boot 빌드 출력 경로(
CT-web/build/libs/CT-web-*.jar)가 정확하게 지정되었습니다. 버전은 루트build.gradle.kts에서 정의된0.1.0으로 설정되어 있으며, 와일드카드 패턴이 버전 변경에 유연하게 대응합니다. Dockerfile의 COPY 명령어가 올바른 위치의 JAR 파일을/app.jar로 복사하고 있습니다.
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 @coderabbitai help to get the list of available commands and usage tips.
There was a problem hiding this comment.
Actionable comments posted: 11
🤖 Fix all issues with AI agents
In @CT-auth/build.gradle.kts:
- Around line 20-21: The build declares the test-only dependency using the
runtime API configuration (api(libs.spring.security.test)), which can leak test
libs into production classpath; change that declaration to a test-scoped
configuration such as testImplementation(libs.spring.security.test) (or testApi
if the test dependency must be exposed to downstream test modules) so
spring.security.test is only included on the test classpath.
- Around line 23-27: Update the jjwt dependency to the latest 0.13.0 release by
replacing the current reference to libs.jjwt in the dependencies block (the line
with "api(libs.jjwt)") so the project uses jjwt 0.13.0; after changing the
dependency reference, refresh/resolve Gradle dependencies and run a full
build/test to verify there are no compatibility issues with classes or JWT usage
in the codebase.
In
@CT-auth/src/main/kotlin/com/chuseok22/ctauth/infrastructure/filter/TokenAuthenticationFilter.kt:
- Around line 64-74: The current try/catch in TokenAuthenticationFilter catches
only CustomException and ExpiredJwtException, leaving other exceptions
unhandled; add a final catch (e: Exception) block after the existing catches in
the authenticate/filters' try scope to SecurityContextHolder.clearContext(), log
the exception with log.error(e) including a clear message like
"[TokenAuthenticationFilter] Unexpected error", and call
sendErrorResponse(response, ErrorCode.INTERNAL_SERVER_ERROR) (or the appropriate
generic ErrorCode) to return a controlled error response instead of letting
exceptions bubble up the filter chain.
In
@CT-auth/src/main/kotlin/com/chuseok22/ctauth/infrastructure/jwt/JwtProvider.kt:
- Around line 61-69: getMemberId() is reading a non-existent "memberId" claim
but createToken() stores the memberId as the JWT subject; change getMemberId()
to retrieve the subject from the parsed claims (e.g., claims.subject or
claims.getSubject()) and throw CustomException(ErrorCode.INVALID_JWT) if subject
is null/blank, and apply the same fix to the other affected method(s) around
lines 80-94 that similarly expect a "memberId" claim instead of the subject;
preserve existing JwtException logging/throwing behavior.
In
@CT-auth/src/main/kotlin/com/chuseok22/ctauth/infrastructure/user/CustomUserDetails.kt:
- Around line 21-23: getMemberId currently calls member.id.toString() which can
NPE because member.id is UUID?; make the call null-safe (e.g. use
member.id?.toString() ?: "") or throw a clear IllegalStateException if an id
must exist, and update the CustomUserDetails.getMemberId implementation
accordingly so it never dereferences a nullable UUID without a check.
In
@CT-common/src/main/kotlin/com/chuseok22/ctcommon/application/exception/ErrorCode.kt:
- Line 20: Update the enum entry MEMBER_NOT_FOUND in ErrorCode (the ErrorCode.kt
enum) to fix the message typo: replace the current message "회원을 없습니다" with the
correct phrase "회원을 찾을 수 없습니다" so the MEMBER_NOT_FOUND constant returns the
grammatically correct Korean error message.
In
@CT-member/src/main/kotlin/com/chuseok22/ctmember/infrastructure/repository/MemberRepository.kt:
- Around line 7-11: The repository method names in MemberRepository reference a
non-existent 'deleted' field; update the method names to match the entity's
soft-delete field 'isDeleted' so Spring Data JPA generates correct queries:
rename findByIdAndDeletedFalse(...) to findByIdAndIsDeletedFalse(...) and
findByStudentNameAndDeletedFalse(...) to
findByStudentNameAndIsDeletedFalse(...). Ensure the signatures (parameter types
and return types) remain unchanged while only renaming the methods.
In
@CT-redis/src/main/kotlin/com/chuseok22/ctredis/infrastructure/config/RedisConfig.kt:
- Around line 82-87: Current polymorphic typing uses
BasicPolymorphicTypeValidator.builder().allowIfBaseType(Any::class.java) which
permits deserialization of any type and is a security risk; replace this with a
restricted validator that only allows known safe packages or explicit classes
(e.g., use
BasicPolymorphicTypeValidator.builder().allowIfSubType("com.yourapp.package").allowIfSubType("com.yourapp.dto").or
explicitly allow specific classes) and pass that validator into
activateDefaultTyping instead of allowIfBaseType(Any::class.java) to limit
accepted polymorphic types for
activateDefaultTyping/ObjectMapper.DefaultTyping.NON_FINAL.
In
@CT-web/src/main/kotlin/com/chuseok22/ctweb/application/exception/GlobalExceptionHandler.kt:
- Around line 37-49: The handleException method currently logs only the
exception message; update it to pass the exception object to the logger so the
full stack trace is recorded (mirror how handleCustomException logs the
exception). Locate the handleException function and change the log.error call to
include the throwable (e.g., log.error(e) or log.error(e) { "예상치 못한 예외 발생:
${e.message}" }) so the stack trace is emitted while keeping the existing
response creation intact.
In @gradle/libs.versions.toml:
- Around line 48-49: Replace the legacy aggregator entry `jjwt = { module =
"io.jsonwebtoken:jjwt", version.ref = "jjwt" }` with separate modular entries
`jjwt-api`, `jjwt-impl`, and a serializer like `jjwt-jackson` in
libs.versions.toml (using the same version.ref), and update build.gradle.kts to
use `implementation(libs.jjwt.api)` and `runtimeOnly(libs.jjwt.impl)` plus
`runtimeOnly(libs.jjwt.jackson)` so the API, implementation and JSON mapper are
declared with correct scopes.
🧹 Nitpick comments (14)
CT-web/src/main/kotlin/com/chuseok22/ctweb/CampusTableServerApplication.kt (1)
7-9:@EnableJpaAuditing을 별도의 설정 클래스로 분리하는 것을 권장합니다.메인 애플리케이션 클래스에
@EnableJpaAuditing을 직접 추가하면@DataJpaTest슬라이스 테스트 시 문제가 발생할 수 있습니다. 테스트 컨텍스트에서 auditing 관련 빈이 로드되지 않아 테스트가 실패할 수 있습니다.♻️ 별도 설정 클래스로 분리하는 방법
새 파일 생성:
CT-web/src/main/kotlin/com/chuseok22/ctweb/config/JpaAuditingConfig.ktpackage com.chuseok22.ctweb.config import org.springframework.context.annotation.Configuration import org.springframework.data.jpa.repository.config.EnableJpaAuditing @Configuration @EnableJpaAuditing class JpaAuditingConfig메인 애플리케이션 클래스에서는
@EnableJpaAuditing제거:package com.chuseok22.ctweb import org.springframework.boot.autoconfigure.SpringBootApplication import org.springframework.boot.runApplication -import org.springframework.data.jpa.repository.config.EnableJpaAuditing @SpringBootApplication -@EnableJpaAuditing class CampusTableServerApplication이렇게 분리하면 테스트 시
@Import(JpaAuditingConfig::class)로 필요할 때만 포함하거나,@DataJpaTest에서 제외할 수 있습니다.CT-common/src/main/kotlin/com/chuseok22/ctcommon/infrastructure/persistence/BaseEntity.kt (1)
44-47:delete()메서드 구현이 적절합니다.
Instant를 파라미터로 받아 테스트 용이성을 확보한 점이 좋습니다. AI 요약에서 언급된TimeProvider와 잘 연동될 것으로 보입니다.향후 soft delete 복원 기능이 필요할 경우를 대비하여
restore()메서드 추가를 고려해 보세요.♻️ 선택적 개선안
fun delete(now: Instant) { this.isDeleted = true deletedAt = now } + + /** + * Soft Delete 복원 + */ + fun restore() { + this.isDeleted = false + this.deletedAt = null + }CT-redis/src/main/kotlin/com/chuseok22/ctredis/infrastructure/properties/RedisProperties.kt (1)
6-13:@Validated사용 시 유효성 검증 어노테이션 추가 고려
@Validated어노테이션을 사용하고 있지만, 실제 유효성 검증 어노테이션(@NotBlank,@Min등)이 프로퍼티에 적용되어 있지 않습니다. 또한, Kotlin data class에서 빈 클래스 블록{}은 생략 가능합니다.♻️ 제안 코드
@Validated @ConfigurationProperties(prefix = "spring.data.redis") data class RedisProperties( + @field:NotBlank val host: String, + @field:Min(1) val port: Int, val password: String -) { -} +)
jakarta.validation.constraints.NotBlank및jakarta.validation.constraints.Minimport가 필요합니다.CT-web/src/main/kotlin/com/chuseok22/ctweb/infrastructure/config/CorsConfig.kt (1)
14-23: Origin 패턴 외부화 고려CORS 설정은 적절하게 구성되어 있습니다. 다만,
allowedOriginPatterns가 하드코딩되어 있어 환경별로 다른 도메인을 사용해야 할 경우 재배포가 필요합니다. 환경 변수나application.yml을 통한 외부화를 고려해 볼 수 있습니다.♻️ 외부화 예시
// CorsProperties.kt @ConfigurationProperties(prefix = "cors") data class CorsProperties( val allowedOriginPatterns: List<String> ) // CorsConfig.kt @Configuration @EnableConfigurationProperties(CorsProperties::class) class CorsConfig( private val corsProperties: CorsProperties ) { @Bean fun corsConfigurationSource(): CorsConfigurationSource { val configuration = CorsConfiguration().apply { allowedOriginPatterns = corsProperties.allowedOriginPatterns // ... 나머지 설정 } // ... } }# application.yml cors: allowed-origin-patterns: - "https://www.campustable.shop" - "http://localhost:3000"CT-common/src/main/kotlin/com/chuseok22/ctcommon/application/exception/CustomException.kt (1)
3-5: LGTM! 커스텀 예외 클래스가 깔끔하게 구현되었습니다.
ErrorCode를 활용한 예외 처리 패턴이 적절합니다. 필요시 예외 체이닝을 위해cause파라미터를 추가하는 것도 고려해볼 수 있습니다.💡 예외 체이닝 지원 (선택사항)
class CustomException( val errorCode: ErrorCode, -) : RuntimeException(errorCode.message) + cause: Throwable? = null +) : RuntimeException(errorCode.message, cause)CT-common/src/main/kotlin/com/chuseok22/ctcommon/application/exception/ErrorResponse.kt (1)
3-6: LGTM! 에러 응답 구조가 적절합니다.일관된 에러 응답을 위한 data class가 잘 정의되어 있습니다.
JSON 직렬화 시
errorCode가 enum 이름 그대로 노출됩니다. 클라이언트에 더 명확한 형태로 전달하려면 커스텀 직렬화를 고려해볼 수 있습니다:💡 JSON 직렬화 형식 개선 (선택사항)
data class ErrorResponse( val errorCode: String, // ErrorCode.name 또는 code 값 val errorMessage: String ) { companion object { fun of(errorCode: ErrorCode) = ErrorResponse( errorCode = errorCode.name, errorMessage = errorCode.message ) } }CT-auth/src/main/kotlin/com/chuseok22/ctauth/core/token/TokenStore.kt (1)
3-13: LGTM! 깔끔한 인터페이스 설계입니다.토큰 저장소 추상화가 잘 정의되어 있습니다.
향후 토큰 재발급(reissue) 로직에서 저장된 리프레시 토큰을 조회해야 할 경우,
get(key: String): String?메서드 추가를 고려해 보세요./** * Key에 해당하는 리프레시 토큰 조회 */ fun get(key: String): String?CT-member/src/main/kotlin/com/chuseok22/ctmember/application/MemberService.kt (1)
12-12: 사용되지 않는 로거 변수
log변수가 선언되었지만 클래스 내에서 사용되지 않습니다. 향후 로깅이 필요할 경우를 대비한 것으로 보이나, 현재는 불필요한 코드입니다.🔧 제안: 사용하지 않을 경우 제거
-private val log = KotlinLogging.logger { } - @Service class MemberService(CT-auth/src/main/kotlin/com/chuseok22/ctauth/infrastructure/config/JwtConfig.kt (1)
24-28: Secret Key 최소 길이 검증 권장HMAC-SHA 알고리즘에서 보안을 위해 secret key는 최소 256비트(32바이트) 이상이어야 합니다. 잘못된 길이의 키가 설정될 경우 런타임 예외가 발생할 수 있으므로, 초기화 시점에 키 길이를 검증하는 것이 좋습니다.
🔒 제안: Secret Key 길이 검증 추가
@Bean fun jwtSecretKey(): SecretKey { val keyBytes: ByteArray = Decoders.BASE64.decode(properties.secretKey) + require(keyBytes.size >= 32) { "JWT secret key must be at least 256 bits (32 bytes)" } return Keys.hmacShaKeyFor(keyBytes) }CT-common/src/main/kotlin/com/chuseok22/ctcommon/application/exception/ErrorCode.kt (1)
10-20: 메시지 구두점 일관성 권장에러 메시지들의 구두점 사용이 일관되지 않습니다. 일부는 마침표로 끝나고(
"접근이 거부되었습니다."), 일부는 마침표가 없습니다. 통일된 스타일을 적용하는 것이 좋습니다.CT-auth/src/main/kotlin/com/chuseok22/ctauth/infrastructure/properties/JwtProperties.kt (1)
12-13: 만료 시간 값에 양수 검증 추가를 권장합니다.
accessExpMillis와refreshExpMillis가 0 또는 음수일 경우 토큰이 즉시 만료되거나 예상치 못한 동작이 발생할 수 있습니다.@field:Positive어노테이션을 추가하여 설정 오류를 방지하세요.♻️ 권장 수정사항
import jakarta.validation.constraints.NotBlank +import jakarta.validation.constraints.Positive import org.springframework.boot.context.properties.ConfigurationProperties import org.springframework.validation.annotation.Validated @Validated @ConfigurationProperties(prefix = "jwt") data class JwtProperties( @field:NotBlank val secretKey: String, + @field:Positive val accessExpMillis: Long, + @field:Positive val refreshExpMillis: Long, @field:NotBlank val issuer: String )CT-auth/src/main/kotlin/com/chuseok22/ctauth/infrastructure/config/SecurityConfig.kt (1)
28-30: CORS 설정이 기본값으로만 구성됨
.cors {}는 빈 설정으로, 명시적인 CORS 정책이 없습니다. 프로덕션 환경에서는 허용할 origin, method, header를 명시적으로 설정하는 것이 권장됩니다.♻️ 명시적 CORS 설정 예시
@Bean fun corsConfigurationSource(): CorsConfigurationSource { val configuration = CorsConfiguration().apply { allowedOrigins = listOf("https://campustable.shop") allowedMethods = listOf("GET", "POST", "PUT", "DELETE", "OPTIONS") allowedHeaders = listOf("*") allowCredentials = true } return UrlBasedCorsConfigurationSource().apply { registerCorsConfiguration("/**", configuration) } }그리고
filterChain에서:-.cors {} +.cors { it.configurationSource(corsConfigurationSource()) }CT-auth/src/main/kotlin/com/chuseok22/ctauth/infrastructure/constant/AuthConstants.kt (1)
9-10:ROOT_DOMAIN을 환경 설정으로 외부화 고려도메인을 하드코딩하면 개발/스테이징/프로덕션 환경 간 전환이 어렵습니다.
@ConfigurationProperties를 통해 외부 설정으로 관리하는 것이 좋습니다.CT-member/src/main/kotlin/com/chuseok22/ctmember/infrastructure/entity/Member.kt (1)
8-14: JPA 엔티티의equals()/hashCode()구현 고려UUID 기반 엔티티에서
equals()와hashCode()를 오버라이드하지 않으면 영속성 컨텍스트 외부에서 컬렉션 사용 시 예상치 못한 동작이 발생할 수 있습니다.id기반으로 구현을 추가하는 것이 권장됩니다.♻️ equals/hashCode 구현 예시
override fun equals(other: Any?): Boolean { if (this === other) return true if (other !is Member) return false return id != null && id == other.id } override fun hashCode(): Int = javaClass.hashCode()
📜 Review details
Configuration used: Path: .coderabbit.yaml
Review profile: CHILL
Plan: Pro
📒 Files selected for processing (37)
CT-auth/build.gradle.ktsCT-auth/src/main/kotlin/com/chuseok22/ctauth/core/token/TokenProvider.ktCT-auth/src/main/kotlin/com/chuseok22/ctauth/core/token/TokenStore.ktCT-auth/src/main/kotlin/com/chuseok22/ctauth/core/user/UserPrincipal.ktCT-auth/src/main/kotlin/com/chuseok22/ctauth/infrastructure/config/JwtConfig.ktCT-auth/src/main/kotlin/com/chuseok22/ctauth/infrastructure/config/SecurityConfig.ktCT-auth/src/main/kotlin/com/chuseok22/ctauth/infrastructure/constant/AuthConstants.ktCT-auth/src/main/kotlin/com/chuseok22/ctauth/infrastructure/constant/SecurityUrls.ktCT-auth/src/main/kotlin/com/chuseok22/ctauth/infrastructure/filter/TokenAuthenticationFilter.ktCT-auth/src/main/kotlin/com/chuseok22/ctauth/infrastructure/jwt/JwtProvider.ktCT-auth/src/main/kotlin/com/chuseok22/ctauth/infrastructure/jwt/JwtStore.ktCT-auth/src/main/kotlin/com/chuseok22/ctauth/infrastructure/properties/JwtProperties.ktCT-auth/src/main/kotlin/com/chuseok22/ctauth/infrastructure/user/CustomUserDetails.ktCT-auth/src/main/kotlin/com/chuseok22/ctauth/infrastructure/user/CustomUserDetailsService.ktCT-auth/src/main/kotlin/com/chuseok22/ctauth/infrastructure/util/AuthUtil.ktCT-common/build.gradle.ktsCT-common/src/main/kotlin/com/chuseok22/ctcommon/application/exception/CustomException.ktCT-common/src/main/kotlin/com/chuseok22/ctcommon/application/exception/ErrorCode.ktCT-common/src/main/kotlin/com/chuseok22/ctcommon/application/exception/ErrorResponse.ktCT-common/src/main/kotlin/com/chuseok22/ctcommon/core/time/TimeProvider.ktCT-common/src/main/kotlin/com/chuseok22/ctcommon/infrastructure/config/TimeConfig.ktCT-common/src/main/kotlin/com/chuseok22/ctcommon/infrastructure/persistence/BaseEntity.ktCT-common/src/main/kotlin/com/chuseok22/ctcommon/infrastructure/time/SystemTimeProvider.ktCT-member/src/main/kotlin/com/chuseok22/ctmember/application/MemberService.ktCT-member/src/main/kotlin/com/chuseok22/ctmember/core/constant/Role.ktCT-member/src/main/kotlin/com/chuseok22/ctmember/infrastructure/entity/Member.ktCT-member/src/main/kotlin/com/chuseok22/ctmember/infrastructure/repository/MemberRepository.ktCT-redis/build.gradle.ktsCT-redis/src/main/kotlin/com/chuseok22/ctredis/infrastructure/config/RedisConfig.ktCT-redis/src/main/kotlin/com/chuseok22/ctredis/infrastructure/properties/RedisProperties.ktCT-web/src/main/kotlin/com/chuseok22/ctweb/CampusTableServerApplication.ktCT-web/src/main/kotlin/com/chuseok22/ctweb/application/exception/GlobalExceptionHandler.ktCT-web/src/main/kotlin/com/chuseok22/ctweb/infrastructure/config/ComponentScanConfig.ktCT-web/src/main/kotlin/com/chuseok22/ctweb/infrastructure/config/CorsConfig.ktCT-web/src/main/kotlin/com/chuseok22/ctweb/infrastructure/config/SwaggerConfig.ktgradle/libs.versions.tomlsettings.gradle.kts
🧰 Additional context used
🪛 detekt (1.23.8)
CT-redis/src/main/kotlin/com/chuseok22/ctredis/infrastructure/properties/RedisProperties.kt
[warning] 12-13: The class or object RedisProperties is empty.
(detekt.empty-blocks.EmptyClassBlock)
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (1)
- GitHub Check: build
🔇 Additional comments (31)
CT-common/src/main/kotlin/com/chuseok22/ctcommon/core/time/TimeProvider.kt (1)
5-8: LGTM!테스트 가능한 시간 추상화를 위한 깔끔한 인터페이스입니다.
Instant를 반환 타입으로 사용한 것은 타임존 독립적인 시간 처리에 적합합니다.core패키지에 인터페이스를 배치하고 구현체를infrastructure에 분리한 구조도 좋습니다.CT-common/src/main/kotlin/com/chuseok22/ctcommon/infrastructure/time/SystemTimeProvider.kt (1)
7-13: LGTM!
Clock을 주입받아 테스트 시 시간을 제어할 수 있는 좋은 구현입니다.TimeConfig에서 명시적으로 빈을 생성하므로@Component어노테이션 없이도 정상 동작합니다.CT-common/src/main/kotlin/com/chuseok22/ctcommon/infrastructure/config/TimeConfig.kt (1)
9-21: LGTM!UTC 기반
Clock빈과TimeProvider빈이 올바르게 구성되어 있습니다. 서버 환경에서Clock.systemUTC()를 사용하는 것은 타임존 혼란을 방지하는 좋은 선택입니다. 테스트 시에는Clock.fixed()를 주입하여 시간을 고정할 수 있어 단위 테스트 작성이 용이합니다.CT-common/src/main/kotlin/com/chuseok22/ctcommon/infrastructure/persistence/BaseEntity.kt (3)
11-12: 클래스 구조 LGTM!
@MappedSuperclass와@EntityListeners(AuditingEntityListener::class)조합으로 Spring Data JPA 감사(auditing) 기능이 올바르게 구성되었습니다.
29-39: Soft delete 필드 구성 LGTM!
isDeleted필드는 non-null로,deletedAt필드는 nullable로 적절하게 구성되었습니다. 삭제되지 않은 엔티티에서deletedAt이 null인 것이 자연스러운 설계입니다.
18-27: 감사(auditing) 필드 구현이 올바릅니다.
@field:접두사를 사용하여 Kotlin에서 JPA 어노테이션이 backing field에 적용되도록 올바르게 처리했습니다.Instant? = null타입과nullable = false조합은 Spring Data JPA auditing에서 표준 패턴입니다.TIMESTAMPTZ는 프로젝트에서 PostgreSQL을 유일한 대상 데이터베이스로 사용하고 있기 때문에 적절합니다.CT-common/build.gradle.kts (1)
28-30: LGTM!
kotlin-logging의존성을api스코프로 추가한 것은 적절합니다. 이를 통해 CT-common에 의존하는 모든 모듈에서 일관된 로깅 유틸리티를 사용할 수 있습니다.settings.gradle.kts (1)
6-6: LGTM!Redis 모듈을 프로젝트에 포함시키는 것은 PR 목적에 부합합니다.
CT-web/src/main/kotlin/com/chuseok22/ctweb/infrastructure/config/ComponentScanConfig.kt (1)
7-9: LGTM!
basePackages를"com.chuseok22"으로 설정하면 해당 패키지와 모든 하위 패키지(ctauth, ctredis, ctweb 등)가 스캔됩니다. 멀티 모듈 프로젝트에서 올바른 설정입니다.CT-member/src/main/kotlin/com/chuseok22/ctmember/core/constant/Role.kt (1)
3-6: LGTM! Spring Security 권한 명명 규칙을 올바르게 따르고 있습니다.
ROLE_접두사를 사용한 enum 정의가 Spring Security의 기본 권한 체계와 호환됩니다.CT-web/src/main/kotlin/com/chuseok22/ctweb/infrastructure/config/SwaggerConfig.kt (1)
27-28: LGTM!메서드 이름이
OpenAPI()에서openAPI()로 변경되어 Kotlin/Java의 camelCase 네이밍 컨벤션을 따르게 되었습니다. 적절한 리팩토링입니다.CT-auth/src/main/kotlin/com/chuseok22/ctauth/infrastructure/constant/SecurityUrls.kt (1)
3-17: LGTM! 보안 화이트리스트가 잘 정의되어 있습니다.인증 및 Swagger 문서 엔드포인트가 적절히 포함되어 있습니다.
향후 Actuator 헬스체크 엔드포인트(
/actuator/health) 등 추가 공개 엔드포인트가 필요할 수 있는지 확인해 주세요. 현재 설정으로는 해당 엔드포인트들도 인증이 필요합니다.CT-redis/build.gradle.kts (1)
1-19: LGTM!라이브러리 모듈 구성이 올바르게 설정되어 있습니다.
bootJar비활성화와jar활성화 패턴이 적절하며,spring-boot-starter-data-redis를api스코프로 선언하여 이 모듈을 의존하는 다른 모듈에서 Redis 의존성을 transitively 사용할 수 있도록 했습니다.CT-auth/src/main/kotlin/com/chuseok22/ctauth/infrastructure/user/CustomUserDetailsService.kt (1)
16-21: JWT 토큰과 사용자 조회 흐름 확인 완료 - 일관성 문제 없음JWT 토큰의 subject는
memberId(UUID)를 사용하므로,username파라미터로studentName조회하는 것은 일관성이 있습니다. Spring Security의UserDetailsService인터페이스 제약상 메서드명은loadUserByUsername이어야 하지만, 이 도메인에서 "username"은studentName을 의미하므로 문제없습니다. 구현이 올바르고findByStudentNameAndDeletedFalse메서드도 정상 작동합니다.CT-member/src/main/kotlin/com/chuseok22/ctmember/application/MemberService.kt (1)
19-23: LGTM!
@Transactional(readOnly = true)적용과 soft-delete 필터링을 통한 멤버 조회 로직이 적절합니다. 멤버가 없을 경우CustomException을 던지는 패턴도 일관성 있게 구현되었습니다.CT-auth/src/main/kotlin/com/chuseok22/ctauth/infrastructure/jwt/JwtStore.kt (1)
7-16: LGTM!
TokenStore인터페이스 구현이 적절합니다. Redis를 통한 refresh token 저장 및 TTL 관리가 올바르게 구현되었습니다. Bean 등록은JwtConfig에서 처리하므로@Component어노테이션 없이 작동합니다.CT-auth/src/main/kotlin/com/chuseok22/ctauth/infrastructure/config/JwtConfig.kt (1)
33-44: LGTM!
JwtProvider와JwtStoreBean 등록이 적절하게 구성되었습니다. 의존성 주입 방식이 명확하고 테스트하기 용이한 구조입니다.CT-auth/src/main/kotlin/com/chuseok22/ctauth/core/user/UserPrincipal.kt (1)
3-18: LGTM!
UserPrincipal인터페이스가 인증 주체의 핵심 정보를 명확하게 정의하고 있습니다.CustomUserDetails에서 이 인터페이스를 구현하여 Spring Security와 통합하는 구조가 적절합니다.
getMemberId()가String을 반환하는 이유가 확인되었습니다.Member엔티티의id는UUID타입이며,CustomUserDetails에서member.id.toString()으로 명시적으로 변환하고 있습니다. 이는 REST API와 JSON 직렬화 호환성을 위한 의도적인 설계이므로 현재 구현이 적절합니다. UUID 반환 타입이 특별히 필요하지 않은 한, 현재 구현은 유지해도 됩니다.CT-auth/src/main/kotlin/com/chuseok22/ctauth/core/token/TokenProvider.kt (1)
5-31: LGTM!인터페이스 설계가 깔끔하고 토큰 생성, 검증, 파싱에 필요한 핵심 메서드들이 잘 정의되어 있습니다. 구현체와의 분리를 통해 테스트 용이성과 확장성을 확보했습니다.
CT-auth/src/main/kotlin/com/chuseok22/ctauth/infrastructure/util/AuthUtil.kt (1)
6-27: LGTM!유틸리티 클래스가 깔끔하게 구현되었습니다. Kotlin의 null-safe 연산자(
?.,takeIf)를 적절히 활용하여 Bearer 토큰 추출 로직이 안전하게 처리됩니다. 빈 문자열과 공백 처리도 잘 되어 있습니다.CT-web/src/main/kotlin/com/chuseok22/ctweb/application/exception/GlobalExceptionHandler.kt (1)
14-32: CustomException 핸들러 구현이 적절합니다.예외 객체와 함께 로깅하여 스택 트레이스가 포함되고,
errorCode의 HTTP 상태와 메시지를 올바르게 활용하고 있습니다.gradle/libs.versions.toml (1)
3-3: Kotlin 2.3.0는 2025년 12월 16일에 JetBrains에서 공식 릴리스했으며, Spring Boot 3.5.9는 Kotlin 1.7.x 이상을 지원하므로 호환성에 문제가 없습니다.CT-auth/src/main/kotlin/com/chuseok22/ctauth/infrastructure/user/CustomUserDetails.kt (1)
10-36: 전체적인 구현이 적절합니다.JWT 기반 인증에서
getPassword()가 빈 문자열을 반환하는 것은 적절하며, Spring Security 6+에서는isAccountNonExpired(),isAccountNonLocked()등의 기본 구현이true를 반환하므로 별도 오버라이드가 필요 없습니다.CT-auth/src/main/kotlin/com/chuseok22/ctauth/infrastructure/config/SecurityConfig.kt (1)
26-52: SecurityFilterChain 구성이 적절합니다.CSRF 비활성화, stateless 세션 정책, JWT 필터 추가 등 JWT 기반 인증에 적합한 구성입니다.
CT-auth/src/main/kotlin/com/chuseok22/ctauth/infrastructure/constant/AuthConstants.kt (1)
1-23: 상수 구성이 체계적입니다.인증 관련 상수들이 용도별로 잘 분류되어 있으며, 코드 전반에서 일관되게 사용될 수 있습니다.
CT-auth/src/main/kotlin/com/chuseok22/ctauth/infrastructure/filter/TokenAuthenticationFilter.kt (2)
87-97:ApiRequestType.OTHER에 대한 처리 명확화 필요정의되지 않은 URI 타입에 대해 경고 로그만 남기고 계속 진행합니다. 의도한 동작인지 확인이 필요합니다. 보안상 알 수 없는 경로는 차단하는 것이 안전할 수 있습니다.
30-75: 전체적인 필터 구조가 적절합니다.화이트리스트 체크, 토큰 검증, 인증 컨텍스트 설정 흐름이 잘 구성되어 있습니다.
CT-redis/src/main/kotlin/com/chuseok22/ctredis/infrastructure/config/RedisConfig.kt (1)
28-38: Redis 연결 설정이 적절합니다.Lettuce 기반 standalone 구성이 잘 되어 있습니다. 프로덕션에서는 연결 풀링이나 Sentinel/Cluster 구성을 고려해볼 수 있습니다.
CT-member/src/main/kotlin/com/chuseok22/ctmember/infrastructure/entity/Member.kt (1)
35-51: 팩토리 메서드 패턴 적용이 잘 되어 있습니다.
create()팩토리 메서드와 private 생성자를 통해 객체 생성을 캡슐화하고, 입력값 정규화/검증을 일관되게 적용하고 있습니다.CT-auth/src/main/kotlin/com/chuseok22/ctauth/infrastructure/jwt/JwtProvider.kt (2)
22-36: 토큰 생성 로직이 적절합니다.액세스 토큰과 리프레시 토큰을 카테고리로 구분하고, 만료 시간을 properties에서 관리하는 구조가 잘 되어 있습니다.
38-59: 토큰 검증 예외 처리가 잘 구성되어 있습니다.만료된 토큰은 재전달하고, 다른 유효하지 않은 토큰 유형들은 적절히 로깅하며 false를 반환합니다.
CT-auth/src/main/kotlin/com/chuseok22/ctauth/infrastructure/filter/TokenAuthenticationFilter.kt
Show resolved
Hide resolved
CT-auth/src/main/kotlin/com/chuseok22/ctauth/infrastructure/filter/TokenAuthenticationFilter.kt
Show resolved
Hide resolved
CT-auth/src/main/kotlin/com/chuseok22/ctauth/infrastructure/jwt/JwtProvider.kt
Show resolved
Hide resolved
CT-common/src/main/kotlin/com/chuseok22/ctcommon/application/exception/ErrorCode.kt
Outdated
Show resolved
Hide resolved
CT-member/src/main/kotlin/com/chuseok22/ctmember/infrastructure/repository/MemberRepository.kt
Show resolved
Hide resolved
CT-redis/src/main/kotlin/com/chuseok22/ctredis/infrastructure/config/RedisConfig.kt
Show resolved
Hide resolved
CT-web/src/main/kotlin/com/chuseok22/ctweb/application/exception/GlobalExceptionHandler.kt
Show resolved
Hide resolved
There was a problem hiding this comment.
Actionable comments posted: 0
🧹 Nitpick comments (1)
CT-common/src/main/kotlin/com/chuseok22/ctcommon/infrastructure/persistence/BaseEntity.kt (1)
44-47:delete()메서드에 멱등성(idempotency) 검사 추가를 고려해 주세요.이미 삭제된 엔티티에 대해
delete()를 다시 호출하면 기존deletedAt타임스탬프가 덮어씌워집니다. 감사(audit) 추적 관점에서 최초 삭제 시점을 보존하는 것이 중요할 수 있습니다.♻️ 멱등성 검사를 추가한 수정 제안
fun delete(now: Instant) { + if (deleted) return this.deleted = true deletedAt = now }또는 이미 삭제된 경우 예외를 던지도록 할 수도 있습니다:
fun delete(now: Instant) { + require(!deleted) { "이미 삭제된 엔티티입니다." } this.deleted = true deletedAt = now }
📜 Review details
Configuration used: Path: .coderabbit.yaml
Review profile: CHILL
Plan: Pro
📒 Files selected for processing (1)
CT-common/src/main/kotlin/com/chuseok22/ctcommon/infrastructure/persistence/BaseEntity.kt
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (1)
- GitHub Check: build
🔇 Additional comments (2)
CT-common/src/main/kotlin/com/chuseok22/ctcommon/infrastructure/persistence/BaseEntity.kt (2)
11-13: LGTM! 표준 Spring Data JPA 감사(auditing) 설정입니다.
@MappedSuperclass와AuditingEntityListener조합이 올바르게 구성되었습니다. AI 요약에 따르면CampusTableServerApplication에@EnableJpaAuditing이 활성화되어 있어 정상 동작할 것입니다.
18-27: PostgreSQL 특화 컬럼 타입 사용 확인이 필요합니다.
TIMESTAMPTZ는 PostgreSQL 전용 컬럼 타입입니다. 프로젝트가 PostgreSQL만 사용한다면 문제없지만, 다른 데이터베이스로의 마이그레이션이 필요할 경우 호환성 문제가 발생할 수 있습니다.
There was a problem hiding this comment.
Actionable comments posted: 2
🤖 Fix all issues with AI agents
In
@CT-auth/src/main/kotlin/com/chuseok22/ctauth/infrastructure/filter/TokenAuthenticationFilter.kt:
- Around line 107-124: Wrap the UUID parsing inside handleValidToken: after
obtaining memberId via tokenProvider.getMemberId(token), catch
IllegalArgumentException around UUID.fromString(memberId) and convert it to a
clear authentication failure (e.g., throw new BadCredentialsException or
AuthenticationServiceException with a descriptive message) so the error is
explicit instead of propagating a raw IllegalArgumentException; ensure you do
not call memberService.findMemberById or proceed to filterChain.doFilter when
parsing fails.
In @gradle/libs.versions.toml:
- Around line 3-8: The Kotlin version entry (kotlin = "2.3.0") is not known to
be compatible with Spring Boot 3.5.9, so either pin Kotlin to a Spring
Boot–compatible version or upgrade Spring Boot to a 4.x line that explicitly
supports Kotlin 2.3; update the kotlin key accordingly and run full compile/test
matrix; also bump jjwt from "0.12.7" to "0.13.0" by changing the jjwt key, and
leave kotlinLogging as-is ("7.0.14") unless tests surface issues.
🧹 Nitpick comments (1)
CT-auth/src/main/kotlin/com/chuseok22/ctauth/infrastructure/jwt/JwtProvider.kt (1)
71-78: getExpiredAt에서 Exception 대신 구체적인 예외 타입을 사용하는 것을 고려하세요.모든
Exception을 catch하면 예상치 못한 오류(예:NullPointerException,OutOfMemoryError등)도INVALID_JWT로 변환될 수 있습니다.JwtException만 catch하는 것이 더 정확할 수 있습니다.♻️ 권장 수정
override fun getExpiredAt(token: String): Date { return try { getClaims(token).expiration - } catch (e: Exception) { + } catch (e: JwtException) { log.error(e) { "JWT 만료시간 추출 실패: ${e.message}" } throw CustomException(ErrorCode.INVALID_JWT) } }
📜 Review details
Configuration used: Path: .coderabbit.yaml
Review profile: CHILL
Plan: Pro
📒 Files selected for processing (8)
.github/workflows/spring-boot-cicd.ymlCT-auth/build.gradle.ktsCT-auth/src/main/kotlin/com/chuseok22/ctauth/infrastructure/filter/TokenAuthenticationFilter.ktCT-auth/src/main/kotlin/com/chuseok22/ctauth/infrastructure/jwt/JwtProvider.ktCT-auth/src/main/kotlin/com/chuseok22/ctauth/infrastructure/user/CustomUserDetails.ktCT-common/src/main/kotlin/com/chuseok22/ctcommon/application/exception/ErrorCode.ktCT-redis/src/main/kotlin/com/chuseok22/ctredis/infrastructure/config/RedisConfig.ktgradle/libs.versions.toml
🚧 Files skipped from review as they are similar to previous changes (2)
- CT-redis/src/main/kotlin/com/chuseok22/ctredis/infrastructure/config/RedisConfig.kt
- CT-common/src/main/kotlin/com/chuseok22/ctcommon/application/exception/ErrorCode.kt
🔇 Additional comments (9)
.github/workflows/spring-boot-cicd.yml (1)
4-6: LGTM!
main브랜치에 대한 CI/CD 트리거 활성화가 적절합니다. 이 변경으로 main 브랜치에 푸시될 때 워크플로우가 실행됩니다.CT-auth/src/main/kotlin/com/chuseok22/ctauth/infrastructure/user/CustomUserDetails.kt (1)
10-37: LGTM! JWT 기반 인증을 위한 UserDetails 구현이 적절합니다.
getPassword()가 빈 문자열을 반환하는 것은 JWT 기반 인증에서 적절하며,getMemberId()의 null 체크와 예외 처리도 잘 구현되어 있습니다.한 가지 참고 사항:
getName()은member.name을,getUsername()은member.studentName을 반환하는데, 두 필드의 의미 차이가 명확하다면 문제없지만, 추후 혼동을 방지하기 위해 문서화를 고려해 보세요.CT-auth/build.gradle.kts (1)
14-30: LGTM! 의존성 구성이 적절합니다.JWT 라이브러리 구성(
jjwt-api는implementation,jjwt-impl과jjwt-jackson은runtimeOnly)이 JJWT 권장 패턴을 따르고 있습니다.spring-security-test를api에서implementation으로 변경한 것도 테스트 의존성이 외부로 노출되지 않도록 하는 올바른 선택입니다.gradle/libs.versions.toml (1)
48-54: LGTM! JJWT 및 Kotlin Logging 라이브러리 선언이 적절합니다.JJWT 라이브러리(
jjwt-api,jjwt-impl,jjwt-jackson)와 Kotlin Logging 라이브러리가 올바르게 구성되어 있습니다.CT-auth/src/main/kotlin/com/chuseok22/ctauth/infrastructure/filter/TokenAuthenticationFilter.kt (2)
38-80: 필터 로직 구조가 적절합니다.화이트리스트 체크 후 토큰 검증, 예외 처리 분기가 명확합니다.
ExpiredJwtException을 별도로 처리하여 만료된 토큰에 대한 명확한 에러 코드를 반환하는 것이 좋습니다.
150-163: 에러 응답 처리가 잘 구현되어 있습니다.
ObjectMapper를 사용한 JSON 응답 생성과 적절한 Content-Type, 상태 코드, 인코딩 설정이 포함되어 있습니다.CT-auth/src/main/kotlin/com/chuseok22/ctauth/infrastructure/jwt/JwtProvider.kt (3)
61-69: getMemberId의 예외 처리가 개선될 수 있습니다.
JwtException을 catch한 후 로깅하고 다시 throw하는 패턴은 유효하지만,ExpiredJwtException도JwtException의 하위 클래스이므로 여기서 catch되어 원래의 예외 유형이 유지됩니다. 현재 구현이 의도된 동작이라면 문제없습니다.
80-103: LGTM! JWT 토큰 생성 및 검증 로직이 잘 구현되어 있습니다.
Instant.now()를 사용한 시간 처리, claim 설정, 서명 로직이 JJWT 라이브러리의 권장 패턴을 따르고 있습니다.parseSignedClaims()를 사용한 검증도 적절합니다.
38-59: 토큰 검증 로직이 적절합니다.
ExpiredJwtException을 rethrow하여 호출자가 만료된 토큰을 별도로 처리할 수 있게 하고, 다른 JWT 예외들은false를 반환하여 유효하지 않은 토큰으로 처리하는 전략이 적절합니다.
✨ 변경 사항
✅ 테스트
Summary by CodeRabbit
새 기능
개선 사항
기타
✏️ Tip: You can customize this high-level summary in your review settings.