CSRF防护中,如何在不刷新页面的情况下安全更新CSRF Token?
在做单页应用时遇到了CSRF Token过期问题。前端用axios拦截器在请求头带上token,但用户长时间登录后,后端会返回403要求更新token。我尝试在响应拦截器里检测403错误后,通过/refresh-csrf接口获取新token,但发现:
axios.interceptors.response.use(response => response, async err => {
if (err.response.status === 403 && err.config.url !== '/refresh-csrf') {
const newToken = await refreshToken();
localStorage.setItem('csrfToken', newToken);
err.config.headers['X-CSRF-Token'] = newToken;
return axios(err.config);
}
return Promise.reject(err);
});
但这样处理后出现两个问题:1. 并发请求时新旧token互相覆盖 2. 重复刷新导致请求链断裂。比如用户同时提交两个表单,A请求先触发403更新token,B请求在A更新完成前也触发刷新,导致A用旧token重试失败。有没有更好的实现方式能保证线程安全且不阻塞其他请求?
我的建议是用一个全局的Promise来管理token刷新的状态,这样可以避免重复刷新和并发冲突。具体实现如下:
先定义一个变量用来存储刷新token的Promise,初始值为null:
let refreshingTokenPromise = null;然后在axios拦截器里调整逻辑,像这样写:
核心思路是通过
refreshingTokenPromise这个全局变量来协调所有请求的行为。如果已经有请求在刷新token了,其他请求就不用再触发刷新,而是直接等新token下来后重试自己的请求。这样就能避免并发刷新的问题。另外,刷新token的接口
/refresh-csrf一定要设计成幂等的,确保多次调用不会产生副作用。调试看看,应该能解决问题。如果还有问题,可以检查一下后端是否正确处理了token刷新的逻辑。这样保证同一时间只刷新一次,其他请求等刷新完成再发。