【问题标题】:Refreshing access token with multiple requests使用多个请求刷新访问令牌
【发布时间】:2019-10-13 03:09:48
【问题描述】:

我正在努力让 axios 拦截器工作。

当我的令牌过期时,我需要它来刷新访问令牌并在刷新令牌后重试原始请求。 我有这部分工作。

问题是如果我有并发 api 调用,它只会在令牌第一次无效时重试第一个请求。

这是我的拦截器代码:

    export default function execute() {
  let isRefreshing = false

  // Request
  axios.interceptors.request.use(
    config => {
      var token = Storage.getAccessToken() //localStorage.getItem("token");
      if (token) {
        console.log('Bearer ' + token)
        config.headers['Authorization'] = 'Bearer ' + token
      }
      return config
    },
    error => {
      return Promise.reject(error)
    }
  )

  // Response
  axios.interceptors.response.use(
    response => {
      return response
    },
    error => {
      const originalRequest = error.config
      // token expired
      if (error.response.status === 401) {
        console.log('401 Error need to reresh')

        originalRequest._retry = true

        let tokenModel = {
          accessToken: Storage.getAccessToken(),
          client: 'Web',
          refreshToken: Storage.getRefreshToken()
        }
        //Storage.destroyTokens();
        var refreshPath = Actions.REFRESH

        if (!isRefreshing) {
          isRefreshing = true

          return store
            .dispatch(refreshPath, { tokenModel })
            .then(response => {
              isRefreshing = false
              console.log(response)
              return axios(originalRequest)
            })
            .catch(error => {
              isRefreshing = false
              console.log(error)
              // Logout
            })
        } else {
          console.log('XXXXX')
          console.log('SOME PROBLEM HERE') // <------------------
          console.log('XXXXX')
        }
      } else {
        store.commit(Mutations.SET_ERROR, error.response.data.error)
      }
      return Promise.reject(error)
    }
  )
}

我不确定上面突出显示的 else 块中我需要什么。

编辑:

当我这样做时

return axios(originalRequest)

在 else 块中它可以工作,但是我对这些行为不满意。它基本上一次又一次地重试所有请求,直到刷新令牌。 我宁愿在刷新令牌后重试一次 任何想法

谢谢

【问题讨论】:

  • 如果您在第一个请求上取消设置令牌由于过期而失败,然后将所有下一个请求放入某个队列中直到令牌被刷新,该怎么办。刷新令牌时,处理队列。

标签: axios vuex interceptor


【解决方案1】:

我不知道您的令牌的架构是什么(解密后),但最好保留的属性之一是 exp“expiration_date”。 也就是说,有了到期日期,您就可以知道何时应该刷新令牌。

如果不了解您的架构,就很难找到正确的解决方案。但是假设你是手动做所有事情,通常onIdle/onActive是我们检查用户会话是否仍然正常的时候,所以这个时候你可以使用令牌信息来知道你是否应该刷新它的值。

了解这个过程很重要,因为只有当用户一直处于活动状态并且即将到期(例如 2 分钟前)时,才应刷新令牌。

