11package com.threegap.bitnagil.network.auth
22
3+ import com.threegap.bitnagil.network.token.ReissueService
4+ import com.threegap.bitnagil.network.token.TokenProvider
35import kotlinx.coroutines.runBlocking
6+ import kotlinx.coroutines.sync.Mutex
7+ import kotlinx.coroutines.sync.withLock
48import okhttp3.Authenticator
59import okhttp3.Request
610import okhttp3.Response
711import okhttp3.Route
812
9- class TokenAuthenticator : Authenticator {
13+ class TokenAuthenticator (
14+ private val tokenProvider : TokenProvider ,
15+ private val reissueService : ReissueService ,
16+ private val onTokenExpired : (() -> Unit )? = null ,
17+ ) : Authenticator {
18+
19+ private val authMutex = Mutex ()
1020
1121 override fun authenticate (route : Route ? , response : Response ): Request ? {
12- if (response.code != UNAUTHORIZED ) return null
13- if (responseCount(response) >= MAX_RETRY ) return null
22+ if (! shouldRetry(response)) return null
1423
15- // 재발급 api 연결 시 수정 예정입니다.(현재 코드는 임시)
16- val newAccessToken = runBlocking {}
24+ val currentToken = runBlocking { tokenProvider.getAccessToken() }
25+ val authHeader = response.request.header(AUTHORIZATION )
26+ val requestToken = authHeader?.takeIf { it.startsWith(" $TOKEN_PREFIX " ) }
27+ ?.substring(" $TOKEN_PREFIX " .length)
1728
18- return response.request.newBuilder()
19- .header(AUTHORIZATION , " $BEARER $newAccessToken " )
20- .build()
29+ if (! currentToken.isNullOrBlank() && ! requestToken.isNullOrBlank() && currentToken != requestToken) {
30+ return buildRequestWithToken(response.request, currentToken)
31+ }
32+
33+ return runBlocking {
34+ authMutex.withLock { refreshAndRetry(response) }
35+ }
36+ }
37+
38+ private suspend fun refreshAndRetry (response : Response ): Request ? {
39+ val refreshToken = tokenProvider.getRefreshToken()
40+
41+ if (refreshToken.isNullOrBlank()) {
42+ handleTokenExpiration()
43+ return null
44+ }
45+
46+ return runCatching {
47+ reissueService.reissueToken(refreshToken)
48+ }.fold(
49+ onSuccess = { baseResponse ->
50+ if (baseResponse.data != null && baseResponse.code == SUCCESS_CODE ) {
51+ tokenProvider.saveTokens(
52+ accessToken = baseResponse.data.accessToken,
53+ refreshToken = baseResponse.data.refreshToken,
54+ )
55+ buildRequestWithToken(response.request, baseResponse.data.accessToken)
56+ } else {
57+ handleTokenExpiration()
58+ null
59+ }
60+ },
61+ onFailure = {
62+ handleTokenExpiration()
63+ null
64+ },
65+ )
2166 }
2267
2368 private fun responseCount (response : Response ): Int {
@@ -30,10 +75,26 @@ class TokenAuthenticator : Authenticator {
3075 return count
3176 }
3277
78+ private fun shouldRetry (response : Response ): Boolean {
79+ return response.code == UNAUTHORIZED && responseCount(response) < MAX_RETRY
80+ }
81+
82+ private fun buildRequestWithToken (originalRequest : Request , token : String ): Request {
83+ return originalRequest.newBuilder()
84+ .header(AUTHORIZATION , " $TOKEN_PREFIX $token " )
85+ .build()
86+ }
87+
88+ private suspend fun handleTokenExpiration () {
89+ tokenProvider.clearTokens()
90+ onTokenExpired?.invoke()
91+ }
92+
3393 companion object {
3494 private const val UNAUTHORIZED = 401
3595 private const val MAX_RETRY = 2
3696 private const val AUTHORIZATION = " Authorization"
37- private const val BEARER = " Bearer"
97+ private const val TOKEN_PREFIX = " Bearer"
98+ private const val SUCCESS_CODE = " CO000"
3899 }
39100}
0 commit comments