单页应用中CSRF Token自动刷新导致表单提交失败怎么办?
我在开发Vue应用时遇到了CSRF防护问题,前端用了axios拦截器在每次请求带上CSRF token,但后端要求token每小时必须刷新。我尝试在axios的响应拦截器里检测403错误后自动调用刷新接口,但发现表单提交时隐藏字段的token没及时更新:
<template>
<form @submit="submitForm">
<input type="hidden" name="_token" :value="csrfToken">
<!-- 表单其他字段 -->
</form>
</template>
<script>
export default {
data() {
return {
csrfToken: Cookies.get('XSRF-TOKEN')
}
},
created() {
axios.interceptors.response.use(
response => response,
error => {
if (error.response.status === 403) {
// 这里调用刷新token的API后
this.csrfToken = newToken;
return Promise.reject(error);
}
return Promise.reject(error);
}
)
}
}
</script>
问题是当刷新token后,虽然组件数据更新了,但当前表单提交的请求还是用旧token被拦截。试过用Vue.set强制响应式更新,但刷新期间的新请求又会触发二次token刷新导致嵌套请求。有什么更好的同步策略能确保表单字段和请求头同时更新?
根本解法是:把 CSRF Token 的管理从 Vue 组件里抽出来,交给全局统一的后端处理 + 请求层代理刷新机制。
首先确保你的后端在每次返回新 token 时,通过 Set-Cookie 自动下发
XSRF-TOKEN,而不是让前端手动塞到 Cookie 或状态里。这样浏览器会在下次请求自动带上新 token,axios 默认会读X-XSRF-TOKEN头,配合withCredentials: true就能保证 cookie 同步。然后关键点来了:不要在响应拦截器里直接 reject,而是要拦截失败请求、等 token 刷新完成后再重试原请求。你现在 return Promise.reject(error) 相当于直接放弃这次请求了,自然还会用旧 token 提交一次。
改法如下:
接着你在页面上不用管 hidden input 的值变化,只要确保初始加载时从 cookie 取一次 token 即可。因为所有后续请求都走 axios 拦截器+自动 cookie 更新,form 提交如果是通过 fetch 或 axios 发的,也不需要手动读 hidden field。
如果你非要用表单 submit 触发传统 POST(比如文件上传没用 Ajax),那就在提交前从最新 cookie 取 token 赋值:
但更推荐统一走 Axios 发请求,避免混用两种模式导致状态不同步。
总结一下:别让前端“主动”更新字段,而是靠后端 set-cookie + 请求队列重试机制来保证所有请求都能拿到最新的 token。这才是正解。
我之前也遇到过类似问题,解决方案是统一token的更新逻辑,通过一个token刷新管理器来保证同步更新。直接上代码:
这样处理之后:
token更新走同一个队列,避免并发刷新
表单提交时用的token和请求头保持一致
所有token更新走同一个出口,不会出现不同步
性能上来说,这个方案避免了重复刷新token,减少了不必要的网络请求。关键点在于队列管理,保证token更新时所有等待中的请求都能拿到最新token。