React native Refresh有效,但下一次调用仍然使用最后一个令牌

Gre*_*han 14 javascript jwt reactjs bearer-token react-native

我使用以下中间件在到期时刷新我的令牌:

import {AsyncStorage} from 'react-native';
import moment from 'moment';
import fetch from "../components/Fetch";
import jwt_decode from 'jwt-decode';

/**
 * This middleware is meant to be the refresher of the authentication token, on each request to the API,
 * it will first call refresh token endpoint
 * @returns {function(*=): Function}
 * @param store
 */
const tokenMiddleware = store => next => async action => {
  if (typeof action === 'object' && action.type !== "FETCHING_TEMPLATES_FAILED") {
    let eToken = await AsyncStorage.getItem('eToken');
    if (isExpired(eToken)) {
      let rToken = await AsyncStorage.getItem('rToken');

      let formData = new FormData();
      formData.append("refresh_token", rToken);

      await fetch('/token/refresh',
        {
          method: 'POST',
          body: formData
        })
        .then(response => response.json())
        .then(async (data) => {
            let decoded = jwt_decode(data.token);
            console.log({"refreshed": data.token});

            return await Promise.all([
              await AsyncStorage.setItem('token', data.token).then(() => {return AsyncStorage.getItem('token')}),
              await AsyncStorage.setItem('rToken', data.refresh_token).then(() => {return AsyncStorage.getItem('rToken')}),
              await AsyncStorage.setItem('eToken', decoded.exp.toString()).then(() => {return AsyncStorage.getItem('eToken')}),
            ]).then((values) => {
              return next(action);
            });
        }).catch((err) => {
          console.log(err);
        });

      return next(action);
    } else {
      return next(action);
    }
  }

  function isExpired(expiresIn) {
    // We refresh the token 3.5 hours before it expires(12600 seconds) (lifetime on server  25200seconds)
    return moment.unix(expiresIn).diff(moment(), 'seconds') < 10;
  }
};
  export default tokenMiddleware;
Run Code Online (Sandbox Code Playgroud)

和获取助手:

import { AsyncStorage } from 'react-native';
import GLOBALS from '../constants/Globals';
import {toast} from "./Toast";
import I18n from "../i18n/i18n";

const jsonLdMimeType = 'application/ld+json';

export default async function (url, options = {}, noApi = false) {
  if ('undefined' === typeof options.headers) options.headers = new Headers();
  if (null === options.headers.get('Accept')) options.headers.set('Accept', jsonLdMimeType);

  if ('undefined' !== options.body && !(options.body instanceof FormData) && null === options.headers.get('Content-Type')) {
    options.headers.set('Content-Type', jsonLdMimeType);
  }

  let token = await AsyncStorage.getItem('token');
  console.log({"url": url,"new fetch": token});
  if (token) {
    options.headers.set('Authorization', 'Bearer ' + token);
  }

  let api = '/api';

  if (noApi) {
    api = "";
  }

  const link = GLOBALS.BASE_URL + api + url;
  return fetch(link, options).then(response => {
    if (response.ok) return response;

    return response
      .json()
      .then(json => {
        if (json.code === 401) {
          toast(I18n.t(json.message), "danger", 3000);
          AsyncStorage.setItem('token', '');
        }

        const error = json['message'] ? json['message'] : response.statusText;
        throw Error(I18n.t(error));
      })
      .catch(err => {
        throw err;
      });
  })
  .catch(err => {
    throw err;
  });
}
Run Code Online (Sandbox Code Playgroud)

我的问题是:

  • 当我做出动作时,会调用中间件.
  • 如果令牌即将过期,则调用刷新令牌方法并更新AsyncStorage.
  • 然后next(action)应该调用该方法.
  • 但是我的/templates端点在/token/refresh使用旧的过期令牌之前(而不是之后)被调用...
  • 然后结果是我当前的屏幕返回错误(未授权),但如果用户更改屏幕,它将再次起作用,因为其令牌已成功刷新.但这样丑陋:p

编辑:为了这个问题,我修改了我的代码,把它放到一个文件中.我还放了一些console.log来显示这段代码的执行方式

执行队列

我们从图像中可以看出:

  • 我的调用(/ templates)在我的刷新端点之前被激活.我刷新令牌的控制台日志很久就到了......

对此有何帮助?

编辑直到赏金结束:

从这个问题我试着理解为什么我的方法对中间件是错误的,因为我在互联网上发现的许多资源都将中间件作为实现刷新令牌操作的最佳解决方案.

Isa*_*aac 3

我的处理设置略有不同。我没有在中间件中处理刷新令牌逻辑,而是将其定义为辅助函数。这样我就可以在我认为合适的任何网络请求之前执行所有令牌验证,并且任何不涉及网络请求的 redux 操作都不需要此函数

export const refreshToken = async () => {
  let valid = true;

  if (!validateAccessToken()) {
    try {
      //logic to refresh token
      valid = true;
    } catch (err) {
      valid = false;
    }

    return valid;
  }
  return valid;
};

const validateAccessToken = () => {
  const currentTime = new Date();

  if (
    moment(currentTime).add(10, 'm') <
    moment(jwtDecode(token).exp * 1000)
  ) {
    return true;
  }
  return false;
};
Run Code Online (Sandbox Code Playgroud)

现在我们有了这个辅助函数,我为所有需要的 redux 操作调用它

const shouldRefreshToken = await refreshToken();
    if (!shouldRefreshToken) {
      dispatch({
        type: OPERATION_FAILED,
        payload: apiErrorGenerator({ err: { response: { status: 401 } } })
      });
    } else { 
      //...
    }
Run Code Online (Sandbox Code Playgroud)