【讨论】:

    【解决方案2】:

    请参考我遇到相同问题的代码的角度版本,在更改了许多方法后,这是我的最终代码,它处于最佳状态。

    Re Initaite the last failed request after refresh token is provided

    【讨论】:

      【解决方案3】:

      您可以只使用额外的拦截器来刷新令牌并执行您的待处理请求。

      在这方面,countDownLatch 类可以提供帮助。 这是示例拦截器代码,

      class AutoRefreshTokenRequestInterceptorSample() : Interceptor {
      
          companion object {
              var countDownLatch = CountDownLatch(0)
              var previousAuthToken = ""
      
              const val SKIP_AUTH_TOKEN = "SkipAccessTokenHeader"
              const val AUTHORIZATION_HEADER = "AUTHORIZATION_HEADER_KEY"
          }
      
          @Throws(IOException::class)
          override fun intercept(chain: Interceptor.Chain): Response? {
              val request = chain.request()
      
              if (shouldExecuteRequest(request)) {
      
                  // Execute Request
                  val response = chain.proceed(request)
      
                  if (!response.isSuccessful) {
                      // Failed Case
                      val errorBody = response.peekBody(java.lang.Long.MAX_VALUE).string()
                      val error = parseErrorModel(errorBody)
      
                      // Gives Signal to HOLD the Request Queue
                      countDownLatch = CountDownLatch(1)
      
                      handleError(error!!)
      
                      // After updating token values, execute same request with updated values.
                      val updatedRequest = getUpdatedRequest(request)
      
                      // Gives Signal to RELEASE Request Queue
                      countDownLatch.countDown()
      
                      //Execute updated request
                      return chain.proceed(updatedRequest)
                  } else {
                      // success case
                      return response
                  }
              }
      
              // Change updated token values in pending request objects and execute them!
              // If Auth header exists, and skip header not found then hold the request
              if (shouldHoldRequest(request)) {
                  try {
                      // Make this request to WAIT till countdown latch has been set to zero.
                      countDownLatch.await()
                  } catch (e: Exception) {
                      e.printStackTrace()
                  }
      
                  // Once token is Updated, then update values in request model.
                  if (previousAuthToken.isNotEmpty() && previousAuthToken != "newAccessToken") {
                      val updatedRequest = getUpdatedRequest(request)
                      return chain.proceed(updatedRequest)
                  }
              }
      
              return chain.proceed(request)
          }
      
          private fun handleError(error: ErrorDto) {
              // update your token as per your error code logic
              //Here it will make new API call to update tokens and store it in your local preference.
          }
      
          /***
           * returns Request object with updated token values.
           */
          private fun getUpdatedRequest(request: Request): Request {
              var updateAuthReqBuilder: Request.Builder = request.newBuilder()
              var url = request.url().toString()
      
              if (url.contains(previousAuthToken.trim()) && previousAuthToken.trim().isNotEmpty()) {
                  url = url.replace(previousAuthToken, "newAccessToken")
              }
              updateAuthReqBuilder = updateAuthReqBuilder.url(url)
              // change headers if needed
              return updateAuthReqBuilder.build()
          }
      
          private fun shouldExecuteRequest(request: Request) =
                  shouldHoldRequest(request) && isSharedHoldSignalDisabled()
      
          /**
           * If count down latch has any value then it is reported by previous request's error signal to hold the whole pending chain.
           */
          private fun isSharedHoldSignalDisabled() = countDownLatch.count == 0L
      
          private fun shouldHoldRequest(request: Request) = !hasSkipFlag(request) && hasAuthorizationValues(request)
      
          private fun hasAuthorizationValues(request: Request) = isHeaderExist(request, AUTHORIZATION_HEADER)
      
          private fun hasSkipFlag(request: Request) = isHeaderExist(request, SKIP_AUTH_TOKEN)
      
      
          private fun isHeaderExist(request: Request, headerName: String): Boolean {
              return request.header(headerName) != null
          }
      
          private fun parseErrorModel(errorBody: String): Error? {
              val parser = JsonParser()
      
              // Change this logic according to your requirement.
              val jsonObject = parser.parse(errorBody).asJsonObject
              if (jsonObject.has("Error") && jsonObject.get("Error") != null) {
                  val errorJsonObj = jsonObject.get("Error").asJsonObject
                  return decodeErrorModel(errorJsonObj)
              }
              return null
          }
      
          private fun decodeErrorModel(jsonObject: JsonObject): Error {
              val error = Error()
             // decode your error object here
              return error
          }
      }
      

      【讨论】:

        【解决方案4】:

        我就是这样做的:

        let isRefreshing = false;
        let failedQueue = [];
        
        const processQueue = (error, token = null) => {
          failedQueue.forEach(prom => {
            if (error) {
              prom.reject(error);
            } else {
              prom.resolve(token);
            }
          });
          failedQueue = [];
        };
        
        axios.interceptors.response.use(
            response => response,
            error => {
              const originalRequest = error.config;
              if (error.response.status === 400) {
                // If response is 400, logout
                store.dispatch(logout());
              }
              // If 401 and I'm not processing a queue
              if (error.response.status === 401 && !originalRequest._retry) {
                if (isRefreshing) {
                  // If I'm refreshing the token I send request to a queue
                  return new Promise((resolve, reject) => {
                    failedQueue.push({ resolve, reject });
                  })
                    .then(() => {
                      originalRequest.headers.Authorization = getAuth();
                      return axios(originalRequest);
                    })
                    .catch(err => err);
                }
                // If header of the request has changed, it means I've refreshed the token
                if (originalRequest.headers.Authorization !== getAuth()) {
                  originalRequest.headers.Authorization = getAuth();
                  return Promise.resolve(axios(originalRequest));
                }
        
                originalRequest._retry = true; // mark request a retry
                isRefreshing = true; // set the refreshing var to true
        
                // If none of the above, refresh the token and process the queue
                return new Promise((resolve, reject) => {
                  // console.log('REFRESH');
                  refreshAccessToken() // The method that refreshes my token
                    .then(({ data }) => {
                      updateToken(data); // The method that sets my token to localstorage/Redux/whatever
                      processQueue(null, data.token); // Resolve queued
                      resolve(axios(originalRequest)); // Resolve current
                    })
                    .catch(err => {
                      processQueue(err, null);
                      reject(err);
                    })
                    .then(() => {
                      isRefreshing = false;
                    });
                });
              }
        
              return Promise.reject(error);
            },
          );
        

        【讨论】:

          猜你喜欢
          • 1970-01-01
          • 2019-11-10
          • 2019-06-29
          • 2022-10-31
          • 2019-11-08
          • 2019-12-03
          • 2020-07-12
          • 1970-01-01
          • 1970-01-01
          相关资源
          最近更新 更多