Vue Router 权限路由:动态路由、导航守卫与白名单的工程落地

Vue Router 权限路由:动态路由、导航守卫与白名单的工程落地

后台管理系统最常见的"前端安全"问题不是加密,而是权限:

  • 登录后菜单如何按角色显示?
  • 直接输入 URL 能不能越权?
  • 刷新页面后动态路由丢失怎么办?

这篇给你一套能直接落地的权限路由模型:

  • 白名单:登录页/公共页直接放行
  • 登录态校验:无 token 一律回登录(带 redirect)
  • 动态路由注入:根据角色/菜单生成可访问路由
  • 刷新重建:刷新后重新拉用户信息并重新注入
  • 越权兜底:401/403/404 的处理一致且可解释

重点不是背 API,而是把"权限"做成一条可观测、可排查、可迭代的工程链路。


1. 先把权限模型说清楚(否则一定返工)

权限通常分三层,越往下越"硬":

  • 展示权限:菜单/按钮显隐
  • 访问权限:路由能不能进
  • 数据权限:接口返回什么数据(这层必须由后端保证)

这篇聚焦前两层:展示 + 访问。

一个重要结论:

  • 菜单只是 UI,路由守卫必须兜底访问权限

2. 你需要哪些基础能力

要做成可维护的权限路由,至少要有这些"权威信息源":

  • 登录态:token
  • 当前用户:userInfo(至少包含 role/permissions)
  • 路由来源:
    • 前端静态路由(公共页、基础布局)
    • 动态路由(按角色生成/按菜单生成)

以及一个"防重复注入"的标记:

  • isRoutesReady(动态路由是否已注入)

3. 基础结构:白名单 + 受保护路由

白名单路由(无需登录):

  • /login
  • /404

其它路由默认需要登录。


4. 导航守卫主流程(推荐:覆盖 90% 后台项目)

你可以用这个主流程覆盖 90% 项目:

  1. 未登录访问受保护路由 -> 跳登录(带 redirect)
  2. 已登录但用户信息未加载 -> 拉取用户信息
  3. 根据角色生成动态路由 -> 注入 router
  4. 再次进入目标路由(replace 避免重复历史)
  5. 访问无权限 -> 跳 403/404

伪代码(核心是"登录态校验 -> 用户信息 -> 注入动态路由 -> replace 继续前进"):

js 复制代码
router.beforeEach(async (to, from, next) => {
  const userStore = useUserStore()
  const permissionStore = usePermissionStore()

  const whiteList = ['/login', '/404']
  if (whiteList.includes(to.path)) return next()

  if (!userStore.token) {
    return next(`/login?redirect=${encodeURIComponent(to.fullPath)}`)
  }

  // 刷新后:token 在但内存 userInfo 丢了 -> 重新拉
  if (!userStore.userInfo) await userStore.fetchUserInfo()

  // 动态路由未就绪 -> 生成并注入,然后 replace 继续
  if (!permissionStore.isRoutesReady) {
    const dynamicRoutes = buildRoutesByRole(userStore.userInfo.role)
    dynamicRoutes.forEach((r) => router.addRoute(r))
    permissionStore.isRoutesReady = true
    return next({ ...to, replace: true })
  }

  if (!hasRoutePermission(to, userStore.userInfo.role)) {
    return next('/404')
  }

  next()
})

关键点:

  • replace: true 避免"注入路由后同一路由进两次"
  • 动态路由注入要在刷新后能重新执行

工程上的底线:

  • 守卫里不要发重复请求 :用 isRoutesReady 或类似标记保证只初始化一次
  • 不要用菜单显隐代替路由权限:UI 可以绕过,守卫不能

5. 动态路由怎么生成更好维护(两种路线)

常见两种策略:

5.1 前端写死路由表 + 按角色过滤

优点:

  • 简单、可控
  • 不依赖后端返回路由配置

适合中小型项目。

5.2 后端返回菜单/路由配置 + 前端映射组件

优点:

  • 菜单可配置
  • 多系统统一权限模型

风险:

  • 需要维护"组件映射表"
  • 后端字段变化会导致前端路由失效

