Javascript React本机刷新工作,但下一次调用仍使用最后一个令牌

Javascript React本机刷新工作,但下一次调用仍使用最后一个令牌,javascript,reactjs,react-native,jwt,bearer-token,Javascript,Reactjs,React Native,Jwt,Bearer Token,我使用以下中间件在令牌过期时刷新令牌: 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

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

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;
我的问题是:

  • 当我做一个动作时,中间件被调用
  • 如果令牌即将过期,则调用刷新令牌方法并更新异步存储
  • 然后调用
    next(action)
    方法
  • 但是我的
    /templates
    端点是在我的
    /token/refresh
    端点之前(而不是之后)使用旧的过期令牌调用的…
  • 然后,结果是我的当前屏幕返回一个错误(未经授权),但如果用户更改屏幕,它将再次工作,因为它的令牌已成功刷新。但是那样很难看:p
编辑:为了解决这个问题,我修改了代码,将其放入一个文件中。 我还添加了一些console.log来显示如何执行此代码

我们可以从图像中看到:

  • 我的调用(/templates)在刷新端点之前执行。我刷新令牌的控制台日志在那之后很长时间内到达
请问有什么帮助吗

编辑直到赏金结束:


从这个问题中,我试图理解为什么我的方法在中间件方面是错误的,因为我在internet上找到的许多资源都认为中间件是实现刷新令牌操作的最佳解决方案。

您有一个请求竞争条件,没有一个正确的解决方案可以完全解决这个问题。以下项目可作为解决此问题的起点:

  • 单独使用令牌刷新并等待其在客户端执行,例如,如果在会话超时的一半时间内发送了任何请求,则发送令牌刷新(如GET/keepalive),这将导致所有请求都将获得100%授权(我肯定会使用的选项-它不仅可以用于跟踪请求,还可以用于跟踪事件)
  • 接收401后清理令牌-假设在边界场景中删除有效令牌是积极场景(易于实现的解决方案),则重新加载后不会看到工作应用程序
  • 重复接收到401的查询,但有一些延迟(实际上不是最佳选项)
  • 强制令牌更新比超时更频繁-将它们更改为超时的50-75%将减少失败请求的数量(但如果用户在所有会话时间都是iddle,则它们仍将持续存在)。因此,任何有效请求都将返回新的有效令牌,而不是旧的令牌

  • 实现令牌扩展周期当旧令牌可以被计算为传输周期的有效令牌时-旧令牌被扩展一段有限的时间以绕过问题(听起来不是很好,但至少是一个选项)


我在处理上有一个稍微不同的设置。我没有在中间件中处理刷新令牌逻辑,而是将其定义为帮助函数。这样,我就可以在我认为合适的任何网络请求之前进行所有令牌验证,任何不涉及网络请求的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;
};

在您的中间件中,您使
store.dispatch
异步,但
store.dispatch
的原始签名是同步的。这可能会产生严重的副作用

让我们考虑一个简单的中间件,它记录应用程序中发生的每一个动作,以及在它之后计算的状态:

const logger = store => next => action => {
  console.log('dispatching', action)
  let result = next(action)
  console.log('next state', store.getState())
  return result
}
编写上述中间件实质上是执行以下操作:

const next = store.dispatch  // you take current version of store.dispatch
store.dispatch = function dispatchAndLog(action) {  // you change it to meet your needs
  console.log('dispatching', action)
  let result = next(action) // and you return whatever the current version is supposed to return
  console.log('next state', store.getState())
  return result
}
考虑以下示例,其中3个中间件链接在一起:

const{
createStore,
applyMiddleware,
联合传感器,
组成
}=window.Redux;
常量计数器=(状态=0,操作)=>{
开关(动作类型){
案例“增量”:
返回状态+1;
违约:
返回状态;
}
};
const rootReducer=combinereducer({
计数器:计数器减速机
});
const logger=store=>next=>action=>{
控制台日志(“调度”,动作);
让结果=下一步(动作);
log(“下一个状态”,store.getState());
返回结果;
};
const logger2=store=>next=>action=>{
控制台日志(“调度2”,动作);
让结果=下一步(动作);
log(“下一个状态2”,store.getState());
返回结果;
};
const logger3=store=>next=>action=>{
控制台日志(“调度3”,动作);
让结果=下一步(动作);
log(“下一个状态3”,store.getState());
返回结果;
};
const middlewareEnhancer=applyMiddleware(记录器,记录器2,记录器3);
const store=createStore(rootReducer、middlewareEnhancer);
仓库调度({
类型:“增量”
});
console.log('current state',store.getState());

您好,我已经考虑了您的大多数选择。实际上,我正在寻找使用我的上下文(中间件)的解决方案或者非常接近的解决方案。您好,谢谢您的建议,它当然可能会起作用,您能解释一下为什么我的方法不起作用吗?您的解决方案涉及到我的还原器中的许多条件,如果我有其他选择,我不想这样做。中间件应该可以正常工作吗?对于您的实用主义和解决方案,您将得到正确的答案。我已测试我的问题用你的方式解决了。哇,让我告诉你,我很高兴你的答案,明天一定会尝试。非常感谢你花时间写这篇文章。我使用redux persist来存储我的一些减缩器。你的建议第一个很棘手:p。你的apiCallMaker有错误,对吗?如果出现错误,你可以调用操作[2]不是[1]?为了你的解释,你赢得了赏金。确实这是我的问题。但这意味着要解决它需要太多的重构,我更愿意实现另一个解决方案(最快,时间就是金钱),感谢你在这方面的努力。
const logger = store => next => action => {
  console.log('dispatching', action)
  let result = next(action)
  console.log('next state', store.getState())
  return result
}
const next = store.dispatch  // you take current version of store.dispatch
store.dispatch = function dispatchAndLog(action) {  // you change it to meet your needs
  console.log('dispatching', action)
  let result = next(action) // and you return whatever the current version is supposed to return
  console.log('next state', store.getState())
  return result
}