20260112 #5 로그인 및 기타 인증 관련 로직 개발#7
Conversation
|
Note
|
| 코호트 / 파일(들) | 변경 요약 |
|---|---|
인증 요청 DTO \CT-auth/src/main/kotlin/com/chuseok22/ctauth/application/dto/request/LoginRequest.kt`, `CT-auth/src/main/kotlin/com/chuseok22/ctauth/application/dto/request/ReissueRequest.kt`` |
LoginRequest(sejongPortalId, sejongPortalPw, @field:NotBlank) 및 ReissueRequest(refreshToken, @field:NotBlank) 추가 |
인증 응답 DTO \CT-auth/src/main/kotlin/com/chuseok22/ctauth/application/dto/response/LoginResponse.kt`, `CT-auth/src/main/kotlin/com/chuseok22/ctauth/application/dto/response/ReissueResponse.kt`` |
LoginResponse(studentNumber, name, accessToken, refreshToken) 및 ReissueResponse(accessToken, refreshToken) 추가 |
인증 서비스 \CT-auth/src/main/kotlin/com/chuseok22/ctauth/application/service/AuthService.kt`` |
login, reissue, logout 구현 — Sejong 포털 로그인, 회원 조회/생성, TokenManager 호출(토큰 생성·저장·검증·삭제), 예외 매핑 (SEJONG_PORTAL_LOGIN_FAILED) |
토큰 추상화·구현 \CT-auth/src/main/kotlin/com/chuseok22/ctauth/core/token/TokenManager.kt`, `CT-auth/src/main/kotlin/com/chuseok22/ctauth/core/token/TokenPair.kt`, `CT-auth/src/main/kotlin/com/chuseok22/ctauth/infrastructure/jwt/JwtManager.kt`` |
TokenManager 인터페이스 및 TokenPair 추가, JwtManager 구현 추가(토큰 생성·Redis 기반 refresh 토큰 TTL 저장/삭제/검증, 예외 처리) |
토큰 저장소 변경 \CT-auth/src/main/kotlin/com/chuseok22/ctauth/core/token/TokenStore.kt`, `CT-auth/src/main/kotlin/com/chuseok22/ctauth/infrastructure/jwt/JwtStore.kt`` |
TokenStore.get(key): String? 인터페이스에 추가 및 JwtStore에 get 구현 추가(Redis 조회) |
웹 컨트롤러 \CT-web/src/main/kotlin/com/chuseok22/ctweb/application/controller/auth/AuthController.kt`` |
/api/auth에 POST /login, POST /reissue, POST /logout 엔드포인트 추가 (LogMonitoring 적용). 로그아웃은 CustomUserDetails에서 member 추출해 AuthService.logout 호출 |
사용자·보안 설정 \CT-auth/src/main/kotlin/com/chuseok22/ctauth/infrastructure/user/CustomUserDetails.kt`, `CT-auth/src/main/kotlin/com/chuseok22/ctauth/infrastructure/user/CustomUserDetailsService.kt`, `CT-auth/src/main/kotlin/com/chuseok22/ctauth/infrastructure/constant/SecurityUrls.kt`` |
CustomUserDetails.member 가시성 공개(val member), getUsername() → member.studentNumber로 변경, UserDetailsService 조회 기준을 studentNumber로 변경, AUTH_WHITELIST에 "/actuator/health" 추가 |
회원 엔티티·리포지토리 \CT-member/src/main/kotlin/com/chuseok22/ctmember/infrastructure/entity/Member.kt`, `CT-member/src/main/kotlin/com/chuseok22/ctmember/infrastructure/repository/MemberRepository.kt`**` |
엔티티 필드·컬럼 및 팩토리 메서드 studentName → studentNumber로 리네임, 리포지토리 쿼리명도 findByStudentNumberAndDeletedFalse로 변경 |
예외 코드 \CT-common/src/main/kotlin/com/chuseok22/ctcommon/application/exception/ErrorCode.kt`**` |
SEJONG_PORTAL_LOGIN_FAILED 에러 코드 추가(HTTP 400, 메시지: "세종대학교 포털 로그인 실패") |
DB 마이그레이션 \CT-web/src/main/resources/db/migration/V20260112_132017__create_member_table.sql`**` |
member 테이블 컬럼 및 인덱스에서 student_name → student_number로 변경 (컬럼 정의·유니크/인덱스명 수정) |
로깅 레벨 조정 \CT-auth/src/main/kotlin/com/chuseok22/ctauth/infrastructure/jwt/JwtProvider.kt`**` |
토큰 생성 성공 로그 레벨을 info → debug로 변경 |
Sequence Diagram(s)
sequenceDiagram
participant Client
participant AuthController
participant AuthService
participant SejongPortalLoginService
participant MemberRepository
participant TokenManager
participant TokenStore
Client->>AuthController: POST /api/auth/login (sejongPortalId, sejongPortalPw)
AuthController->>AuthService: login(LoginRequest)
AuthService->>SejongPortalLoginService: sejongPortalLogin(id, pw)
SejongPortalLoginService-->>AuthService: SejongMemberInfo (studentNumber, name)
AuthService->>MemberRepository: findByStudentNumberAndDeletedFalse(studentNumber)
alt Member not found
AuthService->>MemberRepository: save(new Member(studentNumber, name))
MemberRepository-->>AuthService: Member
else Member found
MemberRepository-->>AuthService: Member
end
AuthService->>TokenManager: createTokenPair(memberId)
TokenManager-->>AuthService: TokenPair(accessToken, refreshToken)
AuthService->>TokenManager: saveRefreshTokenTtl(memberId, refreshToken)
AuthService-->>AuthController: LoginResponse
AuthController-->>Client: 200 OK + LoginResponse
sequenceDiagram
participant Client
participant AuthController
participant AuthService
participant TokenManager
Client->>AuthController: POST /api/auth/reissue (refreshToken)
AuthController->>AuthService: reissue(ReissueRequest)
AuthService->>TokenManager: validateSavedToken(refreshToken)
TokenManager-->>AuthService: (valid or throws)
AuthService->>TokenManager: removeRefreshTokenTtl(memberId)
AuthService->>TokenManager: createTokenPair(memberId)
TokenManager-->>AuthService: TokenPair(newAccess, newRefresh)
AuthService->>TokenManager: saveRefreshTokenTtl(memberId, newRefresh)
AuthService-->>AuthController: ReissueResponse
AuthController-->>Client: 200 OK + ReissueResponse
Estimated code review effort
🎯 3 (Moderate) | ⏱️ ~25 minutes
Possibly related PRs
- 20260105 #3 spring security 기본 설정 #4: 인증 관련 토큰/스토어 및 Security 구성 요소를 함께 수정하는 PR로 코드 레벨 연관성 높음
- 20260112 #5 로그인 및 기타 인증 관련 로직 개발 #6: 회원 엔티티 및 마이그레이션에서 studentName→studentNumber 변경이 겹쳐 연관됨
Poem
🐰 포털에 달려가 아이디 톡, 비번 톡,
토큰 두 개 뿅! 새로 발급됐네 🎉
학번은 숫자에 깔끔히 정착하고,
Redis에 숨결 담아 갱신은 또 출발,
토끼가 춤추며 말하네: 인증, 완성했지요. 🥕
🚥 Pre-merge checks | ✅ 2 | ❌ 1
❌ Failed checks (1 warning)
| Check name | Status | Explanation | Resolution |
|---|---|---|---|
| Docstring Coverage | Docstring coverage is 14.81% 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 제목은 로그인 및 인증 관련 로직 개발이라는 주요 변경 내용을 명확하게 반영하고 있으며, 변경 사항의 핵심을 잘 요약하고 있습니다. |
✏️ Tip: You can configure your own custom pre-merge checks in the settings.
✨ Finishing touches
- 📝 Generate docstrings
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: 4
🤖 Fix all issues with AI agents
In
@CT-auth/src/main/kotlin/com/chuseok22/ctauth/application/service/AuthService.kt:
- Around line 56-71: The reissue method currently trusts the refresh token
extracted via tokenProvider.getMemberId without checking that the token still
exists in storage; add a validation step using a new
TokenManager.validateRefreshToken(memberId: String, refreshToken: String):
Boolean and call it in reissue (after obtaining memberId and before
removeRefreshTokenTtl/createTokenPair) to verify the provided refreshToken is
the active one for that member, throw an appropriate exception or return an
error if validation fails, and only then proceed to
removeRefreshTokenTtl(memberId) and createTokenPair(memberId).
In
@CT-web/src/main/kotlin/com/chuseok22/ctweb/application/controller/auth/AuthController.kt:
- Around line 35-39: The logout endpoint currently returns 204 without
performing logout; update AuthController.logout to extract the authenticated
user's memberId from the Security Context (e.g., via SecurityContextHolder or
the existing authentication principal), call authService.logout(memberId) to
remove stored refresh tokens, and then return
ResponseEntity.status(HttpStatus.NO_CONTENT).build(); ensure you handle the case
where no authenticated principal/memberId is found (return appropriate error or
401) and use the existing authService.logout(memberId) method name to locate the
service call.
- Around line 31-33: The reissue endpoint in AuthController.kt is missing the
@RequestBody annotation on the ReissueRequest parameter; update the function
signature for reissue(request: ReissueRequest) to annotate the parameter with
@RequestBody (import org.springframework.web.bind.annotation.RequestBody if
needed) so the request body is bound correctly when calling
authService.reissue(request).
- Around line 25-27: The login method in AuthController.kt is missing the
@RequestBody annotation on the LoginRequest parameter, so Spring won't
deserialize the HTTP request body into LoginRequest; add the @RequestBody
annotation to the request parameter of fun login(request: LoginRequest):
ResponseEntity<LoginResponse> (and import
org.springframework.web.bind.annotation.RequestBody if needed) so
authService.login receives the deserialized body.
🧹 Nitpick comments (2)
CT-auth/src/main/kotlin/com/chuseok22/ctauth/application/service/AuthService.kt (2)
29-54: 외부 서비스 호출이 트랜잭션 내부에 포함되어 있습니다.
sejongPortalLogin()호출이@Transactional범위 내에서 실행되어, 외부 포털 응답이 느릴 경우 DB 커넥션이 불필요하게 오래 점유됩니다. 부하가 높은 상황에서 커넥션 풀 고갈로 이어질 수 있습니다.♻️ 트랜잭션 범위 조정 제안
- @Transactional fun login(request: LoginRequest): LoginResponse { val sejongMemberInfo = sejongPortalLogin(request.sejongPortalId, request.sejongPortalPw) val studentNumber = sejongMemberInfo.studentId val name = sejongMemberInfo.name - val member = memberRepository.findByStudentNumberAndDeletedFalse(studentNumber) - ?: run { - log.info { "신규 회원 로그인: 학번=$studentNumber, 이름=$name" } - val newMember = Member.create(studentNumber, name) - memberRepository.save(newMember) - } + val member = findOrCreateMember(studentNumber, name) // 토큰 발급 val tokenPair = tokenManager.createTokenPair(member.id.toString()) log.info { "로그인 성공: 학번=$studentNumber, 이름=$name" } return LoginResponse( studentNumber = studentNumber, name = name, accessToken = tokenPair.accessToken, refreshToken = tokenPair.refreshToken ) } + @Transactional + private fun findOrCreateMember(studentNumber: String, name: String): Member { + return memberRepository.findByStudentNumberAndDeletedFalse(studentNumber) + ?: run { + log.info { "신규 회원 로그인: 학번=$studentNumber, 이름=$name" } + val newMember = Member.create(studentNumber, name) + memberRepository.save(newMember) + } + }
73-79:@Transactional은 필요하지 않습니다.
logout()메서드는tokenStore.remove(key)(Redis 삭제)만 수행하며, 데이터베이스 트랜잭션과 무관합니다. 불필요한 트랜잭션 오버헤드를 제거하기 위해 이 어노테이션을 삭제하세요.
📜 Review details
Configuration used: Path: .coderabbit.yaml
Review profile: CHILL
Plan: Pro
📒 Files selected for processing (16)
CT-auth/src/main/kotlin/com/chuseok22/ctauth/application/dto/request/LoginRequest.ktCT-auth/src/main/kotlin/com/chuseok22/ctauth/application/dto/request/ReissueRequest.ktCT-auth/src/main/kotlin/com/chuseok22/ctauth/application/dto/response/LoginResponse.ktCT-auth/src/main/kotlin/com/chuseok22/ctauth/application/dto/response/ReissueResponse.ktCT-auth/src/main/kotlin/com/chuseok22/ctauth/application/service/AuthService.ktCT-auth/src/main/kotlin/com/chuseok22/ctauth/core/token/TokenManager.ktCT-auth/src/main/kotlin/com/chuseok22/ctauth/core/token/TokenPair.ktCT-auth/src/main/kotlin/com/chuseok22/ctauth/infrastructure/constant/SecurityUrls.ktCT-auth/src/main/kotlin/com/chuseok22/ctauth/infrastructure/jwt/JwtManager.ktCT-auth/src/main/kotlin/com/chuseok22/ctauth/infrastructure/user/CustomUserDetails.ktCT-auth/src/main/kotlin/com/chuseok22/ctauth/infrastructure/user/CustomUserDetailsService.ktCT-common/src/main/kotlin/com/chuseok22/ctcommon/application/exception/ErrorCode.ktCT-member/src/main/kotlin/com/chuseok22/ctmember/infrastructure/entity/Member.ktCT-member/src/main/kotlin/com/chuseok22/ctmember/infrastructure/repository/MemberRepository.ktCT-web/src/main/kotlin/com/chuseok22/ctweb/application/controller/auth/AuthController.ktCT-web/src/main/resources/db/migration/V20260112_132017__create_member_table.sql
🧰 Additional context used
🧬 Code graph analysis (3)
CT-auth/src/main/kotlin/com/chuseok22/ctauth/application/dto/response/ReissueResponse.kt (1)
CT-auth/src/main/kotlin/com/chuseok22/ctauth/core/token/TokenProvider.kt (1)
createAccessToken(5-31)
CT-auth/src/main/kotlin/com/chuseok22/ctauth/application/dto/request/LoginRequest.kt (1)
CT-auth/src/main/kotlin/com/chuseok22/ctauth/infrastructure/util/AuthUtil.kt (1)
it(26-26)
CT-auth/src/main/kotlin/com/chuseok22/ctauth/core/token/TokenManager.kt (3)
CT-auth/src/main/kotlin/com/chuseok22/ctauth/core/token/TokenStore.kt (2)
save(3-14)save(8-8)CT-auth/src/main/kotlin/com/chuseok22/ctauth/core/token/TokenProvider.kt (3)
createAccessToken(5-31)createRefreshToken(15-15)createAccessToken(10-10)CT-auth/src/main/kotlin/com/chuseok22/ctauth/infrastructure/jwt/JwtProvider.kt (2)
createRefreshToken(30-36)createAccessToken(22-28)
🔇 Additional comments (15)
CT-auth/src/main/kotlin/com/chuseok22/ctauth/application/dto/response/ReissueResponse.kt (1)
1-6: LGTM!토큰 재발급 응답을 위한 DTO가 적절하게 정의되었습니다.
TokenPair와 구조가 동일하지만, 레이어드 아키텍처 관점에서 application 계층의 DTO와 core 계층의 도메인 객체를 분리한 것은 올바른 접근입니다.CT-common/src/main/kotlin/com/chuseok22/ctcommon/application/exception/ErrorCode.kt (1)
21-23: LGTM!새로운 에러 코드가 기존 컨벤션을 따르며 적절하게 추가되었습니다.
BAD_REQUEST상태코드는 로그인 실패 시나리오에 적합합니다.CT-auth/src/main/kotlin/com/chuseok22/ctauth/core/token/TokenPair.kt (1)
1-6: LGTM!토큰 쌍을 표현하는 도메인 객체가 core 계층에 적절하게 정의되었습니다. 불변 속성(
val)을 사용한 것이 좋습니다.CT-auth/src/main/kotlin/com/chuseok22/ctauth/infrastructure/user/CustomUserDetails.kt (1)
26-28: 좋습니다!
getUsername()이 학번(studentNumber)을 반환하도록 변경된 것은 적절합니다. Member 엔티티에서studentNumber는lateinit var선언과@Column(nullable = false)제약으로 인해 항상 non-null이 보장됩니다. 또한 생성자의normalizeStudentNumber()메서드를 통해 값이 검증되므로, 안전하고 의미론적으로도 타당한 변경입니다.CT-auth/src/main/kotlin/com/chuseok22/ctauth/infrastructure/constant/SecurityUrls.kt (1)
17-19: 헬스체크 엔드포인트 화이트리스트 추가 승인
/actuator/health를 AUTH_WHITELIST에 추가한 것은 적절한 변경입니다. SecurityConfig에서 AUTH_WHITELIST에 등록된 URL만 인증 없이 접근 가능하고, 나머지 모든 요청은anyRequest().authenticated()로 인증이 필수로 설정되어 있습니다. 따라서/actuator/env,/actuator/beans등 다른 actuator 엔드포인트는 인증이 필요하므로 보안에 문제가 없습니다. Docker 헬스체크에 필요한 패턴이 올바르게 구현되었습니다.CT-auth/src/main/kotlin/com/chuseok22/ctauth/infrastructure/user/CustomUserDetailsService.kt (1)
17-20: LGTM!
studentName에서studentNumber로 변경된 도메인 모델과 일관성 있게findByStudentNumberAndDeletedFalse를 사용하도록 올바르게 수정되었습니다. Spring Security의UserDetailsService인터페이스 계약에 따라 파라미터 이름이username으로 유지되는 것도 적절합니다.CT-auth/src/main/kotlin/com/chuseok22/ctauth/application/dto/request/ReissueRequest.kt (1)
5-8: LGTM!토큰 재발급 요청을 위한 간결한 DTO입니다.
@field:NotBlank유효성 검사가 올바르게 적용되었습니다.CT-auth/src/main/kotlin/com/chuseok22/ctauth/application/dto/request/LoginRequest.kt (1)
5-10: LGTM!로그인 요청을 위한 DTO가 적절한 유효성 검사와 함께 구현되었습니다. 참고로, 이 DTO를 로깅할 때
sejongPortalPw필드가 노출되지 않도록 주의가 필요합니다. 필요하다면toString()메서드를 오버라이드하여 비밀번호를 마스킹하는 것을 고려해 주세요.CT-auth/src/main/kotlin/com/chuseok22/ctauth/application/dto/response/LoginResponse.kt (1)
3-8: LGTM!로그인 응답 DTO가 필요한 정보를 포함하여 잘 구성되었습니다.
보안 관련 참고사항: 현재
refreshToken이 응답 바디에 포함되어 클라이언트 JavaScript에서 접근 가능합니다. XSS 공격 방어를 강화하려면, refresh token을HttpOnly쿠키로 전달하는 방식도 고려해 볼 수 있습니다. 현재 구현도 동작하지만, 보안 요구사항에 따라 검토해 주세요.CT-member/src/main/kotlin/com/chuseok22/ctmember/infrastructure/repository/MemberRepository.kt (1)
10-10: 변경사항이 완벽하게 적용되었습니다.
Member엔티티의 필드명 변경(studentName→studentNumber)에 맞춰 리포지토리 메서드가 올바르게 수정되었고, 관련된 모든 호출 코드(AuthService, CustomUserDetailsService)가 새로운 메서드명으로 업데이트되었습니다. Spring Data JPA 네이밍 규칙을 준수하고 있으며, 기존 메서드명에 대한 참조는 남아있지 않습니다.CT-auth/src/main/kotlin/com/chuseok22/ctauth/core/token/TokenManager.kt (1)
3-19: LGTM! 깔끔한 인터페이스 설계입니다.토큰 생성, 저장, 삭제에 대한 관심사가 잘 분리되어 있으며, TokenProvider/TokenStore와의 역할 분담이 명확합니다.
CT-web/src/main/resources/db/migration/V20260112_132017__create_member_table.sql (1)
1-26: LGTM! 마이그레이션 스크립트가 적절하게 작성되었습니다.
- 소프트 삭제 패턴에 맞춘 partial unique index 적용이 적절합니다.
- 조회 패턴에 맞는 복합 인덱스 구성도 좋습니다.
CT-member/src/main/kotlin/com/chuseok22/ctmember/infrastructure/entity/Member.kt (1)
16-17: LGTM! 필드명 변경이 일관되게 적용되었습니다.
studentName→studentNumber로의 리네이밍이 컬럼 매핑, 생성자, 팩토리 메서드, 정규화 함수에 모두 일관되게 반영되었습니다.Also applies to: 29-30, 36-37, 40-44
CT-auth/src/main/kotlin/com/chuseok22/ctauth/infrastructure/jwt/JwtManager.kt (1)
14-39: LGTM! TokenManager 인터페이스가 잘 구현되었습니다.TokenProvider와 TokenStore에 대한 위임이 깔끔하며, 관심사 분리가 잘 되어 있습니다.
CT-auth/src/main/kotlin/com/chuseok22/ctauth/application/service/AuthService.kt (1)
81-89: LGTM!외부 포털 서비스 호출에 대한 에러 핸들링이 적절하게 구현되어 있습니다. 예외를 잡아 로깅하고
CustomException으로 래핑하여 일관된 에러 응답을 반환합니다.
CT-web/src/main/kotlin/com/chuseok22/ctweb/application/controller/auth/AuthController.kt
Outdated
Show resolved
Hide resolved
CT-web/src/main/kotlin/com/chuseok22/ctweb/application/controller/auth/AuthController.kt
Outdated
Show resolved
Hide resolved
CT-web/src/main/kotlin/com/chuseok22/ctweb/application/controller/auth/AuthController.kt
Show resolved
Hide resolved
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/application/service/AuthService.kt:
- Around line 56-73: AuthService.reissue currently creates a new tokenPair but
never persists the new refresh token (same bug as in login); after calling
tokenManager.createTokenPair(memberId) you must save the new refresh token to
Redis by invoking the tokenManager method used elsewhere for storing refresh
tokens (e.g., tokenManager.saveRefreshToken(memberId, tokenPair.refreshToken) or
the equivalent persistence call) immediately after creation and before returning
ReissueResponse, ensuring the stored TTL matches how
removeRefreshTokenTtl/delete was used.
- Around line 29-54: The login method creates a token pair but never persists
the refresh token to Redis; after
tokenManager.createTokenPair(member.id.toString()) add a call to
tokenManager.saveRefreshTokenTtl(member.id.toString(), tokenPair.refreshToken)
(or the project's exact saveRefreshTokenTtl API) before returning so the refresh
token is stored and validateSavedToken() won't fail during reissue; update the
login function surrounding the tokenPair creation and before building
LoginResponse to persist the refresh token TTL.
🧹 Nitpick comments (3)
CT-auth/src/main/kotlin/com/chuseok22/ctauth/application/service/AuthService.kt (1)
83-91: 예외 처리 시 원본 예외 정보가 유실될 수 있습니다.현재 구현은 모든 예외를
CustomException으로 래핑하면서 로그에만 원본 예외를 기록합니다. 디버깅을 위해CustomException에 cause를 전달하는 것을 고려해 보세요.♻️ 선택적 개선안
} catch (e: Exception) { log.error(e) { "세종대학교 포털 로그인 중 오류 발생: ${e.message}" } - throw CustomException(ErrorCode.SEJONG_PORTAL_LOGIN_FAILED) + throw CustomException(ErrorCode.SEJONG_PORTAL_LOGIN_FAILED, e) }
CustomException이 cause 파라미터를 지원하는지 확인이 필요합니다.CT-auth/src/main/kotlin/com/chuseok22/ctauth/infrastructure/jwt/JwtManager.kt (2)
17-23:redisTemplate의존성이 불필요할 수 있습니다.
tokenStore가 Redis 접근을 추상화하고 있다면,redisTemplate을 직접 주입받을 필요가 없습니다.validateSavedToken()을tokenStore로 통일하면 의존성을 줄일 수 있습니다.
44-53: Redis 접근 방식이 일관되지 않습니다.
saveRefreshTokenTtl()과removeRefreshTokenTtl()은tokenStore추상화를 사용하지만,validateSavedToken()은redisTemplate을 직접 사용합니다. 아키텍처 일관성을 위해 모든 메서드에서 같은 접근 방식을 사용해야 합니다.♻️ 해결책: TokenStore에 get() 메서드 추가
TokenStore 인터페이스에
get()메서드를 추가한 후 JwtStore에서 구현하고,validateSavedToken()에서 이를 사용하세요:override fun validateSavedToken(token: String) { val memberId = tokenProvider.getMemberId(token) val key = getKey(memberId) - val savedToken = redisTemplate.opsForValue().get(key) + val savedToken = tokenStore.get(key) ?: throw CustomException(ErrorCode.INVALID_JWT) if (savedToken != token) { log.warn { "유효하지 않은 refreshToken 사용 시도: $memberId" } throw CustomException(ErrorCode.INVALID_JWT) } }
📜 Review details
Configuration used: Path: .coderabbit.yaml
Review profile: CHILL
Plan: Pro
📒 Files selected for processing (5)
CT-auth/src/main/kotlin/com/chuseok22/ctauth/application/service/AuthService.ktCT-auth/src/main/kotlin/com/chuseok22/ctauth/core/token/TokenManager.ktCT-auth/src/main/kotlin/com/chuseok22/ctauth/infrastructure/jwt/JwtManager.ktCT-auth/src/main/kotlin/com/chuseok22/ctauth/infrastructure/user/CustomUserDetails.ktCT-web/src/main/kotlin/com/chuseok22/ctweb/application/controller/auth/AuthController.kt
🚧 Files skipped from review as they are similar to previous changes (1)
- CT-auth/src/main/kotlin/com/chuseok22/ctauth/infrastructure/user/CustomUserDetails.kt
🔇 Additional comments (5)
CT-auth/src/main/kotlin/com/chuseok22/ctauth/core/token/TokenManager.kt (1)
3-24: LGTM! 토큰 관리 인터페이스가 잘 설계되었습니다.메서드 시그니처가 명확하고, KDoc 주석으로 각 메서드의 역할이 잘 문서화되어 있습니다. 구현체(
JwtManager)와의 계약이 명확합니다.CT-web/src/main/kotlin/com/chuseok22/ctweb/application/controller/auth/AuthController.kt (2)
43-50: LGTM! 로그아웃 엔드포인트가 적절하게 구현되었습니다.
@AuthenticationPrincipal을 통해 인증된 사용자 정보를 받아 처리하고, 204 No Content 응답을 반환하는 것이 RESTful 관례에 부합합니다.
27-41:/login및/reissue엔드포인트의 보안 설정이 올바르게 구성되어 있습니다.
SecurityUrls.AUTH_WHITELIST에"/api/auth/login"과"/api/auth/reissue"가 등록되어 있으며,SecurityConfig.kt의requestMatchers(...).permitAll()을 통해 인증 없이 접근 가능하도록 제대로 설정되어 있습니다.CT-auth/src/main/kotlin/com/chuseok22/ctauth/application/service/AuthService.kt (1)
75-81: LGTM! 로그아웃 로직이 적절합니다.Redis에서 refresh token을 삭제하여 토큰을 무효화하는 로직이 올바르게 구현되어 있습니다.
CT-auth/src/main/kotlin/com/chuseok22/ctauth/infrastructure/jwt/JwtManager.kt (1)
32-42: LGTM! TTL 저장 및 삭제 로직이 적절합니다.
tokenStore를 통한 추상화된 접근 방식을 사용하고 있으며, 로깅도 적절히 포함되어 있습니다.
CT-auth/src/main/kotlin/com/chuseok22/ctauth/application/service/AuthService.kt
Show resolved
Hide resolved
CT-auth/src/main/kotlin/com/chuseok22/ctauth/application/service/AuthService.kt
Outdated
Show resolved
Hide resolved
There was a problem hiding this comment.
Actionable comments posted: 1
🤖 Fix all issues with AI agents
In
@CT-auth/src/main/kotlin/com/chuseok22/ctauth/application/service/AuthService.kt:
- Line 38: The log statement in AuthService that currently does log.info { "신규
회원 로그인: 학번=$studentNumber, 이름=$name" } exposes personal data; change it to
either log.debug(...) or mask the values before logging (e.g., show only last
2-4 chars or replace with asterisks) so that full studentNumber and name are not
written to info-level logs, and apply the same change to the other occurrence on
the file (the similar log at lines ~47). Locate these statements in AuthService
and replace the info-level prints with masked values or a debug-level log to
comply with privacy policy.
📜 Review details
Configuration used: Path: .coderabbit.yaml
Review profile: CHILL
Plan: Pro
📒 Files selected for processing (7)
CT-auth/src/main/kotlin/com/chuseok22/ctauth/application/service/AuthService.ktCT-auth/src/main/kotlin/com/chuseok22/ctauth/core/token/TokenStore.ktCT-auth/src/main/kotlin/com/chuseok22/ctauth/infrastructure/jwt/JwtManager.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/user/CustomUserDetails.ktCT-web/src/main/kotlin/com/chuseok22/ctweb/application/controller/auth/AuthController.kt
🚧 Files skipped from review as they are similar to previous changes (2)
- CT-auth/src/main/kotlin/com/chuseok22/ctauth/infrastructure/jwt/JwtManager.kt
- CT-auth/src/main/kotlin/com/chuseok22/ctauth/infrastructure/user/CustomUserDetails.kt
🔇 Additional comments (9)
CT-auth/src/main/kotlin/com/chuseok22/ctauth/core/token/TokenStore.kt (1)
5-8: LGTM!
get메서드 추가가 적절합니다. Redis에 저장된 refresh token 조회를 위한 인터페이스 확장으로, TokenStore의 완전한 CRUD 지원을 가능하게 합니다.CT-auth/src/main/kotlin/com/chuseok22/ctauth/infrastructure/jwt/JwtProvider.kt (1)
27-27: LGTM!토큰 생성 로그를
info에서debug레벨로 변경한 것은 적절합니다. 토큰 생성은 빈번한 작업이므로 프로덕션 환경에서의 로그 노이즈를 줄이는 데 도움이 됩니다.Also applies to: 35-35
CT-auth/src/main/kotlin/com/chuseok22/ctauth/infrastructure/jwt/JwtStore.kt (1)
11-13: LGTM!
TokenStore.get인터페이스의 구현이 적절합니다. Redis에서 값을 조회하고 없으면null을 반환하는 간결한 구현입니다.CT-auth/src/main/kotlin/com/chuseok22/ctauth/application/service/AuthService.kt (4)
29-55: 로그인 플로우 구현이 적절합니다.세종대학교 포털 인증, 회원 조회/생성, 토큰 발급의 흐름이 잘 구성되어 있습니다.
@Transactional어노테이션을 통해 회원 생성의 트랜잭션 처리도 적절합니다.
73-78: LGTM!로그아웃 구현이 간결하고 적절합니다. refresh token TTL 제거를 통해 토큰을 무효화하는 방식이 올바릅니다.
80-88: LGTM!외부 서비스 호출을 도메인 예외로 감싸는 패턴이 적절합니다. 원본 예외를 로깅하여 디버깅에 도움이 됩니다.
57-71: 토큰 재발급 로직은 올바르게 구현되어 있습니다
validateSavedToken메서드가 Redis에 저장된 토큰과 요청된 토큰을 직접 비교합니다(JwtManager 라인 61:if (savedToken != token)).saveRefreshTokenTtl메서드는memberId를 기반으로 한 동일한 키를 사용하여 새 토큰으로 덮어쓰므로, 이전 토큰이 자동으로 무효화됩니다. 따라서 명시적 제거는 불필요하며, 현재 구현은 토큰 재발급 보안을 안전하게 보장합니다.CT-web/src/main/kotlin/com/chuseok22/ctweb/application/controller/auth/AuthController.kt (2)
20-25: 컨트롤러 구조가 적절하고 보안 설정이 올바르게 구성되어 있습니다.인증 관련 엔드포인트가 잘 구성되어 있으며,
@Validated어노테이션을 사용한 요청 검증과@LogMonitoring을 통한 일관된 로깅 적용이 좋습니다./api/auth/login과/api/auth/reissue엔드포인트가SecurityUrls의AUTH_WHITELIST에 포함되어 있어 인증 없이 접근 가능하도록 올바르게 설정되어 있습니다.
43-50: 인증 필수 엔드포인트로 이미 보호됨
/logout엔드포인트는 Spring Security 설정에서 AUTH_WHITELIST에 포함되지 않으므로.anyRequest().authenticated()에 의해 보호되고 있습니다. 인증되지 않은 사용자의 요청은 컨트롤러 메서드 도달 전 Spring Security 필터 단계에서 거부되므로,@AuthenticationPrincipal은 메서드 호출 시점에 항상 값을 가집니다. 따라서 null 처리에 대한 우려는 불필요합니다.Likely an incorrect or invalid review comment.
|
|
||
| val member = memberRepository.findByStudentNumberAndDeletedFalse(studentNumber) | ||
| ?: run { | ||
| log.info { "신규 회원 로그인: 학번=$studentNumber, 이름=$name" } |
There was a problem hiding this comment.
개인정보 로깅 검토 필요
학번(studentNumber)과 이름(name)은 개인정보에 해당할 수 있습니다. info 레벨 로깅은 프로덕션 환경에서도 기록되므로, 개인정보보호법 및 내부 정책에 따라 마스킹 처리하거나 debug 레벨로 변경하는 것을 고려하세요.
🔒 마스킹 처리 예시
- log.info { "신규 회원 로그인: 학번=$studentNumber, 이름=$name" }
+ log.info { "신규 회원 로그인: 학번=${studentNumber.take(4)}****, 이름=${name.first()}**" }- log.info { "로그인 성공: 학번=$studentNumber, 이름=$name" }
+ log.info { "로그인 성공: 학번=${studentNumber.take(4)}****, 이름=${name.first()}**" }Also applies to: 47-47
🤖 Prompt for AI Agents
In
@CT-auth/src/main/kotlin/com/chuseok22/ctauth/application/service/AuthService.kt
at line 38, The log statement in AuthService that currently does log.info { "신규
회원 로그인: 학번=$studentNumber, 이름=$name" } exposes personal data; change it to
either log.debug(...) or mask the values before logging (e.g., show only last
2-4 chars or replace with asterisks) so that full studentNumber and name are not
written to info-level logs, and apply the same change to the other occurrence on
the file (the similar log at lines ~47). Locate these statements in AuthService
and replace the info-level prints with masked values or a debug-level log to
comply with privacy policy.
✨ 변경 사항
✅ 테스트
Summary by CodeRabbit
릴리스 노트
새로운 기능
변경 사항
✏️ Tip: You can customize this high-level summary in your review settings.