通过AXIOS拦截器请求自动刷新访问令牌

fv2wmkja  于 12个月前  发布在  iOS
关注(0)|答案(4)|浏览(159)

我的React应用程序使用Axios进行API请求。
Axios通过请求拦截器将访问令牌添加到每个API请求的头部。我想在发送当前请求之前检查到期日期。如果访问令牌到期,Axios应该进行刷新操作,更改存储中的访问令牌,然后使用新令牌从应用程序发出第一个请求。
对于刷新和其他API操作,我使用Redux Toolkit Slices。
我没有任何情况下刷新请求拦截器工作正常。我找到了解决方案与响应拦截器,但它不是我的方式。
如何在请求拦截器中刷新访问令牌?
这是我的Axios示例:

export const createAPI = (): AxiosInstance => {
  const api = axios.create({
    baseURL: getAPIURL(),
    timeout: REQUEST_TIMEOUT,
    withCredentials: true
  });

  api.interceptors.request.use(
    async (config: InternalAxiosRequestConfig) => {
      const accessToken = store.getState().user.accessToken;

      if (accessToken && config.headers) {
        config.headers['authorization'] = `Bearer ${accessToken}`;
      }
      
      // todo Check access token and refresh if it expired

      if (!config.data) return config;
      
      config.data = adaptFromClientToServer(config.data);

      return config;
    }
  );

  api.interceptors.response.use(
    (response) => {
      if (!response.data) return response;
      
      response.data = adaptFromServerToClient(response.data);
      
      return response;
    }
  );

  return api;
};

字符串

sz81bmfz

sz81bmfz1#

在请求拦截器中检查token是否过期时,需要注意的是,如果token在前端没有过期,并不一定意味着它在后端仍然有效。前端和后端系统之间可能存在时间延迟。为了确保token的有效性,建议在请求拦截器和响应拦截器上同时实现解决方案。
此外,在API URL为公共URL的场景中,如果不需要令牌,则无需发起令牌刷新。在这种情况下,跳过刷新令牌API调用是有效的。这种方法有助于优化令牌管理,避免在令牌未使用时执行不必要的刷新操作。
不推荐仅依靠请求拦截器来检查刷新令牌。

gwbalxhn

gwbalxhn2#

你的API模块看起来在正确的轨道上, Package 常见的API逻辑(随着时间的推移变得越来越复杂),并简化视图和视图模型中的代码。我的答案是关于可移植和弹性API客户端的设计选择。

令牌响应

令牌响应总是包含expires_in字段。还请注意,访问令牌可能是不透明/不可读令牌,而不是JWT:

{
   "token_type":"bearer",
   "access_token":"_0XBPWQQ_c0f0677f-5aa9-4c4e-a3d4-c0f53db4037a",
   "refresh_token":"_1XBPWQQ_197003c2-704f-4475-923c-2b40e5f5d696",
   "scope":"openid profile",
   "expires_in":900
}

字符串

刷新选择

前端可以保留expires_in字段和发布时间,并通过以下方法之一管理刷新:
1.如果当前时间指示令牌即将到期,则在API请求之前进行刷新
1.当令牌接近到期时,刷新后台计时器
1.如果API返回401状态,则执行刷新

弹性

访问令牌可能会因过期以外的原因而失败。其中一个原因可能是吊销。在某些设置中,它可能是由基础设施事件(如令牌签名密钥续订或负载平衡故障转移)引起的。因此,请始终执行选项3(主要行为),并根据您的偏好将其与选项1或2(优化)结合使用。

一致性

如果多个视图同时调用API,则令牌刷新应该同步。这导致了一种设计,即放弃promise,但只在第一个视图上发出刷新请求,然后使用新的访问令牌恢复所有API请求。可以使用这样的代码:

public async synchronizedRefresh(): Promise<void> {
    await this._concurrencyHandler.execute(this._performTokenRefresh);
}

更多信息

有关这些概念的更多信息,请参阅我的这些资源。我的代码不使用Axios拦截器,但令牌刷新代码很容易集成到您的代码中。

8e2ybdfx

8e2ybdfx3#

这就是你的处理方式

axios.interceptors.request.use(
  async (config) => {
    const token = store.getState().user.accessToken;
    if (token) {
      config.headers.Authorization = `Bearer ${token}`;
      // decode the token and check if (token.exp < Date.now() / 1000)
      if (isTokenExpired(token)) {
          // Call your refresh token endpoint
          // you have to implement an endpoint to get the refresh token
          // if you use 3rd party auth servers, you make requests to their /refreh enpoints
          const newToken = await refreshToken();
          if (newToken) {
            // Token refreshed, update the Authorization header with the new token
            config.headers.Authorization = `Bearer ${newToken}`;
            // Retry the original request
          }    
        } 
      }
    }
    return config;
  },
  (error) => {
    return Promise.reject(error);
  }
);

字符串

rggaifut

rggaifut4#

您可以使用jose(或类似的)库从令牌中提取exp声明。它是一个数字日期,您可以轻松地将其与当前时间进行比较(减去几秒以考虑潜在的延迟)。使用Promise处理多个请求

let refreshingPromise = null;

api.interceptors.request.use(async (config) => {
    if (refreshingPromise) {
        await refreshingPromise;
    }

    let accessToken = store.getState().user.accessToken;
    const claims = jose.decodeJwt(accessToken);
    const expiration = new Date(claims.exp * 1000);
    const now = new Date();
    now.setSeconds(now.getSeconds() - 10);

    if (now > expiration) {
        if (!refreshingPromise) {
            refreshingPromise = // your refresh logic
        }
        await refreshingPromise;
        accessToken = store.getState().user.accessToken; // Update the access token
        refreshingPromise = null;
    }

    if (accessToken && config.headers) {
        config.headers['authorization'] = `Bearer ${accessToken}`;
    }

    if (config.data) {
        config.data = adaptFromClientToServer(config.data);
    }

    return config;
});

字符串

相关问题