前端小白必看:路由保护总被绕过?3招搞定权限拦截(附防坑指南)
前端小白必看:路由保护总被绕过?3招搞定权限拦截(附防坑指南)
咱先别急着敲代码,聊聊那些让人头秃的瞬间
说实话,我刚开始做前端那会儿,觉得路由保护这事儿特简单——不就是判断一下有没有登录吗?if (user) { next() } else { next('/login') },完事儿。结果呢?上线第二天,测试同事直接甩给我一个链接:https://我们公司后台.com/admin/dashboard,说"你看,我没登录也能进"。
我当时那个脸啊,绿得跟 Vue 的 logo 似的。
更离谱的是有一次,用户直接在浏览器地址栏里输 URL,绕过了登录页,大摇大摆地看到了不该看的数据。老板在群里@我,发了三个问号。那三个问号,我到现在都记得,每个问号都像一把刀,插在我幼小的心灵上。
还有那种场景:你在登录页写了个表单,用户输完账号密码,点击登录,成功跳转到了首页。看起来一切正常对吧?但你试试直接刷新页面——啪,用户信息没了,页面显示"欢迎,undefined"。用户一脸懵逼,你也一脸懵逼,两个人对着屏幕发呆。
所以今天这篇文章,我不跟你扯什么"前端安全架构设计"这种虚头巴脑的东西,就唠唠怎么把路由保护这扇门关严实了。咱们要的是那种,就算用户把键盘敲烂也进不去的铜墙铁壁。
路由保护到底是个啥玩意儿
别被"路由守卫"这种听起来很武侠的名字吓到。你就把它想象成你们小区的保安大爷——没门禁卡的,管你是送外卖的还是业主亲戚,统统拦在闸机外面。
在前端路由里,这个"闸机"就是几个钩子函数。当用户想从 A 页面跳转到 B 页面时,这些钩子就会跳出来喊一声:“站住!查身份证!”
核心逻辑其实就三板斧:
- 我要去哪?(
to参数,目标路由) - 我从哪来?(
from参数,来源路由) - 让不让我过?(
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,前端才报错。
这种前后端状态不一致的问题,没有银弹。我的做法是:
- 重要操作前(比如点保存),先发请求检查当前权限
- 或者设置一个"心跳"机制,定时拉取最新权限
- 最粗暴的:WebSocket 推送权限变更,前端收到后强制刷新
// 在需要敏感操作的组件里
methods: {
async handleSave() {
// 操作前再确认一次权限
const hasPermission = await checkPermission('article:edit')
if (!hasPermission) {
this.$message.error('您的权限已变更,请刷新页面')
return
}
// 执行保存...
}
}
出问题了怎么快速"抓鬼"
路由守卫的 bug 最难调试,因为它在路由跳转前执行,报错信息往往不清晰。分享几个我常用的 debug 技巧。
1. 先打日志,别瞎猜
在守卫的每个分支都加上 console.log,把 to、from、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 了。希望你的路由守卫,永远不被绕过。

&spm=1001.2101.3001.5002&articleId=158655882&d=1&t=3&u=dd582ae7e9fd4fa3a1975a81bb0c0005)
349

被折叠的 条评论
为什么被折叠?



