This commit is contained in:
Eric85710 2026-07-01 17:24:06 +08:00
parent c3c861ac62
commit 033bea2a4e

View file

@ -6,69 +6,88 @@ import com.example.login_v3.data.api.TecnologiaApi
import kotlinx.coroutines.flow.first import kotlinx.coroutines.flow.first
import kotlinx.coroutines.runBlocking import kotlinx.coroutines.runBlocking
import okhttp3.Authenticator import okhttp3.Authenticator
import kotlinx.coroutines.sync.Mutex
import kotlinx.coroutines.sync.withLock
import okhttp3.Request import okhttp3.Request
import okhttp3.Route
import okhttp3.Response import okhttp3.Response
import okhttp3.Route
import javax.inject.Inject import javax.inject.Inject
class TokenAuthenticator @Inject constructor( class TokenAuthenticator @Inject constructor(
private val tokenManager: TokenManager, private val tokenManager: TokenManager,
// 💡 關鍵:使用 dagger.Lazy 避免 Hilt 的循環依賴 (Circular Dependency)
private val apiService: dagger.Lazy<TecnologiaApi> private val apiService: dagger.Lazy<TecnologiaApi>
) : Authenticator { ) : Authenticator {
companion object {
// 🌟 關鍵:全域共用同一把鎖,防止多個 API 同時過期時重複打刷新介面
private val refreshMutex = Mutex()
}
override fun authenticate(route: Route?, response: Response): Request? { override fun authenticate(route: Route?, response: Response): Request? {
// 1. 取得 DataStore 中的當前帳號資訊
Log.d("AuthDebug", "--- 進入 Authenticator (觸發網址: ${response.request.url}) ---") Log.d("AuthDebug", "--- 進入 Authenticator (觸發網址: ${response.request.url}) ---")
val refreshToken = runBlocking { tokenManager.currentRefreshToken.first() }
val currentUserId = runBlocking { tokenManager.currentUserId.first() } // 使用 runBlocking 配合 Mutex,讓同時間進來的 401 請求排隊
return runBlocking {
refreshMutex.withLock {
// 1. 檢查目前 DataStore 裡的 Access Token
val currentTokenInStore = tokenManager.currentAccessToken.first()
val oldRequestToken = response.request.header("Authorization")?.removePrefix("Bearer ")
// 🌟 防禦機制:如果 DataStore 裡的 Token 已經跟當初發起請求時的舊 Token 不同了
// 代表排在你前面的請求已經幫忙刷新成功了!這裡直接拿新 Token 重試即可,不用再打一次 API。
if (currentTokenInStore != oldRequestToken && !currentTokenInStore.isNullOrEmpty()) {
Log.d("AuthDebug", "分流防禦:偵測到其他請求已刷新過 Token,直接重複利用新 Token")
return@runBlocking response.request.newBuilder()
.header("Authorization", "Bearer $currentTokenInStore")
.build()
}
// 2. 真正準備去打後端刷新
val refreshToken = tokenManager.currentRefreshToken.first()
val currentUserId = tokenManager.currentUserId.first()
Log.d("AuthDebug", "當前用戶: $currentUserId, Token是否存在: ${!refreshToken.isNullOrEmpty()}") Log.d("AuthDebug", "當前用戶: $currentUserId, Token是否存在: ${!refreshToken.isNullOrEmpty()}")
if (refreshToken.isNullOrEmpty() || currentUserId.isNullOrEmpty()) { if (refreshToken.isNullOrEmpty() || currentUserId.isNullOrEmpty()) {
return null // 沒有憑證資訊,不重試,直接拋出原 401 錯誤 return@runBlocking null
} }
// 2. 同步發送網路請求,拿 refresh_token 去換新 Token // 3. 同步發送網路請求
val refreshResponse = runBlocking { val refreshResponse = try {
try {
// 用 apiService.get() 來取得真正需要用的實例
apiService.get().refreshToken(RefreshRequest(refreshToken)) apiService.get().refreshToken(RefreshRequest(refreshToken))
} catch (e: Exception) { } catch (e: Exception) {
Log.e("AuthDebug", "打刷新 API 發生網路異常: ${e.message}")
null null
} }
}
// 3. 判斷刷新是否成功 // 4. 判斷刷新是否成功
if (refreshResponse != null && refreshResponse.isSuccessful) { if (refreshResponse != null && refreshResponse.isSuccessful) {
Log.d("AuthDebug", "✅ Token 刷新成功,準備寫入 DataStore")
// saveAuthData...
Log.d("AuthDebug", "✅ DataStore 寫入完成,重新發送原請求")
val newAuthData = refreshResponse.body() val newAuthData = refreshResponse.body()
if (newAuthData != null) { if (newAuthData != null) {
Log.d("AuthDebug", "✅ Token 刷新成功,準備寫入 DataStore")
// 4. 成功:更新本地 DataStore 資料 // 導正順序:確實執行完 saveAuthData
runBlocking {
tokenManager.saveAuthData( tokenManager.saveAuthData(
userId = currentUserId, userId = currentUserId,
accessToken = newAuthData.access_token, accessToken = newAuthData.access_token,
refreshToken = newAuthData.refresh_token, refreshToken = newAuthData.refresh_token,
expiresInSec = newAuthData.expires_in expiresInSec = newAuthData.expires_in
) )
}
// 5. 成功:用新的 Access Token 重新建構剛才失敗的 API 請求 Log.d("AuthDebug", "✅ DataStore 寫入完成,重新發送原請求")
return response.request.newBuilder()
return@runBlocking response.request.newBuilder()
.header("Authorization", "Bearer ${newAuthData.access_token}") .header("Authorization", "Bearer ${newAuthData.access_token}")
.build() .build()
} }
} }
// 6. 失敗:如果連 Refresh Token 都失效(例如過期被後端拒絕),則強制登出,導回登入頁 // 5. 失敗:連 Refresh Token 都失效,強制登出
// TokenAuthenticator.kt 失敗時 Log.e("AuthDebug", "❌ Refresh Token 已失效,執行強制登出!")
runBlocking { tokenManager.logout(currentUserId)
tokenManager.logout(currentUserId) // 這會移除 USER_ID_LIST,觸發 hasAccountLoggedIn = false return@runBlocking null
} }
}
return null
} }
} }