如何在 React.js 应用程序中刷新 JWT 令牌?

IT技术 reactjs react-redux jwt axios redux-thunk
2021-05-08 16:00:53

我在这里检查了所有类似的问题,但没有一个是我需要的。我正在保护我的应用程序中的路由,并随每个请求发送 JWT,这里一切正常。问题是当 JWT 到期时,我需要知道如何刷新该令牌并使用户保持登录状态,而不是注销用户。

每个人都在谈论创建一个处理它的“中间件”,但没有人说如何创建该中间件以及其中有什么?

那么,这样做的最佳实践是什么?我应该在发送任何请求之前检查 JWT 到期日期吗?或者我应该等待“401”响应然后尝试刷新令牌(我不知道该怎么做),或者究竟是什么?

如果有人在 Github 上有这样的中间件或包或项目的工作示例可以帮助我解决这个问题,那就太好了。

我只对流程的前端部分感兴趣,从react发送什么以及我应该收到什么以及如何处理它。

2个回答

如果您正在使用 Axios(我强烈推荐),您可以在响应的拦截器中声明您的令牌刷新行为这将适用于 Axios 发出的所有 https 请求。

这个过程是这样的

  1. 检查错误状态是否为 401
    • 如果有有效的刷新令牌:使用它来获取访问令牌
    • 如果没有有效的刷新令牌:注销用户并返回
  2. 使用新令牌再次重做请求。

下面是一个例子:

axios.interceptors.response.use(
  (response) => {
    return response
  },
  (error) => {
    return new Promise((resolve) => {
      const originalRequest = error.config
      const refreshToken = localStorage.get('refresh_token')
      if (error.response && error.response.status === 401 && error.config && !error.config.__isRetryRequest && refreshToken) {
        originalRequest._retry = true

        const response = fetch(api.refreshToken, {
          method: 'POST',
          headers: {
            'Content-Type': 'application/json',
          },
          body: JSON.stringify({
            refresh: refreshToken,
          }),
        })
          .then((res) => res.json())
          .then((res) => {
            localStorage.set(res.access, 'token')

            return axios(originalRequest)
          })
        resolve(response)
      }

      return Promise.reject(error)
    })
  },
)

你的中间件应该看起来像这个代码块(例如,你可以使用任何你想要的)

/* eslint-disable */
import request from 'superagent';
function call(meta, token) {
  const method = meta.API_METHOD ? meta.API_METHOD : 'GET';
  let req = request(method, 'http://localhost:8000/' + meta.API_CALL);
  req = req.set({ Authorization: `JWT ${token}` });
  req = meta.API_TYPE ? req.type('Content-Type', meta.API_TYPE) : req.set('Content-Type', 'application/json');
  if (meta.API_PAYLOAD) {
    req = req.send(meta.API_PAYLOAD);
  }
  if (meta.API_QUERY) {
    req.query(meta.API_QUERY);
  }

  return req;
}

export default store => next => action => {
  const state = store.getState();
  const token = state.logged && state.logged.get('token') ?
    state.logged.get('token') : 'eyJhbGciOiJIUzUxMiJ9';
  if (action.meta && action.meta.API_CALL) {
    call(action.meta, token)
      .then((res) => {
        store.dispatch({
          type: action.meta.API_SUCCESS,
          result: res.body,
        });
      })
      .catch(({ status, response }) => {
        if (action.meta.API_ERRORS && action.meta.API_ERRORS[status]) {
          return store.dispatch({
            type: action.meta.API_ERRORS[status],
            result: response.body,
          });
        }
        if (action.meta.API_ERRORS && action.meta.API_ERRORS[status] === '401') {
          /*call the refresh token api*/
          call(<Your Meta for refreshing>, <expiredtoken>)
                .then((res) => {
                    store.dispatch({
                    type: action.meta.API_SUCCESS,
                    result: res.body,
                    });
                })
                .catch(({ status, response }) => {
                    if (action.meta.API_ERRORS && action.meta.API_ERRORS[status]) {
                    return store.dispatch({
                        type: action.meta.API_ERRORS[status],
                        result: response.body,
                    });
                    }
                    throw response;
                });
        }
        throw response;
      });
  }
  return next(action);
};