Best practice for front-end refresh token

background needs

After the client login request is successful, the server encrypts the user information (such as user id) with a special algorithm and sends it to the user (that is, token) as a verification mark. The token is decrypted and verified. If it passes, the requested data will be returned to the client; otherwise, the request will fail.

problem performance

Generally, for the sake of security, the token will set an expiration time. After the expiration, the relevant interface cannot be requested. What should I do at this time? Is it to log out directly?

solution

Option One

The backend returns the expiration time, and the frontend judges the token expiration time and calls the refresh token interface

Option II

Write a timer to refresh the token interface regularly

third solution

Intercept in the request interceptor, determine whether the token is about to expire, and call the refresh token interface

Option 1 requires the backend to provide an interface, and the client requests to increase the bandwidth. In Option 2, the client keeps refreshing, resulting in a waste of resources. For a better user experience, we choose Option 3 to intercept and refresh the token in the interceptor.

Specific operation

After the login request is successful, a token and token expiration time will be returned. Each time the API is requested, the front end can first determine whether the token is about to expire or has expired. If so, request to refresh the token interface and successfully replace the original token. to re-initiate the request

...
// 是否有请求正在刷新token
window.isRefreshing = false;
// 被挂起的请求数组
let refreshSubscribers = [];
// 刷新请求(refreshSubscribers数组中的请求得到新的token之后会自执行,用新的token去请求数据)
function onRrefreshed(token) {
  refreshSubscribers.map(cb => cb(token));
}
// push所有请求到数组中
function subscribeTokenRefresh(cb) {
  refreshSubscribers.push(cb);
}
// http request拦截
axios.interceptors.request.use(config => {
  // 开启 progress bar
  NProgress.start();
  const meta = (config.meta || {});
  const isToken = meta.isToken === false;
  config.headers['Authorization'] = `Basic ${Base64.encode(`${website.clientId}:${website.clientSecret}`)}`;
  
  // 判断token是否将要过期
  if (isTokenExpired()) {
    // 判断是否正在刷新
    if (!window.isRefreshing) {
      // 将刷新token的标志置为true
      window.isRefreshing = true;
      // 发起刷新token的请求
      this.$store
          .dispatch("refreshToken")
          .then(res => {
            // 将标志置为false
            window.isRefreshing = false;
            // 执行数组里的函数,重新发起被挂起的请求
            onRrefreshed(res.data.data.token);
            /*执行onRefreshed函数后清空数组中保存的请求*/
            refreshSubscribers = [];
          })
          .catch(() => {
            /*将标志置为false*/
            window.isRefreshing = false;
          });
    }
    /*把请求(token)=>{....}都push到一个数组中*/
    let retry = new Promise((resolve) => {
      /*(token) => {...}这个函数就是回调函数*/
      subscribeTokenRefresh(() => {
        config.headers.Authorization = "Bearer " + getToken();
        /*将请求挂起*/
        resolve(config);
      });
    });
    return retry;
  }
  return config
}, error => {
  return Promise.reject(error)
});
...
复制代码

Guess you like

Origin juejin.im/post/7077757706743840782