前端小白必看:路由保护总被绕过?3招搞定权限拦截(附防坑指南)

前端小白必看:路由保护总被绕过?3招搞定权限拦截(附防坑指南)

前端小白必看:路由保护总被绕过?3招搞定权限拦截(附防坑指南)


咱先别急着敲代码,聊聊那些让人头秃的瞬间

说实话,我刚开始做前端那会儿,觉得路由保护这事儿特简单——不就是判断一下有没有登录吗?if (user) { next() } else { next('/login') },完事儿。结果呢?上线第二天,测试同事直接甩给我一个链接:https://我们公司后台.com/admin/dashboard,说"你看,我没登录也能进"。

我当时那个脸啊,绿得跟 Vue 的 logo 似的。

更离谱的是有一次,用户直接在浏览器地址栏里输 URL,绕过了登录页,大摇大摆地看到了不该看的数据。老板在群里@我,发了三个问号。那三个问号,我到现在都记得,每个问号都像一把刀,插在我幼小的心灵上。

还有那种场景:你在登录页写了个表单,用户输完账号密码,点击登录,成功跳转到了首页。看起来一切正常对吧?但你试试直接刷新页面——啪,用户信息没了,页面显示"欢迎,undefined"。用户一脸懵逼,你也一脸懵逼,两个人对着屏幕发呆。

所以今天这篇文章,我不跟你扯什么"前端安全架构设计"这种虚头巴脑的东西,就唠唠怎么把路由保护这扇门关严实了。咱们要的是那种,就算用户把键盘敲烂也进不去的铜墙铁壁。


路由保护到底是个啥玩意儿

别被"路由守卫"这种听起来很武侠的名字吓到。你就把它想象成你们小区的保安大爷——没门禁卡的,管你是送外卖的还是业主亲戚,统统拦在闸机外面。

在前端路由里,这个"闸机"就是几个钩子函数。当用户想从 A 页面跳转到 B 页面时,这些钩子就会跳出来喊一声:“站住!查身份证!”

核心逻辑其实就三板斧:

  1. 我要去哪?to 参数,目标路由)
  2. 我从哪来?from 参数,来源路由)
  3. 让不让我过?next() 函数,放行或拦截)

代码长这样,先混个脸熟:

// Vue Router 3.x 的写法
router.beforeEach((to, from, next) => {
  // 看看用户有没有带"通行证"
  const hasToken = localStorage.getItem('token')
  
  if (hasToken) {
    // 有证?过!
    next()
  } else {
    // 没证?回大门口登记去
    next('/login')
  }
})

看着简单是吧?但魔鬼藏在细节里。比如你有没有想过,如果用户访问的就是登录页本身,你把他重定向到登录页,会发生什么?死循环!页面会像抽风一样疯狂跳转,直到浏览器崩溃。这种 bug 我写过,当时调试了半小时,最后发现是逻辑里少了个判断,气得我想给当时的自己一巴掌。


几种常见的"守门员"套路大比拼

路由守卫这玩意儿,Vue Router 给我们提供了好几种"款式",每种都有自己的脾气。选错了,轻则代码臃肿,重则漏洞百出。

全局前置守卫——小区大门口的保安队长

这是最常用的,也是权限拦截的第一道防线。不管你想进哪个门,都得先过他这关。

// router/index.js
import Vue from 'vue'
import Router from 'vue-router'
import store from '@/store'

Vue.use(Router)

const router = new Router({
  routes: [
    { path: '/login', component: () => import('@/views/Login.vue') },
    { 
      path: '/dashboard', 
      component: () => import('@/views/Dashboard.vue'),
      meta: { requiresAuth: true }  // 标记需要权限
    },
    { 
      path: '/admin', 
      component: () => import('@/views/Admin.vue'),
      meta: { requiresAuth: true, roles: ['admin'] }  // 还要特定角色
    }
  ]
})

