Nuxt.js/Vue.js中Vuex存储的Axios认证Token刷新后重置问题排查
首先,咱们来梳理下你遇到的核心问题:页面刷新后,虽然nuxtServerInit调用了fetch,但Token没有正确同步到客户端的Axios实例,同时服务端获取Cookie的方式也存在上下文不一致的风险。下面是具体的问题点和修复方案:
问题1:服务端获取Cookie的上下文错误
在nuxtServerInit中你拿到了当前请求的req,但后续的fetch和fetchToken并没有传递这个req,而是用this.app.context.req来获取Cookie。在服务端渲染场景下,每个请求的上下文是独立的,this.app.context可能指向的不是当前请求的上下文,导致无法正确获取到当前用户的Token Cookie。
修复方式:传递请求上下文
修改nuxtServerInit、fetch和fetchToken,把req参数逐层传递下去:
export const actions = { async nuxtServerInit ({ dispatch }, { req }) { // 将req传递给fetch action await dispatch('fetch', { req }) }, // Fetch Token async fetchToken ({ dispatch }, { req }) { let token // 服务端直接使用传入的req获取Cookie,客户端用document.cookie const cookieStr = process.browser ? document.cookie : req.headers.cookie const cookies = Cookie.parse(cookieStr || '') || {} token = cookies['ccmsToken'] if (token) { await dispatch('updateToken', token) } }, // Fetch async fetch ({ commit, dispatch }, { req, username = 'admin', endpoint = 'http://localhost:8000/api/user' } = {}) { // 将req传递给fetchToken await dispatch('fetchToken', { req }) if (!this.state.token) { return } try { const data = await this.$axios.$get(`${endpoint}?username=${username}`) commit('setUser', data) } catch (e) { await dispatch('reset') } }, // Login async login ({ dispatch }, { fields, endpoint = 'http://localhost:8000/api/login' } = {}) { try { const data = await this.$axios.$post(endpoint, fields) if (data.success) { await dispatch('updateToken', data.token) // 注意这里调用fetch要传对象格式的参数 await dispatch('fetch', { username: data.user.username }) } else { throw new Error(data.message) } } catch (error) { if (error.response?.status === 401) { throw new Error('Bad credentials') } throw error } }, // ...其他actions保持不变 }
问题2:客户端Axios实例未同步Token
页面刷新时,服务端的nuxtServerInit会把Token存入store并设置服务端的Axios Token,但客户端的Axios实例是独立的,不会自动同步这个Token。导致客户端发起请求时没有带上认证头。
修复方式:客户端初始化时设置Axios Token
创建一个客户端专属的Nuxt插件,在客户端初始化时从store中读取Token并设置到Axios实例:
- 在
plugins/axios-token.js中编写插件代码:
export default function ({ store, $axios }) { // 仅在客户端运行,从store获取Token并设置到Axios if (process.browser && store.state.token) { // 第二个参数是认证scheme,根据你的后端要求调整,比如'Bearer' $axios.setToken(store.state.token, '') } }
- 在
nuxt.config.js中注册这个插件:
export default { // ...其他配置 plugins: [ // 其他插件... { src: '~/plugins/axios-token.js', mode: 'client' } // 指定仅在客户端运行 ] }
问题3:登出时Cookie过期时间设置错误
在updateToken的服务端逻辑中,当Token为null时,你设置的过期时间是明天,这会导致Cookie不会被删除,应该设置为过去的时间:
// ...updateToken中的服务端逻辑 } else { // ...Server let params = { path: '/' } // 建议明确设置path,避免Cookie作用域问题 if (!token) { const date = new Date() date.setDate(date.getDate() - 1) // 设置为昨天,让Cookie立即过期 params.expires = date params.maxAge = -1 // 额外设置maxAge确保兼容性 } this.app.context.res.setHeader('Set-Cookie', Cookie.serialize('ccmsToken', token || '', params)) }
额外优化点
this.$axios.setToken(token, '')中的第二个参数是认证scheme,如果你的后端要求使用Bearer,建议改为$axios.setToken(token, 'Bearer'),避免认证失败。- 在
fetch中拼接URL参数时,建议使用URLSearchParams来避免编码问题:
const params = new URLSearchParams({ username }) const data = await this.$axios.$get(endpoint, { params })
完成这些修改后,刷新页面时,服务端会正确从请求Cookie中获取Token并同步到store,客户端初始化时也会把store中的Token设置到Axios实例,这样就能保证刷新后请求依然带有认证头了。
内容的提问来源于stack exchange,提问作者Karolis Stakėnas

