Skip to content

20260112 #5 로그인 및 기타 인증 관련 로직 개발#7

Merged
Chuseok22 merged 13 commits intomainfrom
20260112_#5_로그인_및_기타_인증_관련_로직_개발
Jan 12, 2026

Hidden character warning

The head ref may contain hidden characters: "20260112_#5_\ub85c\uadf8\uc778_\ubc0f_\uae30\ud0c0_\uc778\uc99d_\uad00\ub828_\ub85c\uc9c1_\uac1c\ubc1c"
Merged

20260112 #5 로그인 및 기타 인증 관련 로직 개발#7
Chuseok22 merged 13 commits intomainfrom
20260112_#5_로그인_및_기타_인증_관련_로직_개발

Conversation

@Chuseok22
Copy link
Member

@Chuseok22 Chuseok22 commented Jan 12, 2026

✨ 변경 사항


✅ 테스트


  • 수동 테스트 완료
  • 테스트 코드 완료

Summary by CodeRabbit

릴리스 노트

  • 새로운 기능

    • 인증 API 추가: 로그인(학번·비밀번호), 토큰 재발급, 로그아웃 — 로그인 응답에 학생번호, 이름, 액세스/리프레시 토큰 포함
    • 요청/응답 DTO 및 토큰 페어 타입 추가
  • 변경 사항

    • 헬스 체크 엔드포인트 공개 액세스 추가
    • 사용자 식별을 학번 기반으로 전환
    • 리프레시 토큰 저장·검증·삭제 흐름 및 JWT 기반 토큰 관리 강화

✏️ Tip: You can customize this high-level summary in your review settings.

@Chuseok22 Chuseok22 self-assigned this Jan 12, 2026
@Chuseok22 Chuseok22 added the feat 새로운 기능 추가 (Feature) label Jan 12, 2026
@coderabbitai
Copy link

coderabbitai bot commented Jan 12, 2026

Note

.coderabbit.yaml has unrecognized properties

CodeRabbit is using all valid settings from your configuration. Unrecognized properties (listed below) have been ignored and may indicate typos or deprecated fields that can be removed.

⚠️ Parsing warnings (1)
Validation error: Unrecognized key(s) in object: 'ignored_branch'
⚙️ Configuration instructions
  • Please see the configuration documentation for more information.
  • You can also validate your configuration using the online YAML validator.
  • If your editor has YAML language server enabled, you can add the path at the top of this file to enable auto-completion and validation: # yaml-language-server: $schema=https://coderabbit.ai/integrations/schema.v2.json

Walkthrough

로그인·재발급·로그아웃 흐름을 처리하는 인증 계층(컨트롤러·서비스·DTO)과 JWT/Redis 기반 토큰 인프라(TokenManager/JwtManager/TokenStore 등)를 추가하고, 회원 식별 필드를 studentNamestudentNumber로 리네임했습니다.

Changes

코호트 / 파일(들) 변경 요약
인증 요청 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? 인터페이스에 추가 및 JwtStoreget 구현 추가(Redis 조회)
웹 컨트롤러
\CT-web/src/main/kotlin/com/chuseok22/ctweb/application/controller/auth/AuthController.kt``
/api/authPOST /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`**`
엔티티 필드·컬럼 및 팩토리 메서드 studentNamestudentNumber로 리네임, 리포지토리 쿼리명도 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_namestudent_number로 변경 (컬럼 정의·유니크/인덱스명 수정)
로깅 레벨 조정
\CT-auth/src/main/kotlin/com/chuseok22/ctauth/infrastructure/jwt/JwtProvider.kt`**`
토큰 생성 성공 로그 레벨을 infodebug로 변경

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
Loading
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
Loading

Estimated code review effort

🎯 3 (Moderate) | ⏱️ ~25 minutes

Possibly related PRs

Poem

