路由与布局骨架篇:登录态与路由守卫 | token 校验、白名单、重定向

67 阅读7分钟

同学们好,我是 Eugene(尤金),一个拥有多年中后台开发经验的前端工程师~

(Eugene 发音很简单,/juːˈdʒiːn/,大家怎么顺口怎么叫就好)

你是否也有过:明明学过很多技术,一到关键时候却讲不出来、甚至写不出来?

你是否也曾怀疑自己,是不是太笨了,明明感觉会,却总差一口气?

就算想沉下心从头梳理,可工作那么忙,回家还要陪伴家人。

一天只有24小时,时间永远不够用,常常感到力不从心。

技术行业,本就是逆水行舟,不进则退。

如果你也有同样的困扰,别慌。

从现在开始,跟着我一起心态归零,利用碎片时间,来一次彻彻底底的基础扫盲

这一次,我们一起慢慢来,扎扎实实变强。

不搞花里胡哨的理论堆砌,只分享看得懂、用得上的前端干货,

咱们一起稳步积累,真正摆脱“面向搜索引擎写代码”的尴尬。

一、先搞清楚几个概念

1.1 什么是登录态?

登录态就是「当前用户是否已经登录」的状态。前端通常用 token 来表示:

  • 有 token:已登录
  • 没有 token:未登录

token 一般存在 localStoragesessionStorage 里,登录成功后由后端返回,后面每次请求都带上,后端根据 token 识别用户。

1.2 什么是路由守卫?

路由守卫就是路由跳转前的「关卡」,用来统一处理跳转逻辑。Vue Router 里最常用的就是 router.beforeEach

  • 每次路由跳转前都会执行
  • 可以在这里做 token 校验、权限判断
  • 可以通过 next() 控制是放行还是拦截、重定向

二、整体流程:beforeEach 里要解决什么问题?

核心就三步:

用户访问某个页面
    ↓
beforeEach 拦截
    ↓
判断:有没有 token?
    ├─ 有 token → 已登录,放行
    └─ 没 token → 未登录
            ↓
        目标路由在白名单吗?
            ├─ 在(登录页、404 等)→ 放行
            └─ 不在 → 拦截,重定向到登录页

三、白名单:哪些路由不需要登录?

白名单就是「不用登录也能访问」的路由列表,比如:

  • 登录页 /login
  • 注册页 /register
  • 404 页 /404
  • 首页 /(有些产品首页可以匿名访问)

做法:把白名单路径写在一个数组里,用 includes 判断当前路径是否在白名单中。

四、重定向逻辑:去哪、回哪

常见两种需求:

  • 未登录访问需要登录的页面 → 跳到登录页
  • 登录后想回到「之前想访问的页面」

第二个用 redirect 参数实现:在跳转登录页时把目标地址带上,登录成功后根据 redirect 跳回去。

五、beforeEach 里「应该」做什么?

  1. 做登录态检查:根据 token 判断是否登录
  2. 白名单判断:判断当前路由是否在白名单
  3. 简单重定向:未登录时重定向到登录页,并带上 redirect
  4. 放行:满足条件时调用 next() 放行

可以简单概括为:只做和「能不能进这个页面」相关的判断,别的都尽量放外面

六、beforeEach 里「不应该」做什么?

  1. 异步请求:不在 beforeEach 里发接口请求(如获取用户信息)。因为 beforeEach 会被频繁调用,容易造成重复请求、闪烁和性能问题。
  2. 复杂业务逻辑:比如获取菜单、权限树等,应放到登录后或主布局加载时。
  3. 全局副作用:比如修改 Vuex/Pinia 里很多状态,容易让逻辑难以追踪。
  4. 过度依赖 router:避免在守卫里做大量路由遍历、动态添加等复杂操作,保持守卫简洁。

七、完整代码示例

下面是一个完整的示例,包含:

  • 白名单配置
  • token 获取
  • redirect 保存与使用
  • 清晰的结构和注释
// router/index.js
import { createRouter, createWebHistory } from 'vue-router'
import { getToken } from '@/utils/auth'  // 假设你有这个工具函数

const routes = [
  {
    path: '/login',
    name: 'Login',
    component: () => import('@/views/Login.vue'),
    meta: { title: '登录', requireAuth: false }
  },
  {
    path: '/home',
    name: 'Home',
    component: () => import('@/views/Home.vue'),
    meta: { title: '首页', requireAuth: true }
  },
  {
    path: '/user',
    name: 'User',
    component: () => import('@/views/User.vue'),
    meta: { title: '个人中心', requireAuth: true }
  },
  {
    path: '/:pathMatch(.*)*',
    name: 'NotFound',
    component: () => import('@/views/404.vue'),
    meta: { title: '404', requireAuth: false }
  }
]