// 全局前置守卫——所有路由都要过这关
router.beforeEach(async (to, from, next) => {
  // 开始显示加载条,别让用户看白屏
  // 如果你用了 NProgress 或者 Element 的 Loading
  // NProgress.start()
  
  console.log(`[路由守卫] 正在从 ${from.path} 跳转到 ${to.path}`)
  
  // 1. 先检查有没有 token
  const token = localStorage.getItem('access_token')
  
  // 2. 如果访问的是登录页,且有 token,直接踢到首页
  // 这种场景常见于:用户已登录,但手动输入 /login 想再登一次
  if (to.path === '/login' && token) {
    console.log('已登录用户访问登录页,重定向到首页')
    next('/dashboard')
    return
  }
  
  // 3. 检查目标路由是否需要认证
  if (to.matched.some(record => record.meta.requiresAuth)) {
    // 需要认证,但没 token?滚去登录
    if (!token) {
      console.warn('拦截未授权访问:', to.path)
      next({
        path: '/login',
        // 把原本想去的路径记下来,登录成功后好跳回来
        query: { redirect: to.fullPath }
      })
      return
    }
    
    // 4. 有 token,但 Vuex 里没用户信息(比如刷新页面后)
    // 这种情况超常见!刷新后 Vuex 被清空了,但 token 还在 localStorage
    if (!store.getters.userInfo) {
      try {
        console.log('Token 存在但用户信息缺失,尝试拉取用户信息...')
        // 这里发请求去后端拿用户信息
        await store.dispatch('fetchUserInfo')
        // 拿到信息后再放行,确保页面渲染时有数据
        next()
      } catch (error) {
        //  token 过期或无效了,后端返回 401
        console.error('获取用户信息失败,Token 可能已过期:', error)
        localStorage.removeItem('access_token')
        next('/login')
      }
    } else {
      // 一切正常,放行
      next()
    }
  } else {
    // 不需要认证的路由,直接过
    next()
  }
})

// 后置钩子,可以用来关闭 loading
router.afterEach(() => {
  // NProgress.done()
})

export default router

这段代码看起来有点长,但每一行都是血泪教训。特别是那个 to.matched.some,很多人直接用 to.meta.requiresAuth,但如果你的路由是嵌套的,子路由没写 meta,但父路由写了,直接用 to.meta 就漏判了。matched 会检查整个匹配链,稳妥得很。

独享路由守卫——VIP 室的专属保镖

有时候,某个路由特别敏感,比如超级管理员后台,你想给它单独加个守卫,不影响其他页面。这时候就用 beforeEnter

{
  path: '/super-admin',
  component: SuperAdmin,
  beforeEnter: (to, from, next) => {
    // 只有特定角色能进
    const userRole = store.state.userInfo?.role
    
    console.log(`[独享守卫] 检查用户角色: ${userRole}`)
    
    if (userRole === 'super_admin') {
      next()
    } else {
      // 没权限?弹个提示,然后送回首页
      alert('您没有权限访问此页面')
      next('/')
    }
  }
}

这种写法的好处是逻辑内聚,看路由配置就知道这页面有特殊的进门要求。但别滥用,每个路由都写这个,维护起来就是灾难。

组件内守卫——进屋后再查一次身份证

这个就比较骚了,代码写在组件内部。有时候你需要在组件已经实例化后,再做一些权限判断。比如用户已经在页面 A 了,点击某个按钮跳转到页面 B,但页面 B 的组件想根据当前状态决定要不要真的展示。

// Dashboard.vue
export default {
  data() {
    return {
      sensitiveData: null
    }
  },
  
  // 当路由参数改变时复用组件,这个钩子会触发
  beforeRouteUpdate(to, from, next) {
    console.log('[组件守卫] 路由更新,但组件复用')
    // 比如从 /user/1 切换到 /user/2
    // 可以在这里重新拉数据
    this.fetchData(to.params.id)
    next()
  },
  
  // 离开组件前触发,可以用来做"未保存提示"
  beforeRouteLeave(to, from, next) {
    if (this.hasUnsavedChanges) {
      const answer = window.confirm('您有未保存的更改,确定要离开吗?')
      if (answer) {
        next()
      } else {
        next(false)  // 取消导航
      }
    } else {
      next()
    }
  }
}

