プロジェクトのフロントエンドでノンセンシングリフレッシュトークンを実現する方法!

少し前に問題が発生しました。オンラインプラットフォームを使用しているときに、ユーザーが突然ログインする必要があり、ユーザーエクスペリエンスが非常に悪くなることがありますが、その時は良いアイデアがなかったため、保留されました。 ; 散在する情報を調べたり考えたりする時間を経て、最終的にこの問題を解決したので、皆さんと共有しましょう。

環境

  1. Axios V1.3.2が必要です。
  2. プラットフォームはJWT(JSON Web Tokens)ユーザーログイン認証を採用しています。
    (拡張: JWT は、リクエストが信頼できるクライアントからのものであることをバックグラウンドに知らせる認証メカニズムです。より詳細な情報については、関連情報を自分でクエリできます)

問題現象

オンライン ユーザーが使用している場合、突然ログイン ページにジャンプし、再度ログインする必要がある場合があります。

理由

  1. ログイン ページへの突然のジャンプは、現在のトークンの有効期限が切れたため、リクエストが失敗します。失敗したリクエストによって返されたステータス コード 401 はaxios、応答インターセプトaxiosInstance.interceptors.response.useで処理され、tokenこの時点で失敗がわかっているため、ログイン ページにジャンプし、ユーザーが再度ログインできるようにします。
  2. プラットフォームの現在のロジックでは、token有効期限が切れていない期間内であれば、ユーザーはログイン操作を行わずにプラットフォームにログインしてホームページに直接アクセスできるため、ユーザーがプラットフォームを開くと、有効期限が切れていないため、次のような現象が発生しますtoken。今回は、ユーザーは直接ホームページに入り、その他の操作を実行します。しかし、ユーザーの操作の過程でtoken突然失敗し、このとき突然ログイン ページにジャンプすることになり、ユーザー エクスペリエンスに重大な影響を及ぼします。
    (注: 現在、オンライン プロジェクトでは大きなデータ画面や一部のリアルタイム データ表示があるため、ユーザーが操作を行わずに長時間大きな画面に留まり、リアルタイム データを表示する状況が発生します)

エントリーポイント

  1. ユーザーに気付かれずにタイムリーに更新するにはどうすればよいですかtoken?
  2. 無効化の場合token、複数のエラー リクエストが存在する可能性があります。無効化がtoken更新されたときに、失敗した複数のリクエストを再送信するにはどうすればよいですか?

作業手順

好了!经过了一番分析后,我们找到了问题的所在,并且确定了切入点;那么接下来让我们实操,将问题解决掉。
前要:
1、我们仅从前端的角度去处理。
2、后端提供了两个重要的参数:accessToken(用于请求头中,进行鉴权,存在有效期);refreshToken(刷新令牌,用于更新过期的 accessToken,相对于 accessToken 而言,它的有效期更长)。

1、处理 axios 响应拦截

注:在我实际的项目中,accessToken 过期后端返回的 statusCode 值为 401,需要在axiosInstance.interceptors.response.useerror回调中进行逻辑处理。

// 响应拦截
axiosInstance.interceptors.response.use(
  (response) => {
    return response;
  },
  (error) => {
    let {
      data, config
    } = error.response;
    return new Promise((resolve, reject) => {
      /**
       * 判断当前请求失败
       * 是否由 toekn 失效导致的
       */
      if (data.statusCode === 401) {
         /**
         * refreshToken 为封装的有关更新 token 的相关操作 
         */
        refreshToken(() => {
          resolve(axiosInstance(config));
        });
      } else {
        reject(error.response);
      }
    })
  }
)
  1. 我们通过判断statusCode来确定,是否当前请求失败是由token过期导致的;
  2. 使用 Promise 处理将失败的请求,将由于 token 过期导致的失败请求存储起来(存储的是请求回调函数,resolve 状态)。理由:后续我们更新了 token 后,可以将存储的失败请求重新发起,以此来达到用户无感的体验

补充:

现象:在我过了几天登录平台的时候发现,refreshToken过期了,但是没有跳转到登录界面 原因
1、当refreshToken过期失效后,后端返回的状态码也是 401
2、发起的更新token的请求采用的也是处理后的axios,因此响应失败的拦截,对更新请求同样适用
问题:
这样会造成,当refreshToken过期后,会出现停留在首页,无法跳转到登录页面。
解决方法
针对这种现象,我们需要完善一下axios中响应拦截的逻辑。

axiosInstance.interceptors.response.use(
  (response) => {
    return response;
  },
  (error) => {
    let {
      data, config
    } = error.response;
    return new Promise((resolve, reject) => {
      /**
       * 判断当前请求失败
       * 是否由 toekn 失效导致的
       */
      if (
        data.statusCode === 401 &&
        config.url !== '/api/token/refreshToken'
      ) {
        refreshToken(() => {
          resolve(axiosInstance(config));
        });
      } else if (
        data.statusCode === 401 &&
        config.url === '/api/token/refreshToken'
      ) {
        /**
         * 后端 更新 refreshToken 失效后
         * 返回的状态码, 401
         */
        window.location.href = `${HOME_PAGE}/login`;
      } else {
        reject(error.response);
      }
    })
  }
)

2、封装 refreshToken 逻辑

要点:

  1. 存储由于token过期导致的失败的请求。
  2. 更新本地以及axios中头部的token
  3. refreshToken 刷新令牌也过期后,让用户重新登录。
// 存储由于 token 过期导致 失败的请求
let expiredRequestArr: any[] = [];

/**
 * 存储当前因为 token 失效导致发送失败的请求
 */
const saveErrorRequest = (expiredRequest: () => any) => {
  expiredRequestArr.push(expiredRequest);
}

// 避免频繁发送更新 
let firstRequre = true;
/**
 * 利用 refreshToken 更新当前使用的 token
 */
const updateTokenByRefreshToken = () => {
  firstRequre = false;
  axiosInstance.post(
    '更新 token 的请求',
  ).then(res => {
    let {
      refreshToken, accessToken
    } = res.data;
    // 更新本地的token
    localStorage.setItem('accessToken', accessToken);
    // 更新请求头中的 token
    setAxiosHeader(accessToken);
    localStorage.setItem('refreshToken', refreshToken);

    /**
     * 当获取了最新的 refreshToken, accessToken 后
     * 重新发起之前失败的请求
     */
    expiredRequestArr.forEach(request => {
      request();
    })
    expiredRequestArr = [];
  }).catch(err => {
    console.log('刷新 token 失败err', err);
    /**
     * 此时 refreshToken 也已经失效了
     * 返回登录页,让用户重新进行登录操作
     */
    window.location.href = `${HOME_PAGE}/login`;
  })
}

/**
 * 更新当前已过期的 token
 * @param expiredRequest 回调函数,返回由token过期导致失败的请求
 */
export const refreshToken = (expiredRequest: () => any) => {
  saveErrorRequest(expiredRequest);
  if (firstRequre) {
    updateTokenByRefreshToken();
  }
}

总结

经过一波分析以及操作,我们最终实现了实际项目中的无感刷新token,最主要的是有效避免了:用户在平台操作过程中突然要退出登录的现象(尤其是当用户进行信息填写,突然要重新登录,之前填写的信息全部作废,是很容易让人发狂的)。
其实回顾一下,技术上并没有什么难点,只是思路上自己是否能够想通、自洽。人是一棵会思想的芦苇,我们要有自己的思想,面对问题,有自己的思考。
希望我们能在技术的路上走的越来越远,与君共勉!!!

おすすめ

転載: juejin.im/post/7254572706536734781