先记录下

访问令牌 & 刷新令牌

双令牌机制主要用于增加Web应用程序的安全性。这种机制通常包括两种类型的令牌:访问令牌(Access Token)刷新令牌(Refresh Token)

  • 访问令牌:访问令牌是用户完成身份验证后接收的令牌,它包含了用户的身份信息和权限信息。访问令牌—般会有一个较短的过期时间,例如15分钟或1小时。
  • 刷新令牌:当访问令牌过期后,用户可以使用刷新令牌来获取新的访问令牌,而无需再次输入用户名和密码。刷新令牌—般会有一个较长的过期时间,例如24小时或7天。

双令牌机制

实施双令牌机制的基本步骤如下:

  1. 用户身份验证:当用户通过用户名和密码登录时,服务器会验证用户的身份。如果验证成功,服务器会生成访问令牌和刷新令牌,并将它们返回给客户端。
  2. 使用访问令牌:客户端收到令牌后,会将访问令牌存储在本地。然后,客户端在每次向服务器发送请求时,都会在请求头中携带访问令牌。
  3. 访问令牌过期:当访问令牌过期后,服务器会拒绝来自客户端的请求,并返回一个特定的错误码,例如401(未授权)。
  4. 使用刷新令牌:当客户端收到401错误码时,它会使用刷新令牌向服务器请求新的访问令牌。服务器会验证刷新令牌,如果验证成功,服务器会生成新的访问令牌和刷新令牌,并将它们返回给客户端。
  5. 刷新令牌过期:如果刷新令牌也过期了,客户端就需要重新进行身份验证。

双令牌机制的优点

双令牌机制的优点是,即使访问令牌被盗,攻击者也只能在短时间内使用,因为访问令牌很快就会过期。

此外,即使刷新令牌也被盗,服务器也可以在用户下次使用刷新令牌时,发现有两个不同的客户端试图使用同一个刷新令牌,从而废除该刷新令牌(这点存疑?)

疑问

疑问1:访问令牌过期,访问请求失败,如何无感使用刷新令牌获取访问令牌,并处理失败请求

那访问令牌过期时,发送的那个请求,已经失败了,再拿刷新令牌获取新的访问令牌,怎样做到用户无感?

当访问令牌过期,服务器通常会返回401 Unauthorized状态码。在这种情况下,可以在客户端设置一个拦截器来处理这个问题,使用户感知不到令牌的刷新过程。这样,每当客户端收到401状态码,它可以自动使用刷新令牌去获取新的访问令牌,然后再次尝试发送失败的请求。

以下是一个基本的处理流程:

  1. 客户端发送请求。
  2. 服务器检查访问令牌,发现它已经过期,然后返回401状态码。
  3. 客户端拦截器捕获到401错误,然后使用刷新令牌向服务器请求新的访问令牌。
  4. 如果刷新令牌有效,服务器返回新的访问令牌。
  5. 客户端拦截器接收到新的访问令牌,更新本地存储的访问令牌。
  6. 客户端拦截器重新发送原来失败的请求。
  7. 服务器接收到新的请求和新的访问令牌,然后处理请求。

注意,这个过程需要处理并发请求的情况。如果有多个请求同时返回401错误,客户端应该只发送一次刷新令牌的请求,然后用获取到的新访问令牌重新发送所有失败的请求。

以上就是一个基本的处理过期访问令牌的流程,希望对你有所帮助。

现成的axios作为请求客户端,处理双令牌机制的源码实现

处理双令牌机制的Axios源码实现可以通过Axios的拦截器功能来实现。
以下是一个基本的示例:

import axios from 'axios';

// 创建axios实例
const instance = axios.create({
  baseURL: 'https://your-api-url',
  timeout: 5000
});

let isRefreshing = false; // 刷新令牌的标志
let failedQueue = []; // 存储因等待刷新令牌而挂起的请求

const processQueue = (error, token = null) => {
  failedQueue.forEach(prom => {
    if (error) {
      prom.reject(error);
    } else {
      prom.resolve(token);
    }
  });

  failedQueue = [];
};

instance.interceptors.response.use(
  response => response,
  error => {
    const originalRequest = error.config;

    if (error.response.status === 401 && !originalRequest._retry) {
      if (isRefreshing) {
        return new Promise(function(resolve, reject) {
          failedQueue.push({ resolve, reject });
        })
          .then(token => {
            originalRequest.headers['Authorization'] = 'Bearer ' + token;
            return instance(originalRequest);
          })
          .catch(err => {
            return Promise.reject(err);
          });
      }

      originalRequest._retry = true;
      isRefreshing = true;

      return new Promise(function(resolve, reject) {
        // 这里使用你的刷新令牌接口,返回新的访问令牌
        axios.get('/refresh-token')
          .then(({data}) => {
            instance.setToken(data.token);
            originalRequest.headers['Authorization'] = 'Bearer ' + data.token;
            processQueue(null, data.token);
            resolve(instance(originalRequest));
          })
          .catch(err => {
            processQueue(err, null);
            reject(err);
          })
          .finally(() => {
            isRefreshing = false;
          });
      });
    }

    return Promise.reject(error);
  }
);

export default instance;
  • 这段代码首先创建一个新的axios实例,并添加一个响应拦截器。
  • 当服务器返回401错误时,拦截器会检查是否正在刷新访问令牌。
  • 如果正在刷新,那么它会将请求添加到等待队列中,并返回一个Promise,该Promise将在新的访问令牌可用时解析。
  • 如果不在刷新,那么它会发送一个请求来刷新访问令牌,并将所有等待的请求添加到队列中。
  • 当新的访问令牌可用时,它会解析所有等待的Promise,并使用新的访问令牌重新发送原始请求。

注意:上述代码只是一个基本的示例,你可能需要根据你的应用程序的具体需求和服务器的API来进行修改。例如,你可能需要在其他地方存储和获取访问令牌和刷新令牌,或者你可能需要使用不同的方式来处理错误和队列。