Skip to content

feat: JWT 토큰 암호화 저장 및 검증 로그 추가 - #75

Merged
lluke0 merged 1 commit into
developfrom
feature/#72-save-jwt
Mar 4, 2026
Merged

feat: JWT 토큰 암호화 저장 및 검증 로그 추가#75
lluke0 merged 1 commit into
developfrom
feature/#72-save-jwt

Conversation

@lluke0

@lluke0 lluke0 commented Mar 4, 2026

Copy link
Copy Markdown
Member

KSafe 라이브러리를 활용한 암호화 토큰 저장소 구현 및
카카오 로그인 시 토큰 저장/조회 검증 로그 추가

📌 PR 요약

🌱 작업한 내용

🌱 PR 포인트

📸 스크린샷

스크린샷
파일첨부바람

📮 관련 이슈

RCA 룰을 사용하여 코드 리뷰를 해주세요

R (Request Changes) : 적극적으로 반영을 고려해주세요
C (Comment) : 웬만하면 반영해주세요
A (Approve) : 반영해도 좋고, 넘어가도 좋습니다. 사소한 의견입니다.

Summary by CodeRabbit

변경사항

  • 새로운 기능

    • 인증 토큰이 안전하게 로컬에 저장되어 사용자가 앱 재시작 후에도 로그인 상태를 유지할 수 있습니다.
  • 개선사항

    • 인증 토큰의 저장, 조회, 초기화 기능이 추가되었습니다.
    • 플랫폼별 보안 저장소 구현으로 인증 데이터 보호 수준이 향상되었습니다.

KSafe 라이브러리를 활용한 암호화 토큰 저장소 구현 및
카카오 로그인 시 토큰 저장/조회 검증 로그 추가

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
@lluke0
lluke0 requested review from BEEEAM-J and kimmandoo March 4, 2026 14:10
@lluke0 lluke0 self-assigned this Mar 4, 2026
@coderabbitai

coderabbitai Bot commented Mar 4, 2026

Copy link
Copy Markdown

Walkthrough

JWT 토큰의 암호화된 로컬 저장을 위해 KSafe 라이브러리를 추가하고, LocalAuthDataSource 인터페이스와 구현체를 도입하여 토큰 관리 기능을 확장했습니다. Kakao 로그인 후 토큰을 자동으로 저장하는 흐름이 추가되었습니다.

Changes

Cohort / File(s) Summary
의존성 구성
gradle/libs.versions.toml, composeApp/build.gradle.kts
KSafe 라이브러리 버전 1.6.0을 추가하고 commonMain 의존성에 포함했습니다.
로컬 인증 데이터소스
composeApp/src/commonMain/kotlin/com/chukchukhaksa/mobile/data/auth/datasource/LocalAuthDataSource.kt, composeApp/src/commonMain/kotlin/com/chukchukhaksa/mobile/local/datasource/auth/datasource/LocalAuthDataSourceImpl.kt, composeApp/src/commonMain/kotlin/com/chukchukhaksa/mobile/local/datasource/auth/di/LocalAuthDataSourceModule.kt
토큰 저장/조회/삭제 기능을 정의한 LocalAuthDataSource 인터페이스를 도입하고, KSafe를 이용한 암호화된 구현체 및 DI 모듈을 추가했습니다.
인증 저장소 확장
composeApp/src/commonMain/kotlin/com/chukchukhaksa/mobile/domain/auth/repository/AuthRepository.kt, composeApp/src/commonMain/kotlin/com/chukchukhaksa/mobile/data/auth/repository/AuthRepositoryImpl.kt, composeApp/src/commonMain/kotlin/com/chukchukhaksa/mobile/data/auth/di/AuthRepositoryModule.kt
AuthRepository 인터페이스에 토큰 저장/조회/삭제 메서드를 추가하고, AuthRepositoryImpl을 LocalAuthDataSource 의존성으로 업데이트하여 구현했습니다.
로그인 유스케이스 업데이트
composeApp/src/commonMain/kotlin/com/chukchukhaksa/mobile/domain/auth/usecase/KakaoLoginUseCase.kt
Kakao 로그인 후 반환받은 토큰을 authRepository.saveTokens()를 통해 로컬에 저장하는 흐름을 추가했습니다.
DI 모듈 등록
composeApp/src/commonMain/kotlin/com/chukchukhaksa/mobile/di/InitKoin.kt, composeApp/src/androidMain/kotlin/com/chukchukhaksa/mobile/di/AndroidModule.kt, composeApp/src/iosMain/kotlin/com/chukchukhaksa/mobile/di/IosModule.kt
localAuthDataSourceModule을 InitKoin에 포함시키고, 각 플랫폼(Android, iOS) 모듈에 KSafe 싱글톤을 등록했습니다.
기타 수정
composeApp/src/androidMain/kotlin/.../presentation/timetable/timetable/component/WebViewGuideScreen.kt
LaunchedEffect 임포트를 추가했습니다.

