Обновление токена доступа с несколькими запросами - PullRequest
5 голосов
/ 27 мая 2019

Я изо всех сил пытаюсь заставить работать аксиальные перехватчики.

Когда срок действия моего токена истекает, он мне нужен, чтобы обновить токен доступа и повторить исходный запрос после обновления токена.У меня эта часть работает.

Проблема в том, что если у меня есть параллельные вызовы 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)

в остальномБлок это работает, но я не доволен поведением.Он в основном повторяет все запросы снова и снова, пока токен не будет обновлен.Я бы предпочел просто повторить попытку после обновления токена. Любые идеи

Спасибо

Ответы [ 4 ]

1 голос
/ 07 июня 2019

Вы можете просто иметь дополнительный перехватчик, который может обновлять токен и выполнять ожидающие запросы.

В этом может помочь 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
    }
}
0 голосов
/ 01 июля 2019

Вот как я это делаю:

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);
    },
  );
0 голосов
/ 07 июня 2019

Пожалуйста, обратитесь к угловой версии кода, для которой я столкнулся с той же проблемой, и после изменения многих подходов это был мой последний код, который работает в своих лучших проявлениях.

Re Initaite последнего неудавшегося запроса после предоставления токена обновления

0 голосов
/ 07 июня 2019

Я не знаю, какова схема вашего токена (после расшифровки), но один из атрибутов, который стоит сохранить, это exp "expiration_date". Сказав это, имея дату истечения срока действия, вы можете узнать, когда следует обновить свой токен.

Без понимания вашей архитектуры сложно найти правильное решение. Но допустим, что вы все делаете вручную, обычно onIdle / onActive - это когда мы проверяем, все ли в порядке пользовательский сеанс, поэтому в настоящее время вы можете использовать информацию о токене, чтобы узнать, следует ли обновить его значение.

Важно понимать этот процесс, потому что токен должен обновляться только в том случае, если пользователь постоянно активен и срок его действия истекает (как 2 минуты назад).

...