const router = createRouter({
  history: createWebHistory(),
  routes
})

// ========== 白名单:不需要登录就能访问的路由 ==========
const WHITELIST = ['/login', '/register', '/404']

/**
 * 判断目标路由是否在白名单内
 * @param {string} path - 路由路径
 * @returns {boolean}
 */
function isInWhitelist(path) {
  return WHITELIST.some(item => path.startsWith(item))
}

router.beforeEach((to, from, next) => {
  // 1. 设置页面标题(可选,和登录校验无直接关系)
  document.title = to.meta?.title || '我的应用'

  const token = getToken()
  const isLoggedIn = !!token

  // 2. 已登录用户访问登录页 → 直接跳到首页(避免重复登录)
  if (to.path === '/login' && isLoggedIn) {
    next({ path: '/' })
    return
  }

  // 3. 白名单内的路由,直接放行
  if (isInWhitelist(to.path)) {
    next()
    return
  }

  // 4. 未登录 + 非白名单 → 拦截,重定向到登录页,并带上「想去的地址」
  if (!isLoggedIn) {
    next({
      path: '/login',
      query: { redirect: to.fullPath }  // 关键:记住用户想去哪
    })
    return
  }

  // 5. 已登录且不是访问登录页,放行
  next()
})

export default router
// utils/auth.js - token 存取工具(示例)
const TOKEN_KEY = 'my_app_token'

export function getToken() {
  return localStorage.getItem(TOKEN_KEY)
}

export function setToken(token) {
  localStorage.setItem(TOKEN_KEY, token)
}

export function removeToken() {
  localStorage.removeItem(TOKEN_KEY)
}
// views/Login.vue - 登录成功后,跳回 redirect 或首页
import { useRouter, useRoute } from 'vue-router'
import { setToken } from '@/utils/auth'

export default {
  setup() {
    const router = useRouter()
    const route = useRoute()

    async function handleLogin() {
      // 模拟登录接口
      const res = await api.login({ username, password })
      setToken(res.data.token)

      // 重点:有 redirect 就跳回去,否则去首页
      const redirect = route.query.redirect || '/'
      router.push(redirect)
    }

    return { handleLogin }
  }
}

八、常见踩坑

场景问题建议
死循环重定向到登录页时未排除登录页本身,导致在白名单判断和登录页重定向间来回跳/login 放入白名单,或在重定向前明确判断
忘记调用 next()守卫里既没放行也没重定向每个分支都要 next(),且只调用一次
重复 next()同一分支里调用多次 next()每个分支调用一次后 return,避免后续再执行
在 beforeEach 里请求用户信息每次路由切换都请求,性能差、体验差在登录成功后请求一次,或在主布局中请求,守卫只做 token 有无判断

九、总结

  • 登录态:用 token 表示,存 localStorage,由后端签发和校验。
  • 白名单:不用登录也能访问的路由,用数组 + includes/some 判断。
  • 重定向:未登录时跳到 /login,并用 query.redirect 保存目标地址,登录成功后跳回。
  • beforeEach:只做 token 判断、白名单判断、简单重定向和放行;不在这里发请求、不做复杂业务、不重复调用 next()
  • 调用 next():每个分支都要有,且只调用一次,用 return 保证逻辑清晰。

🔍 本系列专栏导航

一、《路由与布局骨架篇:Vue Router 实战 | 动态路由、嵌套路由与多级菜单》

二、《路由与布局骨架篇:登录态与路由守卫 | token 校验、白名单、重定向》

三、《路由与布局骨架篇:多标签页(Tab)与缓存 | keep-alive、includeexclude、路由 meta》

四、《路由与布局骨架篇:布局系统 | 头部、侧边栏、内容区、面包屑的拆分与复用》

👉 跟着系列慢慢学,把技术功底扎扎实实地打牢~


学习本就是一场持久战,不需要急着一口吃成胖子。哪怕今天你只记住了一点点,这都是实打实的进步。

后续我还会继续用这种大白话、讲实战方式,带大家扫盲更多前端基础。

关注我,不迷路,咱们把那些曾经模糊的知识点,一个个彻底搞清楚。

如果你觉得这篇内容对你有帮助,不妨点赞+收藏,下次写代码卡壳时,拿出来翻一翻,比搜引擎更靠谱。

我是 Eugene,你的电子学友,我们下一篇干货见~