🐰 포털에 달려가 아이디 톡, 비번 톡,
토큰 두 개 뿅! 새로 발급됐네 🎉
학번은 숫자에 깔끔히 정착하고,
Redis에 숨결 담아 갱신은 또 출발,
토끼가 춤추며 말하네: 인증, 완성했지요. 🥕

🚥 Pre-merge checks | ✅ 2 | ❌ 1
❌ Failed checks (1 warning)
Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning 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.

❤️ Share

Comment @coderabbitai help to get the list of available commands and usage tips.

Copy link

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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

📥 Commits

Reviewing files that changed from the base of the PR and between e4f7961 and e21b082.

📒 Files selected for processing (16)
  • 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
  • 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
  • CT-auth/src/main/kotlin/com/chuseok22/ctauth/application/service/AuthService.kt
  • 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/constant/SecurityUrls.kt
  • CT-auth/src/main/kotlin/com/chuseok22/ctauth/infrastructure/jwt/JwtManager.kt
  • 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-common/src/main/kotlin/com/chuseok22/ctcommon/application/exception/ErrorCode.kt
  • CT-member/src/main/kotlin/com/chuseok22/ctmember/infrastructure/entity/Member.kt
  • CT-member/src/main/kotlin/com/chuseok22/ctmember/infrastructure/repository/MemberRepository.kt
  • CT-web/src/main/kotlin/com/chuseok22/ctweb/application/controller/auth/AuthController.kt
  • CT-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 엔티티에서 studentNumberlateinit 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 엔티티의 필드명 변경(studentNamestudentNumber)에 맞춰 리포지토리 메서드가 올바르게 수정되었고, 관련된 모든 호출 코드(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! 필드명 변경이 일관되게 적용되었습니다.

studentNamestudentNumber로의 리네이밍이 컬럼 매핑, 생성자, 팩토리 메서드, 정규화 함수에 모두 일관되게 반영되었습니다.

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으로 래핑하여 일관된 에러 응답을 반환합니다.

Copy link

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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

📥 Commits

Reviewing files that changed from the base of the PR and between e21b082 and 7af97fa.

📒 Files selected for processing (5)
  • CT-auth/src/main/kotlin/com/chuseok22/ctauth/application/service/AuthService.kt
  • CT-auth/src/main/kotlin/com/chuseok22/ctauth/core/token/TokenManager.kt
  • CT-auth/src/main/kotlin/com/chuseok22/ctauth/infrastructure/jwt/JwtManager.kt
  • CT-auth/src/main/kotlin/com/chuseok22/ctauth/infrastructure/user/CustomUserDetails.kt
  • CT-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.ktrequestMatchers(...).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를 통한 추상화된 접근 방식을 사용하고 있으며, 로깅도 적절히 포함되어 있습니다.

Copy link

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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

📥 Commits

Reviewing files that changed from the base of the PR and between 7af97fa and b214dd0.

📒 Files selected for processing (7)
  • CT-auth/src/main/kotlin/com/chuseok22/ctauth/application/service/AuthService.kt
  • CT-auth/src/main/kotlin/com/chuseok22/ctauth/core/token/TokenStore.kt
  • CT-auth/src/main/kotlin/com/chuseok22/ctauth/infrastructure/jwt/JwtManager.kt
  • CT-auth/src/main/kotlin/com/chuseok22/ctauth/infrastructure/jwt/JwtProvider.kt
  • CT-auth/src/main/kotlin/com/chuseok22/ctauth/infrastructure/jwt/JwtStore.kt
  • CT-auth/src/main/kotlin/com/chuseok22/ctauth/infrastructure/user/CustomUserDetails.kt
  • CT-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 엔드포인트가 SecurityUrlsAUTH_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" }
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟡 Minor

개인정보 로깅 검토 필요

학번(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.

@Chuseok22 Chuseok22 merged commit b1417ec into main Jan 12, 2026
1 check passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

feat 새로운 기능 추가 (Feature)

Projects

None yet

Development

Successfully merging this pull request may close these issues.

⚙️ [기능추가][auth] 로그인 및 기타 인증 관련 로직 개발

1 participant