Apollo Android-Jwt身份验证令牌刷新ApolloInterceptor内部的逻辑

时间:2020-07-22 18:45:28

标签: android graphql apollo-android

我正在处理一个应用程序,当它收到来自GraphQL的未经身份验证的错误时,需要在后台刷新身份验证令牌。我以前使用OkHttp拦截器为普通API实现了此功能,并将一些逻辑移植到了ApolloInterceptor。

我试图查看内部的Apollo拦截器源代码,以了解它们的工作原理,并设法制定了可行的解决方案。但是,因为这是我第一次使用apollo-android库,所以我想征询一些反馈意见,也许我忽略了某些事情。

class ApolloRefreshTokenInterceptor(
        private val tokenService: TokenService,
        private val tokenStorage: TokenStorage
) : ApolloInterceptor {

    @Volatile
    private var disposed = false
    @Volatile
    private var retryCount = 0

    override fun interceptAsync(request: ApolloInterceptor.InterceptorRequest, chain: ApolloInterceptorChain, dispatcher: Executor, callBack: ApolloInterceptor.CallBack) {

        chain.proceedAsync(request, dispatcher, object : ApolloInterceptor.CallBack {

            override fun onFailure(e: ApolloException) {
                e.printStackTrace()
                callBack.onFailure(e)
            }

            override fun onResponse(response: ApolloInterceptor.InterceptorResponse) {

                if (disposed) return

                if (isUnauthenticated(response)) {

                    // Get current tokens before refreshing.
                    val currentTokens = tokenStorage.get()

                    Log.d("GRAPHQL", "User unauthenticated")
                    synchronized(this) {

                        if (retryCount >= MAX_RETRY_COUNT) {
                            Log.d("GRAPHQL", "Retry count reached limit.")
                            callBack.onResponse(response)
                            callBack.onCompleted()
                            retryCount = 0
                            return
                        }

                        retryCount++

                        // Check if the token was not already refreshed.
                        if (wasTokenAlreadyRefreshed(currentTokens)) {
                            Log.d("GRAPHQL", "Token already refreshed.")
                            return chain.proceedAsync(
                                    request
                                            .toBuilder()
                                            .fetchFromCache(false)
                                            .build(),
                                    dispatcher,
                                    this
                            )
                        }

                        // Refresh token sync.
                        val result = runBlocking {
                            Log.d("GRAPHQL", "Refreshing token.")
                            refreshToken()
                        }

                        if (result.isSuccess()) {

                            Log.d("GRAPHQL", "Tokens refreshed")
                            // Remake request.
                            chain.proceedAsync(
                                    request
                                            .toBuilder()
                                            .fetchFromCache(false)
                                            .build(),
                                    dispatcher,
                                    this
                            )
                            return

                        } else {

                            Log.d("GRAPHQL", "Raise event cannot refresh token")

                            // Raise UserUnauthenticatedEvent to be handled by upper layer.
                            // We cannot refresh the session automatically, user needs to login again.

                            raiseUnAuthenticatedUserEvent()
                            callBack.onResponse(response)
                            callBack.onCompleted()
                            return
                        }

                    }

                } else {
                    retryCount = 0
                    callBack.onResponse(response)
                    callBack.onCompleted()
                }

            }

            override fun onFetch(sourceType: ApolloInterceptor.FetchSourceType?) {
                callBack.onFetch(sourceType)
            }

            override fun onCompleted() {
            }

        })

    }

    override fun dispose() {
        disposed = true
    }

    private fun isUnauthenticated(response: ApolloInterceptor.InterceptorResponse): Boolean {
        return if (response.parsedResponse.isPresent) {
            @Suppress("UNCHECKED_CAST")
            val errors = response.parsedResponse.get().errors as List<Error>?
            errors?.find { it.message == "authentication required" } != null
        } else false
    }

    private suspend fun refreshToken(): Result<Boolean> = tokenService.refresh()

    /**
     * Check if authentication tokens are different. If they are different it means another
     * thread already refreshed the access tokens.
     */
    private fun wasTokenAlreadyRefreshed(authTokens: TokenStorage.AuthTokens?): Boolean =
            authTokens != tokenStorage.get()

    private fun raiseUnAuthenticatedUserEvent() {
        // TODO raise event
    }


}

这些是我考虑的前提条件。

  • 确保用于操作令牌的方法已同步。只有一个线程尝试刷新令牌。
  • 计算重试次数以防止刷新令牌调用过多。
  • 确保API调用以获取不异步的新令牌。
  • 检查访问令牌是否已经被另一个线程刷新,以避免请求新的访问令牌。

这是正确的方法还是执行这种操作,或者有其他选择吗?

谢谢

0 个答案:

没有答案
相关问题