先记录下
访问令牌 & 刷新令牌
双令牌机制主要用于增加Web应用程序的安全性。这种机制通常包括两种类型的令牌:访问令牌(Access Token)
和刷新令牌(Refresh Token)
。
-
访问令牌:访问令牌是用户完成身份验证后接收的令牌,它包含了用户的身份信息和权限信息。访问令牌—般会有一个较短的过期时间,例如15分钟或1小时。
-
刷新令牌:当访问令牌过期后,用户可以使用刷新令牌来获取新的访问令牌,而无需再次输入用户名和密码。刷新令牌—般会有一个较长的过期时间,例如24小时或7天。
双令牌机制
实施双令牌机制的基本步骤如下:
-
用户身份验证:当用户通过用户名和密码登录时,服务器会验证用户的身份。如果验证成功,服务器会生成访问令牌和刷新令牌,并将它们返回给客户端。
-
使用访问令牌:客户端收到令牌后,会将访问令牌存储在本地。然后,客户端在每次向服务器发送请求时,都会在请求头中携带访问令牌。
-
访问令牌过期:当访问令牌过期后,服务器会拒绝来自客户端的请求,并返回一个特定的错误码,例如401(未授权)。
-
使用刷新令牌:当客户端收到401错误码时,它会使用刷新令牌向服务器请求新的访问令牌。服务器会验证刷新令牌,如果验证成功,服务器会生成新的访问令牌和刷新令牌,并将它们返回给客户端。
-
刷新令牌过期:如果刷新令牌也过期了,客户端就需要重新进行身份验证。
双令牌机制的优点
双令牌机制的优点是,即使访问令牌被盗,攻击者也只能在短时间内使用,因为访问令牌很快就会过期。
此外,即使刷新令牌也被盗,服务器也可以在用户下次使用刷新令牌时,发现有两个不同的客户端试图使用同一个刷新令牌,从而废除该刷新令牌(这点存疑?)
疑问
疑问1:访问令牌过期,访问请求失败,如何无感使用刷新令牌获取访问令牌,并处理失败请求
那访问令牌过期时,发送的那个请求,已经失败了,再拿刷新令牌获取新的访问令牌,怎样做到用户无感?
当访问令牌过期,服务器通常会返回401 Unauthorized状态码。在这种情况下,可以在客户端设置一个拦截器来处理这个问题,使用户感知不到令牌的刷新过程。这样,每当客户端收到401状态码,它可以自动使用刷新令牌去获取新的访问令牌,然后再次尝试发送失败的请求。
以下是一个基本的处理流程:
- 客户端发送请求。
- 服务器检查访问令牌,发现它已经过期,然后返回401状态码。
- 客户端拦截器捕获到401错误,然后使用刷新令牌向服务器请求新的访问令牌。
- 如果刷新令牌有效,服务器返回新的访问令牌。
- 客户端拦截器接收到新的访问令牌,更新本地存储的访问令牌。
- 客户端拦截器重新发送原来失败的请求。
- 服务器接收到新的请求和新的访问令牌,然后处理请求。
注意,这个过程需要处理并发请求的情况。如果有多个请求同时返回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来进行修改。例如,你可能需要在其他地方存储和获取访问令牌和刷新令牌,或者你可能需要使用不同的方式来处理错误和队列。