繁体   English   中英

JWT 使用 axios/axios-auth-refresh 刷新令牌流的问题

[英]Problem with JWT Refresh Token Flow with axios/axios-auth-refresh

(我在这里阅读了许多类似的问题,并且大多数/所有人都说要使用不同的 axios 实例来处理刷新令牌请求(与 API 请求相比)。但是,我不清楚这将如何工作,因为我正在使用axios-auth-refresh来自动刷新访问令牌。)

我正在为后端 API 请求使用基于 JWT 的身份验证流程的应用程序。 一般流程运行良好; 登录后,用户会获得一个长期刷新令牌和短期访问令牌。 使用 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()块中错误 object 的转储。

此处的实际文件位于 GitHub 上,尽管它与上面的工作版本略有不同。 主要是,在 GH 版本中,钩子调用axios.post("token").then(...)在上面我进行更明确的调用以添加skipAuthRefresh参数。 添加它让我在控制台中获得了更详细的错误跟踪,但我仍然没有通过catch()捕捉到 401 响应。

我已经尝试了所有我能想到的东西......有什么东西会跳出来作为我缺少的东西吗?

兰迪

(编辑以确保 GitHub 链接指向有问题的文件版本。)

自发布此消息以来,我已经设法解决了这个问题并提出了一个可行的解决方案。

解决方案的关键实际上在于使用不同的 axios 实例来调用更新刷新令牌。 我创建了第二个模块来封装第二个 axios 实例,该实例不会获取由axios-auth-refresh模块创建的拦截器。 在解决了最初导致的一些无意的循环依赖问题后,我发现当刷新令牌本身过时或丢失时,我可以看到 axios 抛出异常。

(有趣的是,这导致了另一个问题:一旦我发现刷新令牌不再有效,我需要注销用户并让他们返回登录屏幕。因为这里的应用程序是一个 React 应用程序,所以身份验证正在使用只能在组件内调用的自定义钩子进行处理。但是,我已将所有 API 调用抽象到一个非 React 模块中,以便我可以封装诸如添加Authorization header、基础 ZE6B7031A8D2C45845 等. 在那个级别,我无法运行 auth 挂钩来访问注销逻辑。我通过在查询 object(一个反应查询对象)上放置一个默认的onError处理程序来解决这个问题,我用于所有 API 调用。)

我基于此 SO 答案中的Request class 来刷新令牌并处理刷新失败。

现在我的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;


我的问题是,当刷新令牌过期时,我无法捕获错误并将用户重定向以重新进行身份验证。 我尝试过的任何方法都无法捕捉到错误。 自动刷新挂钩的(当前)代码是:

如果访问令牌过期,您的 api 的返回码是什么?

如果它不同于 401(默认),则需要配置,请参阅 exanoke 403:

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

暂无
暂无

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM