英文:
Token refreshing with Retrofit and Interceptor for multiple requests in Android
问题
I understand you'd like a translation of the code portion you provided. Here's the translated code:
class AuthInterceptor(private val authorizationRepository: AuthorizationRepository) : Interceptor {
override fun intercept(chain: Interceptor.Chain): Response {
val originalRequest = chain.request()
val userIsNotAuthorize = runBlocking { !authorizationRepository.userIsAuthorize().first() }
if (userIsNotAuthorize) {
return chain.proceed(originalRequest)
}
val accessToken = authorizationRepository.getAccessToken()
val tokenHasExpired = authorizationRepository.tokenHasExpired()
return if (tokenHasExpired) {
chain.proceedDeletingTokenOnError(
originalRequest.newBuilder()
.addHeaders(accessToken)
.build()
)
} else {
val refreshTokenRequest = originalRequest.newBuilder()
.post("".toRequestBody("application/json".toMediaType()))
.url(BuildConfig.BASE_URL + "/v1/user/refresh")
.addHeaders(accessToken)
.build()
val refreshResponse = chain.proceedDeletingTokenOnError(refreshTokenRequest)
refreshResponse.use {
if (it.isSuccessful) {
val refreshedToken = Gson().fromJson(
it.body?.string(),
AuthorizedUserDto::class.java
)
authorizationRepository.setAccessToken(
refreshedToken.items.token,
refreshedToken.items.expiredAt
)
val newCall = originalRequest.newBuilder()
.addHeaders(refreshedToken.items.token)
.build()
chain.proceedDeletingTokenOnError(newCall)
} else {
chain.proceedDeletingTokenOnError(originalRequest)
}
}
}
}
private inline fun Request.Builder.addHeaders(token: String) =
this.apply { header("Authorization", "Bearer $token") }
private fun Interceptor.Chain.proceedDeletingTokenOnError(request: Request): Response {
val response = proceed(request)
if (response.code == 401) {
runBlocking { authorizationRepository.changeUserState(UserState.NotAuthorized) }
}
return response
}
}
Please note that code translations might not capture all the context and dependencies, so it's important to review and adapt the code as needed for your specific use case.
英文:
How to handle token refresh for multiple requests in Interceptor?
I'm implementing token refresh functionality in my Android app using an AuthInterceptor in Retrofit. The code provided works well for a single request, but I'm encountering issues when multiple requests are made simultaneously.
Here's the relevant code snippet:
class AuthInterceptor(private val authorizationRepository: AuthorizationRepository) : Interceptor {
override fun intercept(chain: Interceptor.Chain): Response {
val originalRequest = chain.request()
val userIsNotAuthorize = runBlocking { !authorizationRepository.userIsAuthorize().first() }
if (userIsNotAuthorize) {
return chain.proceed(originalRequest)
}
val accessToken = authorizationRepository.getAccessToken()
val tokenHasExpired = authorizationRepository.tokenHasExpired()
return if (tokenHasExpired) {
chain.proceedDeletingTokenOnError(
originalRequest.newBuilder()
.addHeaders(accessToken)
.build()
)
} else {
val refreshTokenRequest = originalRequest.newBuilder()
.post("".toRequestBody("application/json".toMediaType()))
.url(BuildConfig.BASE_URL + "/v1/user/refresh")
.addHeaders(accessToken)
.build()
val refreshResponse = chain.proceedDeletingTokenOnError(refreshTokenRequest)
refreshResponse.use {
if (it.isSuccessful) {
val refreshedToken = Gson().fromJson(
it.body?.string(),
AuthorizedUserDto::class.java
)
authorizationRepository.setAccessToken(
refreshedToken.items.token,
refreshedToken.items.expiredAt
)
val newCall = originalRequest.newBuilder()
.addHeaders(refreshedToken.items.token)
.build()
chain.proceedDeletingTokenOnError(newCall)
} else {
chain.proceedDeletingTokenOnError(originalRequest)
}
}
}
}
private inline fun Request.Builder.addHeaders(token: String) =
this.apply { header("Authorization", "Bearer $token") }
private fun Interceptor.Chain.proceedDeletingTokenOnError(request: Request): Response {
val response = proceed(request)
if (response.code == 401) {
runBlocking { authorizationRepository.changeUserState(UserState.NotAuthorized) }
}
return response
}
}
The issue I'm facing is that when multiple requests are made and the token has expired, the interceptor proceeds with token refresh for each request individually. As a result, the refresh token endpoint may be called multiple times, causing the token to become invalid.
I've tried debugging my app and noticed that the /v1/user/refresh request can be triggered multiple times. I'm wondering if there is a better solution to handle token refresh for multiple requests simultaneously. Any suggestions or alternative approaches would be greatly appreciated. Thank you!
答案1
得分: 1
以下是使用 ReentrantLock
的拦截器代码部分的翻译:
class AuthInterceptor(private val authorizationRepository: AuthorizationRepository) : Interceptor {
// 创建一个锁对象,假设你对所有请求使用相同的拦截器实例,或者创建一个静态锁
private val reentrantLock = ReentrantLock(true)
override fun intercept(chain: Interceptor.Chain): Response {
val originalRequest = chain.request()
val userIsNotAuthorize = runBlocking { !authorizationRepository.userIsAuthorize().first() }
if (userIsNotAuthorize) {
return chain.proceed(originalRequest)
}
val accessToken = authorizationRepository.getAccessToken()
val tokenHasExpired = authorizationRepository.tokenHasExpired()
return if (tokenHasExpired) {
chain.proceedDeletingTokenOnError(
originalRequest.newBuilder()
.addHeaders(accessToken)
.build()
)
} else {
// 在刷新之前加锁
reentrantLock.lock()
// 在进入锁定块时,验证之前的请求是否已经更新了令牌
val recentAccessToken = authorizationRepository.getAccessToken()
val recentTokenHasExpired = authorizationRepository.tokenHasExpired()
if(recentTokenHasExpired) {
return chain.proceed(
originalRequest.newBuilder()
.addHeaders(accessToken)
.build())
}
// 如果没有刷新,则在锁定内刷新
val refreshTokenRequest = originalRequest.newBuilder()
.post("".toRequestBody("application/json".toMediaType()))
.url(BuildConfig.BASE_URL + "/v1/user/refresh")
.addHeaders(accessToken)
.build()
val refreshResponse = chain.proceedDeletingTokenOnError(refreshTokenRequest)
refreshResponse.use {
if (it.isSuccessful) {
val refreshedToken = Gson().fromJson(
it.body?.string(),
AuthorizedUserDto::class.java
)
authorizationRepository.setAccessToken(
refreshedToken.items.token,
refreshedToken.items.expiredAt
)
val newCall = originalRequest.newBuilder()
.addHeaders(refreshedToken.items.token)
.build()
chain.proceedDeletingTokenOnError(newCall)
} else {
chain.proceedDeletingTokenOnError(originalRequest)
}
}
// 在刷新令牌后解锁,以便队列中等待锁的下一个请求可以使用更新的令牌继续
reentrantLock.unlock()
}
}
private inline fun Request.Builder.addHeaders(token: String) =
this.apply { header("Authorization", "Bearer $token") }
private fun Interceptor.Chain.proceedDeletingTokenOnError(request: Request): Response {
val response = proceed(request)
if (response.code == 401) {
runBlocking { authorizationRepository.changeUserState(UserState.NotAuthorized) }
}
return response
}
}
如果您需要进一步的帮助,请随时告诉我。
英文:
You could handle multiple requests in interceptor using locks, find below code with comments using ReentrantLock
class AuthInterceptor(private val authorizationRepository: AuthorizationRepository) : Interceptor {
//Create a lock object, assuming you are same instance of interceptor for all requests or create a static lock
private val reentrantLock = ReentrantLock(true)
override fun intercept(chain: Interceptor.Chain): Response {
val originalRequest = chain.request()
val userIsNotAuthorize = runBlocking { !authorizationRepository.userIsAuthorize().first() }
if (userIsNotAuthorize) {
return chain.proceed(originalRequest)
}
val accessToken = authorizationRepository.getAccessToken()
val tokenHasExpired = authorizationRepository.tokenHasExpired()
return if (tokenHasExpired) {
chain.proceedDeletingTokenOnError(
originalRequest.newBuilder()
.addHeaders(accessToken)
.build()
)
} else {
//lock before refreshing
reentrantLock.lock()
//verify if previous request already updated the token while entering the locked block
val recentAccessToken = authorizationRepository.getAccessToken()
val recentTokenHasExpired = authorizationRepository.tokenHasExpired()
if(recentTokenHasExpired) {
return chain.proceed(
originalRequest.newBuilder()
.addHeaders(accessToken)
.build())
}
//if not refreshed refresh inside lock
val refreshTokenRequest = originalRequest.newBuilder()
.post("".toRequestBody("application/json".toMediaType()))
.url(BuildConfig.BASE_URL + "/v1/user/refresh")
.addHeaders(accessToken)
.build()
val refreshResponse = chain.proceedDeletingTokenOnError(refreshTokenRequest)
refreshResponse.use {
if (it.isSuccessful) {
val refreshedToken = Gson().fromJson(
it.body?.string(),
AuthorizedUserDto::class.java
)
authorizationRepository.setAccessToken(
refreshedToken.items.token,
refreshedToken.items.expiredAt
)
val newCall = originalRequest.newBuilder()
.addHeaders(refreshedToken.items.token)
.build()
chain.proceedDeletingTokenOnError(newCall)
} else {
chain.proceedDeletingTokenOnError(originalRequest)
}
}
// unlock after refreshing token, so next request
// in queue waiting for lock would proceed with updated token
reentrantLock.unlock()
}
}
private inline fun Request.Builder.addHeaders(token: String) =
this.apply { header("Authorization", "Bearer $token") }
private fun Interceptor.Chain.proceedDeletingTokenOnError(request: Request): Response {
val response = proceed(request)
if (response.code == 401) {
runBlocking { authorizationRepository.changeUserState(UserState.NotAuthorized) }
}
return response
}
}
通过集体智慧和协作来改善编程学习和解决问题的方式。致力于成为全球开发者共同参与的知识库,让每个人都能够通过互相帮助和分享经验来进步。
评论