使用axios/axios-auth-refresh时JWT刷新令牌流程出现问题

4
(我已经阅读了类似问题的一些帖子,大多数/全部都建议为刷新令牌请求(与API请求)使用不同的axios实例。然而,我不清楚如何做到这一点,因为我正在使用 axios-auth-refresh自动刷新访问令牌。)
我正在开发一个基于JWT的后端API请求身份验证流程的应用程序。一般流程正常工作,在登录后,用户会得到一个长期刷新令牌和短期访问令牌。使用axios的 axios-auth-refresh插件,当访问令牌过期时,我能够自动刷新访问令牌。
我的问题是,当刷新令牌过期时,我无法捕获错误并将用户重定向到重新认证。我尝试过的所有方法都无法捕获错误。(目前)自动刷新钩子的代码如下:
const refreshAuth = (failed) =>
  axios({ method: "post", url: "token", skipAuthRefresh: true })
    .then(({ status, data: { success, accessToken } }) => {
      console.warn(`status=${status}`);
      if (!success) Promise.reject(failed);

      processToken(accessToken);
      // eslint-disable-next-line no-param-reassign
      failed.response.config.headers.Authorization = `Bearer ${accessToken}`;
      return Promise.resolve();
    })
    .catch((error) => console.error("%o", error));
createAuthRefreshInterceptor(axios, refreshAuth);

在刷新令牌过期或丢失的情况下,我既没有看到status=xxx控制台行,也没有在catch()块中转储错误对象。实际上,这个文件在GitHub上这里,尽管它与上面的工作版本略有不同。主要是,在GH版本中,钩子调用axios.post("token").then(...),而上面我正在进行更明确的调用以添加skipAuthRefresh参数。添加这个参数让我在控制台中获得了更详细的错误跟踪,但我仍然无法通过catch()捕获401响应。我已经尝试了我能想到的一切... 有什么东西让我错过了吗?Randy
3个回答

3

发表这篇文章后,我终于成功解决了问题并找到了可行的解决方案。

解决方案的关键实际上在于对刷新令牌进行调用时使用不同的axios实例。我创建了第二个模块来封装一个不会受到axios-auth-refresh模块创建的拦截器影响的axios实例。在解决一些无意中引起的循环依赖问题后,我达到了这样一个点:当刷新令牌本身已经过期或缺失时,我能看到axios抛出的异常。

(有趣的是,这导致了另一个问题:一旦我意识到刷新令牌不再有效,我需要注销用户并让他们返回到登录页面。因为此应用程序是一个React应用程序,所以认证是通过自定义钩子处理的,而这些钩子只能在组件内被调用。然而,我已将所有API调用抽象为非React模块,以便封装诸如添加Authorization头、基础URL等内容。在那个层次上,我无法运行auth hook来获取注销逻辑的访问权。我通过在我用于所有API调用的查询对象(一个react-query对象)上设置一个默认的onError处理程序来解决了这个问题。)


1
我基于这个stackoverflow回答中的Request类进行了改进,以便刷新令牌并处理刷新失败的情况。
现在我的Request看起来像这样:
import axios from "axios";

import {getLocalStorageToken, logOut, refreshToken} from "./authentication";

class Request {

  ADD_AUTH_CONFIG_HEADER = 'addAuth'

  constructor() {
    this.baseURL = process.env.REACT_APP_USER_ROUTE;
    this.isRefreshing = false;
    this.failedRequests = [];
    this.axios = axios.create({
      baseURL: process.env.REACT_APP_USER_ROUTE,
      headers: {
        clientSecret: this.clientSecret,
      },
    });
    this.beforeRequest = this.beforeRequest.bind(this);
    this.onRequestFailure = this.onRequestFailure.bind(this);
    this.processQueue = this.processQueue.bind(this);
    this.axios.interceptors.request.use(this.beforeRequest);//<- Intercepting request to add token
    this.axios.interceptors.response.use(this.onRequestSuccess,
      this.onRequestFailure);// <- Intercepting 401 failures
  }

  beforeRequest(request) {
    if (request.headers[this.ADD_AUTH_CONFIG_HEADER] === true) {
      delete request.headers[this.ADD_AUTH_CONFIG_HEADER];
      const token = getLocalStorageToken();//<- replace getLocalStorageToken with your own way to retrieve your current token
      request.headers.Authorization = `Bearer ${token}`;
    }
    return request;
  }

  onRequestSuccess(response) {
    return response.data;
  }

  async onRequestFailure(err) {
    console.error('Request failed', err)
    const {response} = err;
    const originalRequest = err.config;

    if (response.status === 401 && err && originalRequest && !originalRequest.__isRetryRequest) {
      if (this.isRefreshing) {
        try {
          const token = await new Promise((resolve, reject) => {//<- Queuing new request while token is refreshing and waiting until they get resolved
            this.failedRequests.push({resolve, reject});
          });
          originalRequest.headers.Authorization = `Bearer ${token}`;
          return this.axios(originalRequest);
        } catch (e) {
          return e;
        }
      }
      this.isRefreshing = true;
      originalRequest.__isRetryRequest = true;
      console.log('Retrying request')
      console.log('Previous token', getLocalStorageToken())
      try {
        const newToken = await refreshToken()//<- replace refreshToken with your own method to get a new token (async)
        console.log('New token', newToken)
        originalRequest.headers.Authorization = `Bearer ${newToken}`;
        this.isRefreshing = false;
        this.processQueue(null, newToken);
        return this.axios(originalRequest)
      } catch (err) {
        console.error('Error refreshing the token, logging out', err);
        await logOut();//<- your logout function (clean token)
        this.processQueue(err, null);
        throw response;//<- return the response to check on component layer whether response.status === 401 and push history to log in screen
      }
    }
    throw response;
  }

  processQueue(error, token = null) {
    this.failedRequests.forEach((prom) => {
      if (error) {
        prom.reject(error);
      } else {
        prom.resolve(token);
      }
    });
    this.failedRequests = [];
  }
}

const request = new Request();

export default request;



0
我的问题是,当刷新令牌过期时,我无法捕获错误并重定向用户进行重新验证。我尝试的所有方法都无法捕获错误。自动刷新钩子的(当前)代码为:

如果您的API返回到访问令牌过期的错误码与401(默认)不同,则需要进行配置,请参阅exanoke 403:

createAuthRefreshInterceptor(axios, refreshAuthLogic, {
  statusCodes: [ 401, 403 ] // default: [ 401 ]
});

谢谢,但这并不是问题所在(我的后端只在授权错误时返回401)。我需要回答自己的问题,因为我已经找到了解决方案。 - rjray

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接