Estimated code review effort

🎯 3 (Moderate) | ⏱️ ~25 minutes

Possibly related PRs

  • Feature/#70 kakao login server #71: 이전 인증/로그인 작업 확장으로, AuthRepositoryImpl, KakaoLoginUseCase, 인증 DI 모듈, InitKoin 등 동일한 클래스들을 수정하여 LocalAuthDataSource와 토큰 영속성 기능을 추가했습니다.

Suggested reviewers

  • kimmandoo
  • BEEEAM-J

Poem

🐰 KSafe로 토큰을 품고,
암호화된 저장소에 안전히 누워,
Kakao 로그인 후 자동 저장,
로컬 데이터가 쏙 들어가고,
JWT는 더 이상 잃어버리지 않아! 🔐✨

🚥 Pre-merge checks | ✅ 3 | ❌ 2

❌ Failed checks (1 warning, 1 inconclusive)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 0.00% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
Description check ❓ Inconclusive PR 설명이 템플릿 구조를 따르고 있으나, '작업한 내용'과 'PR 포인트' 섹션이 빈 상태이며 스크린샷도 미제공 상태입니다. PR 요약의 '작업한 내용'과 'PR 포인트' 섹션을 구체적으로 작성하고, 필요시 스크린샷을 추가하여 변경사항을 더욱 명확히 설명해주세요.
✅ Passed checks (3 passed)
Check name Status Explanation
Title check ✅ Passed PR 제목이 JWT 토큰 암호화 저장 및 검증 로그 추가라는 주요 변경사항을 명확하게 요약하고 있으며, 변경사항의 핵심을 잘 반영하고 있습니다.
Linked Issues check ✅ Passed PR 변경사항이 이슈 #72의 JWT 저장 기능 요구사항을 충족하고 있으며, KSafe 라이브러리를 통한 암호화 토큰 저장소 구현과 토큰 관리 기능을 완전히 제공하고 있습니다.
Out of Scope Changes check ✅ Passed 모든 변경사항이 JWT 토큰 암호화 저장 기능 구현과 관련되어 있으며, WebViewGuideScreen.kt의 LaunchedEffect import 추가는 유지보수 관련 미미한 변경입니다.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing Touches
  • 📝 Generate docstrings (stacked PR)
  • 📝 Generate docstrings (commit on current branch)
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Post copyable unit tests in a comment
  • Commit unit tests in branch feature/#72-save-jwt

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.

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Actionable comments posted: 2

🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In
`@composeApp/src/commonMain/kotlin/com/chukchukhaksa/mobile/data/auth/repository/AuthRepositoryImpl.kt`:
- Around line 17-20: The saveTokens function can leave the repository in an
inconsistent state if saving the refresh token throws after the access token was
already saved; update AuthRepositoryImpl.saveTokens to perform the two saves
atomically by wrapping localAuthDataSource.saveAccessToken and saveRefreshToken
in a try/catch and, on any exception, roll back to a consistent state (e.g.,
call localAuthDataSource.clearTokens or revert the access token) and rethrow or
propagate the error; alternatively, use a transaction/atomic API if
localAuthDataSource supports it—refer to saveTokens,
localAuthDataSource.saveAccessToken, localAuthDataSource.saveRefreshToken, and
localAuthDataSource.clearTokens when making the change.

