Vue Router 权限路由:动态路由、导航守卫与白名单的工程落地
后台管理系统最常见的"前端安全"问题不是加密,而是权限:
- 登录后菜单如何按角色显示?
- 直接输入 URL 能不能越权?
- 刷新页面后动态路由丢失怎么办?
这篇给你一套能直接落地的权限路由模型:
- 白名单:登录页/公共页直接放行
- 登录态校验:无 token 一律回登录(带 redirect)
- 动态路由注入:根据角色/菜单生成可访问路由
- 刷新重建:刷新后重新拉用户信息并重新注入
- 越权兜底:401/403/404 的处理一致且可解释
重点不是背 API,而是把"权限"做成一条可观测、可排查、可迭代的工程链路。
1. 先把权限模型说清楚(否则一定返工)
权限通常分三层,越往下越"硬":
- 展示权限:菜单/按钮显隐
- 访问权限:路由能不能进
- 数据权限:接口返回什么数据(这层必须由后端保证)
这篇聚焦前两层:展示 + 访问。
一个重要结论:
- 菜单只是 UI,路由守卫必须兜底访问权限。
2. 你需要哪些基础能力
要做成可维护的权限路由,至少要有这些"权威信息源":
- 登录态:
token - 当前用户:
userInfo(至少包含 role/permissions) - 路由来源:
- 前端静态路由(公共页、基础布局)
- 动态路由(按角色生成/按菜单生成)
以及一个"防重复注入"的标记:
isRoutesReady(动态路由是否已注入)
3. 基础结构:白名单 + 受保护路由
白名单路由(无需登录):
/login/404
其它路由默认需要登录。
4. 导航守卫主流程(推荐:覆盖 90% 后台项目)
你可以用这个主流程覆盖 90% 项目:
- 未登录访问受保护路由 -> 跳登录(带 redirect)
- 已登录但用户信息未加载 -> 拉取用户信息
- 根据角色生成动态路由 -> 注入 router
- 再次进入目标路由(replace 避免重复历史)
- 访问无权限 -> 跳 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
- token 有 ->
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 做好兜底,体验和可维护性都会提升