Skip to content

Commit 18354ac

Browse files
authored
Merge pull request #260 from YAPP-Github/BOOK-497-refactor/#259
refactor: TokenAuthenticator 동시성 처리 개선 및 Provider -> Lazy 적용
2 parents f8057df + 8d3c960 commit 18354ac

3 files changed

Lines changed: 88 additions & 25 deletions

File tree

.claude/CLAUDE.md

Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,24 @@
1+
# Reed 프로젝트 작업 지침
2+
3+
## 빌드 관련
4+
5+
- **빌드는 사용자가 직접 수행합니다**
6+
- 기능 작업 완료 후 빌드를 자동으로 실행하지 마세요 (시간이 오래 걸림)
7+
- 빌드가 필요한 경우 사용자에게 알리고 사용자가 직접 실행하도록 합니다
8+
9+
## 커밋 관련
10+
11+
- **커밋 메시지에서 Claude 관련 문구를 제거합니다**
12+
- 다음 문구들을 커밋 메시지에 포함하지 마세요:
13+
- `🤖 Generated with [Claude Code](https://claude.com/claude-code)`
14+
- `Co-Authored-By: Claude Sonnet 4.5 <noreply@anthropic.com>`
15+
- 커밋 작업은 사용자가 직접 수행하는 경우가 많으므로, 요청받지 않은 경우 커밋하지 마세요
16+
17+
## MCP 설정 관련
18+
19+
- **Claude Code CLI의 MCP 설정 파일 위치**
20+
- Claude Desktop이 아니라 **Claude Code CLI**를 사용 중입니다
21+
- MCP 설정은 `~/.claude.json``projects` 섹션에서 프로젝트별로 관리됩니다
22+
- Claude Desktop 설정 파일(`~/Library/Application Support/Claude/claude_desktop_config.json`)을 수정하지 마세요
23+
- **Figma MCP 설정 경로**
24+
- `~/.claude.json``projects``/Users/medi/AndroidStudioProjects/YeoBee-Android``mcpServers``figma`

.claude/CODING.md

Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,25 @@
1+
# 코딩 가이드
2+
3+
## 코드 작성 원칙
4+
5+
- 한글 주석 사용
6+
- Kotlin 코딩 컨벤션 준수
7+
- 기존 코드 스타일 유지
8+
- 파일 끝에 빈 줄(newline) 추가
9+
10+
## Compose 관련
11+
12+
- **Composable 함수 내 Collection 타입**
13+
- `List`, `Set`, `Map` 등의 Collection 대신 `ImmutableList`, `ImmutableSet`, `ImmutableMap` 사용
14+
- `kotlinx.collections.immutable` 라이브러리 사용
15+
- 예시:
16+
```kotlin
17+
// ❌ 사용하지 않음
18+
@Composable
19+
fun TripList(trips: List<Trip>) { ... }
20+
21+
// ✅ 사용
22+
@Composable
23+
fun TripList(trips: ImmutableList<Trip>) { ... }
24+
```
25+
- 변환 시 `toImmutableList()`, `persistentListOf()` 등 사용

core/network/src/main/kotlin/com/ninecraft/booket/core/network/TokenAuthenticator.kt

Lines changed: 39 additions & 25 deletions
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,6 @@ import com.ninecraft.booket.core.network.request.RefreshTokenRequest
66
import com.ninecraft.booket.core.network.service.ReedService
77
import com.orhanobut.logger.Logger
88
import dev.zacsweers.metro.Inject
9-
import dev.zacsweers.metro.Provider
109
import dev.zacsweers.metro.SingleIn
1110
import kotlinx.coroutines.runBlocking
1211
import okhttp3.Authenticator
@@ -18,38 +17,53 @@ import okhttp3.Route
1817
@Inject
1918
class TokenAuthenticator(
2019
private val tokenDataSource: TokenDataSource,
21-
private val serviceProvider: Provider<ReedService>,
20+
private val reedService: Lazy<ReedService>,
2221
) : Authenticator {
22+
private val lock = Any()
23+
2324
override fun authenticate(route: Route?, response: Response): Request? {
24-
return runBlocking {
25-
try {
26-
val refreshToken = tokenDataSource.getRefreshToken()
25+
// 동시 401 응답 시 중복 refresh 방지 (refresh token rotation 대응)
26+
synchronized(lock) {
27+
val failedToken = response.request.header("Authorization")
28+
?.removePrefix("Bearer ")
29+
.orEmpty()
2730

28-
if (refreshToken.isBlank()) {
29-
Logger.d("TokenAuthenticator", "No refresh token available")
30-
tokenDataSource.clearTokens()
31-
return@runBlocking null
32-
}
31+
val currentToken = runBlocking { tokenDataSource.getAccessToken() }
3332

34-
val refreshTokenRequest = RefreshTokenRequest(refreshToken)
35-
val refreshResponse = serviceProvider().refreshToken(refreshTokenRequest)
33+
// 다른 요청이 이미 토큰을 갱신한 경우, 새 토큰으로 재시도만 수행
34+
if (failedToken != currentToken) {
35+
return response.request.newBuilder()
36+
.header("Authorization", "Bearer $currentToken")
37+
.build()
38+
}
3639

37-
tokenDataSource.apply {
38-
setAccessToken(refreshResponse.accessToken)
39-
setRefreshToken(refreshResponse.refreshToken)
40-
}
40+
return runBlocking {
41+
try {
42+
val refreshToken = tokenDataSource.getRefreshToken()
4143

42-
Logger.d("TokenAuthenticator", "Token refreshed successfully")
44+
if (refreshToken.isBlank()) {
45+
Logger.d("No refresh token available")
46+
tokenDataSource.clearTokens()
47+
return@runBlocking null
48+
}
4349

44-
response.request.newBuilder()
45-
.header("Authorization", "Bearer ${refreshResponse.accessToken}")
46-
.build()
47-
} catch (e: Exception) {
48-
Logger.e("TokenAuthenticator", e.message)
49-
tokenDataSource.clearTokens()
50+
val refreshResponse = reedService.value.refreshToken(RefreshTokenRequest(refreshToken))
51+
52+
tokenDataSource.apply {
53+
setAccessToken(refreshResponse.accessToken)
54+
setRefreshToken(refreshResponse.refreshToken)
55+
}
56+
57+
Logger.d("Token refreshed successfully")
5058

51-
// refresh token이 만료되었거나 잘못된 경우, 재시도하지 않음
52-
return@runBlocking null
59+
response.request.newBuilder()
60+
.header("Authorization", "Bearer ${refreshResponse.accessToken}")
61+
.build()
62+
} catch (e: Exception) {
63+
Logger.e(e, "Token refresh failed")
64+
tokenDataSource.clearTokens()
65+
return@runBlocking null
66+
}
5367
}
5468
}
5569
}

0 commit comments

Comments
 (0)