说实话,组件内守卫我用的不多,大部分场景全局守卫都能搞定。但 beforeRouteLeave 那个"未保存提示"功能,确实好用,做表单页面必备。

混入 Mixin——老项目里的"祖传代码"

如果你维护的是 Vue 2 时代的老项目,可能会看到这种写法:

// mixins/auth.js
export default {
  beforeRouteEnter(to, from, next) {
    if (!localStorage.getItem('token')) {
      next('/login')
    } else {
      next()
    }
  }
}

// 然后在几十个组件里这样用
export default {
  mixins: [authMixin]
}

我求你别这么干了。Mixin 这玩意儿,被称为"Vue 的黑暗面",逻辑分散得到处都是,找 bug 的时候想死。新项目直接用组合式 API 或者高阶组件,老项目重构时也尽量把 mixin 里的逻辑抽到路由守卫里。


手把手教你写个"铁面无私"的拦截器

好了,基础概念唠完了,现在上硬菜。我要给你一个完整的、可以直接抄到项目里的权限拦截方案。这套方案经过我三个项目的打磨,坑都踩得差不多了。

第一步:搞个白名单,别让登录页把自己给拦了

这是最经典的死循环 bug。你想啊,守卫的逻辑是"没登录就去登录页",但如果登录页本身也需要"没登录才能去",那用户访问登录页时,守卫一看"没登录",就把他重定向到登录页,然后无限循环。

// 白名单,这些页面不需要登录就能访问
const whiteList = ['/login', '/register', '/404', '/forget-password']

router.beforeEach((to, from, next) => {
  const token = localStorage.getItem('token')
  
  // 在白名单里?直接过,别管有没有 token
  if (whiteList.includes(to.path)) {
    // 但如果已经登录了还访问登录页,就踢到首页
    if (token && to.path === '/login') {
      next('/')
    } else {
      next()
    }
    return
  }
  
  // 不在白名单,检查 token...
})

看到那个 includes 了吗?别用 indexOf,写起来丑还容易出错。ES6 的数组方法该用就用。

第二步:Token 存哪?这是个哲学问题

localStorage 还是 cookie?sessionStorage 还是内存?每个选择都有代价。

// 方案 A:localStorage——最常用,但 XSS 攻击能读到
localStorage.setItem('token', 'Bearer xxxxx')

// 方案 B:cookie 设置 httpOnly——前端 JS 读不到,安全但麻烦
// 需要后端配合设置,前端只管发请求,浏览器自动带 cookie

// 方案 C:内存存储——最安全,但刷新页面就丢
// 适合那种"每次打开页面都重新登录"的超敏感系统

我的建议是:普通项目用 localStorage + 短有效期 token(access token)+ 长有效期刷新 token(refresh token)。代码这样写:

// utils/auth.js
const TOKEN_KEY = 'admin_access_token'
const REFRESH_KEY = 'admin_refresh_token'

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

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

export function removeToken() {
  localStorage.removeItem(TOKEN_KEY)
  localStorage.removeItem(REFRESH_KEY)
}

// 检查 token 是否即将过期(提前 5 分钟刷新)
export function isTokenExpiringSoon(token) {
  try {
    const payload = JSON.parse(atob(token.split('.')[1]))
    const exp = payload.exp * 1000  // 转成毫秒
    return Date.now() > exp - 5 * 60 * 1000
  } catch {
    return true  // 解析失败就当过期处理
  }
}

第三步:异步验证最容易变成死循环

这是最坑的地方。你想在守卫里发请求验证 token 有效性,但如果请求失败了(比如 401),你想把用户踢到登录页。但如果你的 axios 拦截器也在处理 401,然后也做跳转,就可能会两个逻辑打架。