In
`@composeApp/src/commonMain/kotlin/com/chukchukhaksa/mobile/local/datasource/auth/datasource/LocalAuthDataSourceImpl.kt`:
- Around line 10-20: saveAccessToken and saveRefreshToken currently allow blank
strings which can later be read as null by getAccessToken and obscure root
causes; add validation to reject blank tokens before persisting. In the
implementations of saveAccessToken(token: String) and saveRefreshToken(token:
String) perform a check like token.isNotBlank() and throw an
IllegalArgumentException (or return a failure) when blank, otherwise call
ksafe.put(KEY_ACCESS_TOKEN, token, encrypted = true) /
ksafe.put(KEY_REFRESH_TOKEN, token, encrypted = true); keep getAccessToken()
behavior unchanged but mention KEY_ACCESS_TOKEN and KEY_REFRESH_TOKEN to locate
the usages.

ℹ️ Review info
⚙️ Run configuration

Configuration used: Repository UI

Review profile: ASSERTIVE

Plan: Pro

Run ID: 74d1ee23-e093-44d3-8338-bbd7ac39e19b

📥 Commits

Reviewing files that changed from the base of the PR and between b12a045 and c702283.

📒 Files selected for processing (13)
  • composeApp/build.gradle.kts
  • composeApp/src/androidMain/kotlin/com/chukchukhaksa/mobile/di/AndroidModule.kt
  • composeApp/src/commonMain/kotlin/com/chukchukhaksa/mobile/data/auth/datasource/LocalAuthDataSource.kt
  • composeApp/src/commonMain/kotlin/com/chukchukhaksa/mobile/data/auth/di/AuthRepositoryModule.kt
  • composeApp/src/commonMain/kotlin/com/chukchukhaksa/mobile/data/auth/repository/AuthRepositoryImpl.kt
  • composeApp/src/commonMain/kotlin/com/chukchukhaksa/mobile/di/InitKoin.kt
  • composeApp/src/commonMain/kotlin/com/chukchukhaksa/mobile/domain/auth/repository/AuthRepository.kt
  • composeApp/src/commonMain/kotlin/com/chukchukhaksa/mobile/domain/auth/usecase/KakaoLoginUseCase.kt
  • composeApp/src/commonMain/kotlin/com/chukchukhaksa/mobile/local/datasource/auth/datasource/LocalAuthDataSourceImpl.kt
  • composeApp/src/commonMain/kotlin/com/chukchukhaksa/mobile/local/datasource/auth/di/LocalAuthDataSourceModule.kt
  • composeApp/src/commonMain/kotlin/com/chukchukhaksa/mobile/presentation/timetable/timetable/component/WebViewGuideScreen.kt
  • composeApp/src/iosMain/kotlin/com/chukchukhaksa/mobile/di/IosModule.kt
  • gradle/libs.versions.toml

Comment on lines +17 to +20
override suspend fun saveTokens(accessToken: String, refreshToken: String) {
localAuthDataSource.saveAccessToken(accessToken)
localAuthDataSource.saveRefreshToken(refreshToken)
}

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟠 Major

토큰 저장이 원자적이지 않아 부분 저장 상태가 생길 수 있습니다.

Line 18 저장 후 Line 19에서 예외가 나면 access token만 갱신되고 refresh token은 이전 상태로 남을 수 있습니다. 저장 실패 시 롤백(예: clearTokens)을 보장해 일관성을 맞춰주세요.

수정 예시
 override suspend fun saveTokens(accessToken: String, refreshToken: String) {
-    localAuthDataSource.saveAccessToken(accessToken)
-    localAuthDataSource.saveRefreshToken(refreshToken)
+    runCatching {
+        localAuthDataSource.saveAccessToken(accessToken)
+        localAuthDataSource.saveRefreshToken(refreshToken)
+    }.onFailure { error ->
+        localAuthDataSource.clearTokens()
+        throw error
+    }
 }
📝 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.

