[英]Using Coroutine runblock with the Authenticator to handle 401 response from retrofit
I am trying to use the Authenticator to handle 401 response.我正在尝试使用 Authenticator 来处理 401 响应。 What I have done is
我所做的是
fun provideAccessTokenAuthenticator(
mainApiServiceHolder: MainApiServiceHolder,
preferences: SharedPreferences
) = object : Authenticator {
override fun authenticate(route: Route?, response: Response): Request? {
val accessToken = preferences.getString(ACCESS_TOKEN, null)
if (!isRequestWithAccessToken(response) || accessToken == null) {
return null
}
synchronized(this) {
val newAccessToken = preferences.getString(ACCESS_TOKEN, null)!!
// Access token is refreshed in another thread.
if (accessToken != newAccessToken) {
return newRequestWithAccessToken(response.request, newAccessToken)
}
// Need to refresh an access token
val refreshTokenResponse = runBlocking {
Log.d("zzzzzzzzzz", "refresh token is running")
mainApiServiceHolder.mainApiService?.refreshToken(
"refresh_token",
preferences.getString(REFRESH_TOKEN, null)!!,
AuthRepository.CLIENT_ID,
AuthRepository.CLIENT_SECRET
)
}
Log.d("zzzzzzzzzz", refreshTokenResponse?.body()?.access_token!!)
return if (refreshTokenResponse?.isSuccessful!!) {
Log.d("zzzzzzzzzz", "refresh token is successful")
newRequestWithAccessToken(
response.request,
refreshTokenResponse.body()?.access_token!!
)
} else {
Log.d("zzzzzzzzzz", "refresh token is unsuccessful")
response.request.newBuilder().header("Content-Type", "application/json").build()
}
}
}
Now, it gets called when there is a 401 response.现在,它会在有 401 响应时被调用。 The refresh token call is also fired (from Log).
刷新令牌调用也被触发(从日志)。 However, it never gets the result in the refreshTokenResponse and nothing happens after that.
但是,它永远不会在 refreshTokenResponse 中得到结果,之后什么也没有发生。 I think its a wrong way of using runBlock.
我认为这是一种错误的使用 runBlock 的方式。 The api is
api 是
@FormUrlEncoded
@POST("/api/auth/token/")
suspend fun refreshToken(
@Field("grant_type") grant_type: String,
@Field("refresh_token") refresh_token: String,
@Field("client_id") client_id: String,
@Field("client_secret") client_secret: String
): Response<LoginResponse>
Any help would be really appreciated.任何帮助将非常感激。 Thanks
谢谢
In the Retrofit API, consider replacing your async runBlocking{} suspend fun with a synchronous Call .在 Retrofit API 中,考虑用同步Call替换您的 async runBlocking{} suspend fun 。 I had the most luck avoiding the use of coroutines inside the Authenticator.
我最幸运地避免了在 Authenticator 中使用协程。
I was having the same problem.我遇到了同样的问题。 The token request went straight into a black hole.
令牌请求直接进入黑洞。 The app froze.
应用程序冻结了。 The request was never seen again.
该请求再也没有出现过。 No error, no nothing.
没有错误,什么都没有。
But everywhere else in the app, the suspend fun came back just fine.但是在应用程序的其他任何地方,暂停的乐趣都恢复得很好。 From ViewModels, from WorkManager, it worked every time.
从 ViewModels,从 WorkManager,它每次都能正常工作。 But from the Authenticator, never.
但是从 Authenticator 那里,从来没有。 What was wrong with the Authenticator?
Authenticator 出了什么问题? What was special about the Authenticator that made it act this way?
使它以这种方式运行的 Authenticator 有什么特别之处?
Then I replaced the runBlocking{} coroutine with a straightforward Call .然后我将 runBlocking{} 协程替换为简单的Call 。 This time, the request came back and the token arrived without a fuss.
这一次,请求回来了,令牌也毫不费力地到了。
The way I got the API to work looked like this:我让 API 工作的方式如下所示:
@FormUrlEncoded
@POST("token")
fun refreshTokenSync(
@Field("refresh_token") refreshToken: String,
): Call<RefreshMyTokenResponse>
Then, in the Authenticator:然后,在身份验证器中:
val call = API.refreshTokenSync(refreshToken)
val response = call.execute().body()
I hope this helps someone else who ran into the same issue.我希望这可以帮助遇到同样问题的其他人。 You may receive a warning from Android Studio that this is an inappropriate blocking call.
您可能会收到来自 Android Studio 的警告,这是一个不恰当的阻塞调用。 Ignore it.
忽略它。
https://github.com/hoc081098/Refresh-Token-Sample/blob/master/app/src/main/java/com/hoc081098/refreshtokensample/data/remote/interceptor/AuthInterceptor.kt https://github.com/hoc081098/Refresh-Token-Sample/blob/master/app/src/main/java/com/hoc081098/refreshtokensample/data/remote/interceptor/AuthInterceptor.kt
class AuthInterceptor @Inject constructor(
private val userLocalSource: UserLocalSource,
private val apiService: Provider<ApiService>,
) : Interceptor {
private val mutex = Mutex()
override fun intercept(chain: Interceptor.Chain): Response {
val req = chain.request().also { Timber.d("[1] $it") }
if (NO_AUTH in req.headers.values(CUSTOM_HEADER)) {
return chain.proceedWithToken(req, null)
}
val token =
runBlocking { userLocalSource.user().first() }?.token.also { Timber.d("[2] $req $it") }
val res = chain.proceedWithToken(req, token)
if (res.code != HTTP_UNAUTHORIZED || token == null) {
return res
}
Timber.d("[3] $req")
val newToken: String? = runBlocking {
mutex.withLock {
val user =
userLocalSource.user().first().also { Timber.d("[4] $req $it") }
val maybeUpdatedToken = user?.token
when {
user == null || maybeUpdatedToken == null -> null.also { Timber.d("[5-1] $req") } // already logged out!
maybeUpdatedToken != token -> maybeUpdatedToken.also { Timber.d("[5-2] $req") } // refreshed by another request
else -> {
Timber.d("[5-3] $req")
val refreshTokenRes =
apiService.get().refreshToken(RefreshTokenBody(user.refreshToken, user.username))
.also {
Timber.d("[6] $req $it")
}
val code = refreshTokenRes.code()
if (code == HTTP_OK) {
refreshTokenRes.body()?.token?.also {
Timber.d("[7-1] $req")
userLocalSource.save(
user.toBuilder()
.setToken(it)
.build()
)
}
} else if (code == HTTP_UNAUTHORIZED) {
Timber.d("[7-2] $req")
userLocalSource.save(null)
null
} else {
Timber.d("[7-3] $req")
null
}
}
}
}
}
return if (newToken !== null) chain.proceedWithToken(req, newToken) else res
}
private fun Interceptor.Chain.proceedWithToken(req: Request, token: String?): Response =
req.newBuilder()
.apply {
if (token !== null) {
addHeader("Authorization", "Bearer $token")
}
}
.removeHeader(CUSTOM_HEADER)
.build()
.let(::proceed)
}
声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.