// 错误示范:这样写容易死循环或重复跳转
router.beforeEach(async (to, from, next) => {
  if (to.path === '/login') {
    next()
    return
  }
  
  try {
    // 假设这个请求在 401 时会抛错
    await axios.get('/api/verify-token')
    next()
  } catch (error) {
    // 这里跳转到登录页
    next('/login')
  }
})

问题在于,如果你的 axios 拦截器也在处理 401,它可能会先执行 router.push('/login'),然后守卫里的 next('/login') 又执行一次,控制台就会报"NavigationDuplicated"的错误。

正确的做法是:守卫里只做"获取用户信息"这种必要的请求,token 的有效性验证交给 axios 拦截器统一处理。守卫里如果拿到用户信息失败,就直接清 token 跳转,别重复处理错误。

// 改进版守卫
router.beforeEach(async (to, from, next) => {
  const token = getToken()
  
  if (whiteList.includes(to.path)) {
    next()
    return
  }
  
  if (!token) {
    next(`/login?redirect=${encodeURIComponent(to.fullPath)}`)
    return
  }
  
  // 如果 Vuex 里已经有用户信息,说明已经验证过了,直接过
  if (store.state.userInfo) {
    next()
    return
  }
  
  // 否则拉取用户信息
  try {
    // 这个 action 里会处理 401 错误
    await store.dispatch('user/getInfo')
    next()
  } catch (error) {
    // 拉取失败,token 肯定有问题
    await store.dispatch('user/resetToken')  // 清掉本地 token
    next(`/login?redirect=${encodeURIComponent(to.fullPath)}`)
  }
})

第四步:处理好加载状态,别让用户看白屏

异步验证的时候,页面是卡住的,用户看到的就是白屏或者上一个页面。体验极差。

// 在 store 里搞个全局 loading 状态
// store/modules/app.js
const state = {
  pageLoading: false
}

const mutations = {
  SET_PAGE_LOADING: (state, status) => {
    state.pageLoading = status
  }
}

// 在路由守卫里控制
router.beforeEach(async (to, from, next) => {
  store.commit('SET_PAGE_LOADING', true)  // 开始转圈圈
  
  try {
    // ... 各种验证逻辑
    next()
  } catch (error) {
    next('/login')
  } finally {
    // 后置守卫里关闭 loading 也可以
  }
})

router.afterEach(() => {
  store.commit('SET_PAGE_LOADING', false)  // 结束转圈圈
})

然后在 App.vue 里放个全局 loading:

<template>
  <div id="app">
    <!-- 全局页面加载动画 -->
    <div v-if="pageLoading" class="global-loading">
      <div class="spinner">加载中...</div>
    </div>
    
    <router-view v-else />
  </div>
</template>

<script>
import { mapState } from 'vuex'

export default {
  name: 'App',
  computed: {
    ...mapState('app', ['pageLoading'])
  }
}
</script>

<style>
.global-loading {
  position: fixed;
  top: 0;
  left: 0;
  width: 100%;
  height: 100%;
  background: rgba(255, 255, 255, 0.9);
  display: flex;
  justify-content: center;
  align-items: center;
  z-index: 9999;
}
</style>

实际开发中那些让人哭笑不得的坑

理论归理论,实战才是检验真理的唯一标准。下面这些坑,我保证你至少会遇到三个。

坑一:后端返回 401 了,前端还在傻傻地渲染页面

这是最尴尬的情况。用户看着页面上的数据,以为自己登录着,其实 token 早就过期了。他点了个按钮,提交数据,结果后端返回 401,前端才恍然大悟"哦原来我没登录啊"。

解决思路:axios 拦截器里统一处理 401,一旦收到立马清 token 跳转。

// utils/request.js
import axios from 'axios'
import router from '@/router'
import { getToken, removeToken } from './auth'

const service = axios.create({
  baseURL: process.env.VUE_APP_BASE_API,
  timeout: 5000
})