Suggested change
override suspend fun saveTokens(accessToken: String, refreshToken: String) {
localAuthDataSource.saveAccessToken(accessToken)
localAuthDataSource.saveRefreshToken(refreshToken)
}
override suspend fun saveTokens(accessToken: String, refreshToken: String) {
runCatching {
localAuthDataSource.saveAccessToken(accessToken)
localAuthDataSource.saveRefreshToken(refreshToken)
}.onFailure { error ->
localAuthDataSource.clearTokens()
throw error
}
}
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In
`@composeApp/src/commonMain/kotlin/com/chukchukhaksa/mobile/data/auth/repository/AuthRepositoryImpl.kt`
around lines 17 - 20, The saveTokens function can leave the repository in an
inconsistent state if saving the refresh token throws after the access token was
already saved; update AuthRepositoryImpl.saveTokens to perform the two saves
atomically by wrapping localAuthDataSource.saveAccessToken and saveRefreshToken
in a try/catch and, on any exception, roll back to a consistent state (e.g.,
call localAuthDataSource.clearTokens or revert the access token) and rethrow or
propagate the error; alternatively, use a transaction/atomic API if
localAuthDataSource supports it—refer to saveTokens,
localAuthDataSource.saveAccessToken, localAuthDataSource.saveRefreshToken, and
localAuthDataSource.clearTokens when making the change.

Comment on lines +10 to +20
override suspend fun saveAccessToken(token: String) {
ksafe.put(KEY_ACCESS_TOKEN, token, encrypted = true)
}

override suspend fun getAccessToken(): String? {
return ksafe.get(KEY_ACCESS_TOKEN, "", encrypted = true).ifEmpty { null }
}

override suspend fun saveRefreshToken(token: String) {
ksafe.put(KEY_REFRESH_TOKEN, token, encrypted = true)
}

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟡 Minor

빈 토큰 저장을 허용하면 인증 상태가 즉시 깨질 수 있습니다.

saveAccessToken/saveRefreshToken에서 빈 문자열을 허용하면, 조회 시 null로 바뀌어 런타임에서 원인 추적이 어려워집니다. 저장 전에 isNotBlank() 검증으로 빠르게 실패시키는 편이 안전합니다.

수정 예시
 override suspend fun saveAccessToken(token: String) {
+    require(token.isNotBlank()) { "access token must not be blank" }
     ksafe.put(KEY_ACCESS_TOKEN, token, encrypted = true)
 }

 override suspend fun saveRefreshToken(token: String) {
+    require(token.isNotBlank()) { "refresh token must not be blank" }
     ksafe.put(KEY_REFRESH_TOKEN, token, encrypted = true)
 }
📝 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.

Suggested change
override suspend fun saveAccessToken(token: String) {
ksafe.put(KEY_ACCESS_TOKEN, token, encrypted = true)
}
override suspend fun getAccessToken(): String? {
return ksafe.get(KEY_ACCESS_TOKEN, "", encrypted = true).ifEmpty { null }
}
override suspend fun saveRefreshToken(token: String) {
ksafe.put(KEY_REFRESH_TOKEN, token, encrypted = true)
}
override suspend fun saveAccessToken(token: String) {
require(token.isNotBlank()) { "access token must not be blank" }
ksafe.put(KEY_ACCESS_TOKEN, token, encrypted = true)
}
override suspend fun getAccessToken(): String? {
return ksafe.get(KEY_ACCESS_TOKEN, "", encrypted = true).ifEmpty { null }
}
override suspend fun saveRefreshToken(token: String) {
require(token.isNotBlank()) { "refresh token must not be blank" }
ksafe.put(KEY_REFRESH_TOKEN, token, encrypted = true)
}
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In
`@composeApp/src/commonMain/kotlin/com/chukchukhaksa/mobile/local/datasource/auth/datasource/LocalAuthDataSourceImpl.kt`
around lines 10 - 20, saveAccessToken and saveRefreshToken currently allow blank
strings which can later be read as null by getAccessToken and obscure root
causes; add validation to reject blank tokens before persisting. In the
implementations of saveAccessToken(token: String) and saveRefreshToken(token:
String) perform a check like token.isNotBlank() and throw an
IllegalArgumentException (or return a failure) when blank, otherwise call
ksafe.put(KEY_ACCESS_TOKEN, token, encrypted = true) /
ksafe.put(KEY_REFRESH_TOKEN, token, encrypted = true); keep getAccessToken()
behavior unchanged but mention KEY_ACCESS_TOKEN and KEY_REFRESH_TOKEN to locate
the usages.

@lluke0
lluke0 merged commit e78d846 into develop Mar 4, 2026
1 of 2 checks passed
@lluke0
lluke0 deleted the feature/#72-save-jwt branch March 4, 2026 14:21
@coderabbitai coderabbitai Bot mentioned this pull request Mar 14, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

[Feat] jwt 저장

2 participants