Nuxt中间件如何正确访问Vuex Store实现用户编辑权限拦截
报错根因定位
- 报错
Cannot read property 'uid' of null的核心诱因是执行时机不匹配,并非无法在中间件中访问Vuex Store:- 项目的Vuex模块中
currentUser初始值为null,这个值完全依赖异步的onAuthStateChangedAction回调(由认证SDK的状态监听触发)完成赋值,属于典型的异步流程。 - Nuxt中间件的执行时机远早于页面组件实例化:不管是首次页面加载还是客户端路由跳转,页级中间件都会在路由解析完成、页面组件渲染前执行完成。此时如果异步认证流程还未触发、没有提交
SET_AUTH_USER变更Store,store.state.user.currentUser就会保持初始的null值,直接读取其uid属性自然抛出空引用错误——哪怕把重定向逻辑注释掉,只要存在直接读取currentUser.uid的代码行,就会触发报错。 - 同页面计算属性可以正常获取
uid,是因为计算属性具备响应式特性:等异步认证流程完成、Store中的currentUser被更新后,计算属性会自动同步拿到最新值,和中间件执行阶段的空状态处于完全不同的时间节点,不存在逻辑矛盾。
- 项目的Vuex模块中
可落地的修复方案
第一步:补充空值防御,消除基础报错
在中间件逻辑中不要直接对currentUser做链式属性读取,先对值做存在性判断,哪怕后续逻辑还有优化,也能先避免空引用崩溃:
middleware({ store, params, redirect }) { const currentUser = store.state.user.currentUser // 无登录用户时直接重定向,避免后续读取uid报错 if (!currentUser) return redirect('/') // 路由参数uid与当前登录用户不匹配时重定向,拦截越权访问 if (currentUser.uid !== params.uid) return redirect('/') }
第二步:解决执行时机差,拿到准确的认证状态
仅做空值防御还不够——因为中间件执行时,认证状态大概率还没完成初始化,会把所有合法访问的用户也错误重定向走。需要调整逻辑,等认证状态初始化完成后再做越权校验:
- 先在Vuex的user模块中增加
authReady状态,用来标记认证流程是否初始化完成:export const state = () => ({ currentUser: null, authReady: false // 新增:标记认证初始化状态 }) export const mutations = { SET_AUTH_USER(state, payload) { state.currentUser = payload }, // 新增:更新认证准备状态 SET_AUTH_READY(state, value) { state.authReady = value } } export const actions = { async onAuthStateChangedAction({ commit, dispatch }, { authUser }) { try { if (authUser && authUser.emailVerified) { // 保留原有用户信息赋值逻辑 const { uid, email, emailVerified, displayName /* 其余原有解构字段保持不变 */ } = authUser commit('SET_AUTH_USER', { uid, email, emailVerified, displayName }) await dispatch('getUserProfile', authUser) } else { // 未登录/未验证时重置currentUser commit('SET_AUTH_USER', null) } } catch (error) { console.error('Error with Auth State observer: ', error) } finally { // 不管认证流程成功/失败,都标记初始化完成,释放路由等待 commit('SET_AUTH_READY', true) } } } - 将中间件改为异步形式,等待认证初始化完成后再做校验。Nuxt中间件本身支持返回Promise,只有当Promise resolve后,路由才会继续向下渲染:
async middleware({ store, params, redirect }) { // 等待认证状态初始化完成 while (!store.state.user.authReady) { // 小延迟轮询,避免阻塞事件循环 await new Promise(resolve => setTimeout(resolve, 50)) } const currentUser = store.state.user.currentUser // 未登录重定向 if (!currentUser) return redirect('/') // 越权访问重定向 if (currentUser.uid !== params.uid) return redirect('/') }
注意事项
- 因为是客户端层面的拦截,不需要服务端渲染阶段执行校验,可以通过环境判断把这个中间件的运行范围限制为客户端,避免服务端执行时拿不到浏览器端的认证状态做无效判断。
- 客户端路由拦截仅用于提升用户体验,不能作为最终的安全防线:用户可以通过手动修改前端状态绕过拦截,最终的越权校验必须在业务接口层做二次判断,才能从根源上禁止越权编辑的行为。
内容的提问来源于stack exchange,提问作者redshift
相关产品推荐
相关产品推荐