// 请求拦截器,带上 token
service.interceptors.request.use(
  config => {
    const token = getToken()
    if (token) {
      config.headers['Authorization'] = `Bearer ${token}`
    }
    return config
  },
  error => {
    return Promise.reject(error)
  }
)

// 响应拦截器,处理 401
service.interceptors.response.use(
  response => {
    return response.data
  },
  async error => {
    const { response } = error
    
    if (response?.status === 401) {
      // Token 过期或无效
      console.warn('收到 401,准备退出登录')
      
      // 如果当前不在登录页,才跳转
      if (router.currentRoute.path !== '/login') {
        // 先清本地数据
        await store.dispatch('user/resetToken')
        
        // 提示用户
        alert('登录已过期,请重新登录')
        
        // 跳转
        router.push(`/login?redirect=${encodeURIComponent(router.currentRoute.fullPath)}`)
      }
    }
    
    return Promise.reject(error)
  }
)

export default service

坑二:刷新一下页面,Vuex 里的用户信息没了

Vuex 是存在内存里的,刷新页面就重置。但很多新手以为"我登录过了,Vuex 里应该有数据",结果守卫里判断 store.state.user 为 null,又把用户踢去登录页。

解决方案前面说了:守卫里判断,如果 localStorage 有 token 但 Vuex 没用户数据,就发请求去拉。

// store/modules/user.js
const actions = {
  // 登录
  async login({ commit }, userInfo) {
    const { username, password } = userInfo
    const { data } = await login({ username: username.trim(), password })
    commit('SET_TOKEN', data.accessToken)
    setToken(data.accessToken, data.refreshToken)
    return data
  },
  
  // 获取用户信息——刷新页面时会调用
  async getInfo({ commit, state }) {
    // 这里假设 token 已经在请求头里了
    const { data } = await getUserInfo()
    commit('SET_USER_INFO', data)
    return data
  },
  
  // 重置 token
  resetToken({ commit }) {
    return new Promise(resolve => {
      commit('SET_TOKEN', '')
      commit('SET_USER_INFO', null)
      removeToken()
      resolve()
    })
  }
}

坑三:动态路由加载慢了,用户看到一闪而过的空白页

有些项目的路由是后端返回的,比如根据用户权限动态生成可访问的菜单。这时候守卫里要先拉路由配置,再 addRoutes,这个过程可能要几百毫秒。

// 动态路由加载方案
router.beforeEach(async (to, from, next) => {
  const hasToken = getToken()
  
  if (hasToken) {
    if (to.path === '/login') {
      next('/')
    } else {
      // 检查是否已获取过路由配置
      const hasRoutes = store.state.permission.routes && store.state.permission.routes.length > 0
      
      if (hasRoutes) {
        next()
      } else {
        try {
          // 拉取用户信息和路由配置
          const { roles } = await store.dispatch('user/getInfo')
          const accessRoutes = await store.dispatch('permission/generateRoutes', roles)
          
          // 动态添加路由
          router.addRoutes(accessRoutes)
          
          // 这里要 hack 一下,确保路由添加完成后再跳转
          next({ ...to, replace: true })
        } catch (error) {
          await store.dispatch('user/resetToken')
          next(`/login?redirect=${to.path}`)
        }
      }
    }
  } else {
    // ... 白名单逻辑
  }
})

注意那个 next({ ...to, replace: true }),这是 Vue Router 的一个技巧。因为 addRoutes 是动态添加的,直接 next() 可能会导致 404,所以把当前路由重新走一遍,确保新添加的路由能被匹配到。

坑四:权限改了,但页面还缓存着旧数据

管理员在后台把某个用户的权限从"编辑"改成了"查看",但用户那边页面没刷新,还是能看到编辑按钮。点一下,后端返回 403,前端才报错。

这种前后端状态不一致的问题,没有银弹。我的做法是:

  1. 重要操作前(比如点保存),先发请求检查当前权限
  2. 或者设置一个"心跳"机制,定时拉取最新权限
  3. 最粗暴的:WebSocket 推送权限变更,前端收到后强制刷新