工程建议:

  • 如果你是个人/小团队项目,优先 前端静态路由表 + 过滤,可控、可读、可测试
  • 如果你是多系统统一平台,再考虑 后端菜单配置,但要把"组件映射表 + 回滚策略"做扎实

6. 刷新后动态路由丢失怎么解决(必考点)

本质原因:

  • 浏览器刷新后,内存中的 router 动态注入状态会丢

解决策略:

  • 刷新后在守卫里重新拉取用户信息重新注入动态路由
  • token 持久化(localStorage)
  • userInfo 可以不持久化,刷新后重新请求更安全

一个更稳的实现方式:

  • token 持久化
  • userInfo 不强依赖持久化
  • 每次刷新进入守卫:
    • token 有 -> fetchUserInfo()(如果 userInfo 缺失)
    • routes 未注入 -> build + addRoute + replace: true

7. 越权与异常兜底:401/403/404 怎么选

建议兜底路径:

  • 401:登录失效 -> 回登录
  • 403:无权限 -> 403 页面(或 404 隐藏资源)
  • 404:路由不存在 -> 404 页面

如果你不想暴露资源存在性,可以把无权限也跳 404。

常用选择:

  • ToC 或对外平台:倾向把无权限也跳 404(隐藏资源存在性)
  • 内部后台:可以用 403 页面(更利于排查权限配置)

注意:

  • 401 通常由 Axios 拦截器统一处理(清理登录态并跳登录),路由守卫只做补兜底。

8. 常见坑与排查

  • 刷新白屏/404 :大概率是动态路由未注入就进入了目标路由
    • 解决:守卫里注入后 next({ ...to, replace: true })
  • 无限重定向:登录页没加入白名单,或守卫里无条件跳转
  • 菜单有但进不去:展示权限和访问权限未统一(菜单过滤了,路由守卫没放行 / 或相反)
  • 切换账号后菜单没刷新 :退出登录时没清理 isRoutesReady 与动态路由

9. 面试表达(30 秒讲清楚)

我一般会这样讲:

  • 我把权限分成展示权限和访问权限,访问权限由路由守卫兜底。
  • 守卫流程是:白名单放行 -> 无 token 回登录(带 redirect)-> 有 token 但 userInfo 缺失就拉用户信息 -> 根据角色生成动态路由并注入 -> replace 继续进入目标路由。
  • 刷新后动态路由会丢,所以守卫里要做重建,并用标记避免重复注入。
  • 无权限我会根据场景选择 403 或 404,并且 401 一般交给 Axios 拦截器统一处理。

10. 总结

  • 权限路由要保证"访问权限",不是只做菜单显示
  • 导航守卫:登录态校验 -> 拉用户信息 -> 注入动态路由 -> replace 进入目标路由
  • 刷新后路由丢失是正常现象,靠守卫重建即可
  • 401/403/404 做好兜底,体验和可维护性都会提升
相关推荐
小陈工1 天前
Python Web开发入门(十七):Vue.js与Python后端集成——让前后端真正“握手言和“
开发语言·前端·javascript·数据库·vue.js·人工智能·python
xiaotao1311 天前
第九章:Vite API 参考手册
前端·vite·前端打包
午安~婉1 天前
Electron桌面应用聊天(续)
前端·javascript·electron
彧翎Pro1 天前
基于 RO1 noetic 配置 robosense Helios 32(速腾) & xsense mti 300
前端·jvm
小码哥_常1 天前
解锁系统设置新姿势:Activity嵌入全解析
前端
之歆1 天前
前端存储方案对比:Cookie-Session-LocalStorage-IndexedDB
前端
哟哟耶耶1 天前
vue3-单文件组件css功能(:deep,:slotted,:global,useCssModule,v-bind)
前端·javascript·css
是罐装可乐1 天前
深入理解“句柄(Handle)“:从浏览器安全到文件系统访问
前端·javascript·安全
华科易迅1 天前
Vue如何集成封装Axios
前端·javascript·vue.js
康一夏1 天前
Next.js 13变化有多大?
前端·react·nextjs