@@ -6,7 +6,6 @@ import com.ninecraft.booket.core.network.request.RefreshTokenRequest
66import com.ninecraft.booket.core.network.service.ReedService
77import com.orhanobut.logger.Logger
88import dev.zacsweers.metro.Inject
9- import dev.zacsweers.metro.Provider
109import dev.zacsweers.metro.SingleIn
1110import kotlinx.coroutines.runBlocking
1211import okhttp3.Authenticator
@@ -18,38 +17,53 @@ import okhttp3.Route
1817@Inject
1918class 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