// 在需要敏感操作的组件里
methods: {
  async handleSave() {
    // 操作前再确认一次权限
    const hasPermission = await checkPermission('article:edit')
    if (!hasPermission) {
      this.$message.error('您的权限已变更,请刷新页面')
      return
    }
    // 执行保存...
  }
}

出问题了怎么快速"抓鬼"

路由守卫的 bug 最难调试,因为它在路由跳转前执行,报错信息往往不清晰。分享几个我常用的 debug 技巧。

1. 先打日志,别瞎猜

在守卫的每个分支都加上 console.log,把 tofrom、token 状态、用户信息都打印出来。看起来 low,但最有效。

router.beforeEach((to, from, next) => {
  console.group(`[路由守卫] ${from.path} -> ${to.path}`)
  console.log('目标路由:', to)
  console.log('来源路由:', from)
  console.log('Token 存在:', !!getToken())
  console.log('用户信息存在:', !!store.state.userInfo)
  console.log('需要认证:', to.matched.some(r => r.meta.requiresAuth))
  console.groupEnd()
  
  // ... 逻辑
})

2. Vue Devtools 是你的好朋友

打开 Vue Devtools,看 Vuex 里的状态变化。特别是刷新页面后,看看 user 模块是不是空的,token 是不是还在。

3. 是不是忘了调用 next()?

这是最蠢的 bug,但我也犯过。守卫函数里写了判断逻辑,但某个分支忘了写 next(),结果页面就卡住了,既不跳转也不报错。

// 错误示范
if (hasToken) {
  if (to.path === '/login') {
    next('/')
  }
  // 这里忘了 else!如果 to.path 不是 /login,就没调用 next()
}

// 正确示范
if (hasToken) {
  if (to.path === '/login') {
    next('/')
  } else {
    next()  // 别忘了这个!
  }
}

4. 异步操作没等完成就放行了

如果你在守卫里发了异步请求,但没等它完成就调用了 next(),可能会导致页面渲染时数据还没准备好。

// 错误示范
router.beforeEach(async (to, from, next) => {
  if (needFetchUser) {
    fetchUserInfo()  // 没加 await!
    next()  // 这里直接放行了,但请求还没完成
  }
})

// 正确示范
router.beforeEach(async (to, from, next) => {
  if (needFetchUser) {
    await fetchUserInfo()  // 等它完成
    next()
  }
})

5. 浏览器插件在搞鬼

有一次我调试了半天,发现请求头里的 token 总是不对。最后发现是一个浏览器插件(某个广告拦截器)在自动修改我的请求头。关掉插件,一切正常。这种玄学问题,遇到时记得换个浏览器试试。


几个让代码更"丝滑"的野路子

1. 别把逻辑都塞进路由守卫,会炸的

守卫里应该只做"判断是否放行"这件事,具体的权限计算、角色匹配,抽到工具函数里。

// utils/permission.js
import store from '@/store'

/**
 * 检查是否有某个权限
 * @param {string} permission - 权限标识,如 'user:create'
 */
export function hasPermission(permission) {
  const permissions = store.state.userInfo?.permissions || []
  return permissions.includes(permission)
}

/**
 * 检查是否有某个角色
 * @param {string|Array} roles - 角色或角色数组
 */
export function hasRole(roles) {
  const userRole = store.state.userInfo?.role
  if (Array.isArray(roles)) {
    return roles.includes(userRole)
  }
  return userRole === roles
}

// 在守卫里用
import { hasRole } from '@/utils/permission'

router.beforeEach((to, from, next) => {
  if (to.meta.roles && !hasRole(to.meta.roles)) {
    next('/403')  // 没权限,去 403 页面
    return
  }
  next()
})

2. 封装一个权限指令,模板里直接用

除了路由层面的拦截,页面上按钮级别的权限控制也很重要。比如"删除"按钮,只有管理员能看到。

// directives/permission.js
import { hasPermission } from '@/utils/permission'

