Vue3路由守卫结合Firebase Auth实现全局鉴权的正确方案
问题根源
你现有代码的核心问题出在onAuthStateChanged的使用方式错误,直接导致守卫鉴权失效:
onAuthStateChanged是事件监听器,不是可以同步返回结果、支持await等待的Promise方法,你在守卫中调用它之后,没有给路由守卫返回任何明确的放行/跳转指令,路由会默认直接放行进入目标页面- 你写的
return {name: 'Signin'}是返回给onAuthStateChanged的回调函数的,不是返回给Vue Router的beforeEach守卫,守卫根本接收不到这个跳转指令 - 每次路由跳转都会重复注册新的Auth状态监听器,既会造成内存泄漏,也会导致状态判断逻辑乱序
正确实现方案
第一步:封装统一的Auth状态获取方法
不要在路由守卫里重复注册状态监听器,全局只注册一次监听,封装可等待的用户状态获取方法,解决首次加载Auth状态异步的问题:
// src/router/auth.js import { getAuth, onAuthStateChanged } from "firebase/auth"; import app from "../firebase/config"; // 引入你自己初始化的Firebase App实例 const auth = getAuth(app); let isAuthInitialized = false; let currentUser = null; // 全局只注册一次Auth状态监听 onAuthStateChanged(auth, (user) => { currentUser = user; isAuthInitialized = true; }); // 封装等待Auth初始化完成、获取当前用户的方法 export function getCurrentUser() { return new Promise((resolve) => { // 已经完成初始化直接返回缓存的用户状态 if (isAuthInitialized) { resolve(currentUser); return; } // 未初始化时等待首次状态返回,拿到结果立刻取消临时监听 const unsubscribe = onAuthStateChanged(auth, (user) => { unsubscribe(); resolve(user); }); }); }
第二步:编写正确的全局路由守卫
守卫中必须等待用户状态返回后,再根据路由配置判断是放行还是跳转:
// src/router/index.js import { createRouter, createWebHistory } from "vue-router"; import { getCurrentUser } from "./auth"; // 路由配置,通过meta标记是否需要鉴权 const routes = [ { path: "/signin", name: "Signin", component: () => import("../views/Signin.vue"), meta: { requireAuth: false } }, { path: "/dashboard", name: "Dashboard", component: () => import("../views/Dashboard.vue"), meta: { requireAuth: true } // 受保护路由打标记 }, // 其余业务路由... ]; const router = createRouter({ history: createWebHistory(import.meta.env.BASE_URL), routes }); // 全局前置鉴权守卫 router.beforeEach(async (to) => { // 先等待Auth状态加载完成,拿到确定的用户信息 const user = await getCurrentUser(); // 访问受保护页面但未登录,直接跳转登录页 if (to.meta.requireAuth && !user) { return { name: "Signin" }; } // 已登录用户访问登录页,直接跳转控制台/首页,避免重复登录 if (to.name === "Signin" && user) { return { name: "Dashboard" }; } // 其余场景直接放行 return true; }); export default router;
彻底消除内容闪烁的优化
仅靠路由守卫仍然可能在应用首次冷启动时出现极短的内容闪现,可以配合全局加载态彻底阻断未授权内容的渲染:
- 在根组件
App.vue中增加全局初始化加载逻辑,Auth状态确认完成前不渲染路由出口:
<!-- src/App.vue --> <script setup> import { ref } from "vue"; import { getCurrentUser } from "./router/auth"; const isAppLoading = ref(true); // 应用启动时先等待Auth状态校验完成 getCurrentUser().then(() => { isAppLoading.value = false; }); </script> <template> <!-- 加载中只显示全局loading,不渲染任何路由内容 --> <div v-if="isAppLoading" class="global-loading"> 应用加载中... </div> <router-view v-else /> </template>
- 所有路由权限统一通过
meta字段标记,鉴权逻辑全部收口在全局守卫中,不要在单个组件内分散写登录校验,避免逻辑不一致导致的漏判 - 如果需要扩展角色、权限点校验,只需要在路由
meta中增加对应标记,在守卫拿到user对象后追加判断逻辑即可,不需要修改业务组件代码
内容的提问来源于stack exchange,提问作者user9741470
相关产品推荐
相关产品推荐

