Vue Router 4 守卫做鉴权踩到的四个坑:无限重定向、刷新丢动态路由、redirect 开放跳转
导航守卫文档见 Vue Router 官方指南 · 导航守卫。
现象
- 打开
/login页面后浏览器持续跳转,控制台刷屏Redirected when going from /login to /login;或页面白屏,Network 面板连续出现 304。 - 刷新后台某一页,明明已经登录却被踢回登录页;重新登录后回到首页,而不是原来的页面。
addRoute按权限下发的动态路由,刷新后全部消失,直接进 404。- 登录后
push到目标页匹配不到,控制台输出No match found for location with path。
机制
beforeEach(to, from) 的返回值决定这次导航的结果:返回 false 取消导航,返回路由地址字符串或对象做重定向,不写返回值表示放行。Vue Router 4 里 next 仍然可用,但不要和 return 混用;return 之后 next 的调用会被忽略,或造成重复触发。
守卫在每次导航都会执行,包括由重定向触发的那次导航。所以「未登录 → /login」这条规则如果没排除 to 本身就是 /login 的情况,会形成无限重定向。
权限是异步拉取的,而首次导航发生在 app.use(router) 时。如果权限放在 Pinia store 里,并且要等 mount 之后才初始化,守卫第一次读到的就是空值。
addRoute 之后路由表变了,但当前这次导航已经用旧路由表解析过 to,所以需要 return to.fullPath(或 router.replace(to.fullPath))让路由重新解析一次。
刷新时动态路由不存在:所有 addRoute 注册的路由都是内存态,刷新即消失,必须由守卫在每次冷启动时按权限重建。
静态托管 + catch-all 的顺序:先注册 /:pathMatch(.*)* 再 addRoute 时,新加的路由优先级更高没问题(Vue Router 4 会按分数排序),但已经进入 404 的那次导航不会自动重跑。
修法
守卫写法:
router.beforeEach(async (to) => {
const auth = useAuthStore()
if (!auth.loaded) await auth.load() // 只拉一次,避免每次导航都打接口
if (to.meta.public) return true // 白名单显式声明,不靠 path 字符串比较
if (!auth.token) return { name: 'login', query: { redirect: to.fullPath } }
if (!auth.routesReady) {
auth.routesReady = true
auth.menus.forEach(m => router.addRoute(m))
return to.fullPath // 重新解析一次
}
return true
})
登录页那条路由要在 meta 里标 public: true,而不是在守卫里写 if (to.path === '/login'):
{ path: '/login', name: 'login', component: Login, meta: { public: true } }
登录成功后回到原页面:
const redirect = route.query.redirect
router.replace(typeof redirect === 'string' && redirect.startsWith('/') ? redirect : '/')
必须校验 redirect 以 / 开头,否则 ?redirect=https://evil 会变成开放重定向。注意 //evil.com 这种协议相对 URL 也以 / 开头,需要按实际安全要求再收紧(例如排除 // 前缀)。
启动顺序:不要 app.mount 之后再拉权限,否则首屏会闪一下登录页。要么在 mount 前 await,要么用 router.isReady().then(() => app.mount('#app')):
const app = createApp(App)
app.use(router)
await router.isReady()
app.mount('#app')
自检
- 打开
/login,看是否稳定停住,Network 面板不应有连续的文档请求; - 退出登录后点浏览器后退,是否还能看到上一个受保护页面的内容。守卫会拦导航,但页面 DOM 已在内存里,敏感数据要自己清;
- 深链接直接打开受保护页 → 登录 → 是否回到原路径;
- 刷新动态路由页,是否 404;
- 用 curl 或直接手改地址栏试
?redirect=//evil.com,确认不会跳出去; - 404 路由放在最后,且
meta.public为true,否则未登录用户访问不存在的路径会先被踢去登录。