export default {
  inserted(el, binding) {
    const { value } = binding  // 获取传入的权限标识
    
    if (value && !hasPermission(value)) {
      // 没权限,移除元素
      el.parentNode && el.parentNode.removeChild(el)
    }
  }
}

// main.js 里注册
import permission from './directives/permission'
Vue.directive('permission', permission)

// 在模板里用
<template>
  <div>
    <button v-permission="'user:edit'">编辑用户</button>
    <button v-permission="'user:delete'">删除用户</button>
    <!-- 普通用户看不到删除按钮 -->
  </div>
</template>

3. axios 拦截器做二次确认

路由守卫是第一道防线,但万一漏了(比如直接调用 API),axios 拦截器就是第二道。

// 在响应拦截器里,除了处理 401,还可以做权限提示
service.interceptors.response.use(
  response => response.data,
  error => {
    const { status, data } = error.response || {}
    
    if (status === 403) {
      // 403 是 Forbidden,没权限访问
      alert(data.message || '您没有权限执行此操作')
    }
    
    return Promise.reject(error)
  }
)

4. 复杂权限干脆让后端控制数据返回

前端做权限控制,本质是"防君子不防小人"。用户完全可以绕过前端直接调 API。所以真正的权限控制,必须在后端做。

前端的工作是:把有权限的按钮展示出来,没权限的藏起来。但后端必须在每个接口里再次验证:“这个用户真的能看到这条数据吗?”

比如一个获取用户列表的接口,前端控制了只有管理员能看到页面,但后端也要在接口里检查:

// 后端伪代码
app.get('/api/users', (req, res) => {
  // 再次验证 token 和权限
  if (!req.user.hasPermission('user:list')) {
    return res.status(403).json({ message: 'Forbidden' })
  }
  
  // 返回数据
})

5. 懒加载顺便做权限隔离

如果你的项目很大,不同角色看到的页面完全不同,可以用 webpack 的 import() 动态导入,配合路由守卫实现"按需加载+权限隔离"。

// 路由配置
{
  path: '/admin',
  component: () => import(/* webpackChunkName: "admin" */ '@/views/Admin.vue'),
  meta: { roles: ['admin'] }
}

// 守卫里,如果没权限,连代码块都不加载
// 其实 Vue Router 的懒加载本身就有这个效果,匹配不到的路由不会加载对应的 chunk

这样普通用户访问时,根本不会下载 admin 页面的代码,既安全又省流量。


最后随便扯两句

写这篇文章的时候,我想起了自己第一次做权限系统的那个项目。当时我觉得前端嘛,就是把界面做好看,交互做流畅,权限这种"业务逻辑"随便搞搞就行。结果呢?上线一周被测试扒出来三个漏洞,老板开会时点名批评,我恨不得找个地缝钻进去。

后来我才明白,路由保护这事儿,说难也难,说简单也简单。难的是你要考虑各种边界情况:刷新页面怎么办、token 过期怎么办、网络抖动怎么办、用户疯狂点按钮怎么办。简单的是,核心逻辑就那一套:检查凭证,决定放行还是拦截。

但最重要的是心态:别指望前端能解决所有安全问题。前端的路由保护,本质是提升用户体验(没权限的人看不到不该看的按钮),而不是真正的安全防线。真正的安全,在后端,在数据库,在网络层。

咱们做前端的,就是把该做的做到位,不留把柄。下次再遇到这种需求,别再只会写 if (isLoggedIn) 了。稍微动动脑子,把今天这套组合拳打出来——白名单、异步验证、axios 拦截、权限指令、加载状态处理,老板看了都得夸你专业。

哦对了,如果你用 React,React Router 的 Navigate 组件和 useNavigate hook 也能实现类似的功能,逻辑是相通的。Vue 和 React 只是 API 不同,权限控制的思路一模一样。

就这样吧,我去改 bug 了。希望你的路由守卫,永远不被绕过。

在这里插入图片描述

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值