先记录下
访问令牌 & 刷新令牌
双令牌机制主要用于增加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来进行修改。例如,你可能需要在其他地方存储和获取访问令牌和刷新令牌,或者你可能需要使用不同的方式来处理错误和队列。