当前位置: 首页 > news >正文

Vue3 路由权限管理:基于角色的路由生成与访问控制

Vue3 路由权限管理:基于角色的路由生成与访问控制

一、核心概念

1.1 大致流程思路:

用户在登录完成的时候,后端给出一个此登录用户对应的角色名字,此时可以将这个用户的角色存起来(vuex/pinia)中,在设置路由时的meta字段中设置的roles属性如:roles: ['admin', 'accountA', 'accountB']----这就是每个页面能访问的角色名。至此为止,用户的角色名字我们拿到了,每个页面能访问的角色名字我们也知道了,接下来只需要去判断当前用户的角色名是否包含这些权限即可—这就需要交给导航守卫了:router.beforeEach((to, from, next) => {})

1.2 核心组件对比表

组件作用示例特点
静态路由基础公共路由登录页/404页无需权限,初始加载
动态路由权限控制路由管理后台各页面按需加载,角色过滤
路由守卫访问控制beforeEach钩子实时验证,安全屏障
状态管理角色存储Vuex/Pinia全局共享,持久化

基于角色的路由权限管理系统主要包含以下几个核心部分:

  1. 静态路由:无需权限即可访问的路由(如登录页、404页)

  2. 动态路由:需要根据角色的权限进行控制是否显示的路由(如后台管理页面)。

  3. 角色权限配置:定义哪些角色可以访问哪些路由,如:

在这里插入图片描述

  1. 路由守卫:在路由跳转时进行权限验证

二、代码实现解析

1. 路由配置

// 静态路由 - 无需权限
const staticRoutes = [{path: '/login',name: 'Login',component: LoginView},{path: '/404',name: 'NotFound',component: NotFoundView}
];// 动态路由 - 要根据角色的权限进行显示
export const dynamicRoutes = [{path: '/admin',name: 'admin',component: HomeView,meta: {title: "首页",title_path: '/admin',sub_title: "后台管理",roles: ['admin', 'accountA', 'accountB'] // 修正为数组格式},children: [{path: 'user',name: 'user',component: UserView,meta: {title: "用户管理",title_path: '/admin/user',sub_title: "用户信息编辑",roles: ['admin', 'accountA', 'accountB'] // 修正为数组格式}},{path: 'role',name: 'role',component: RoleView,meta: {title: "角色管理",title_path: '/admin/role',sub_title: "角色信息编辑",roles: ['admin', 'accountA'] // 修正为数组格式}},{path: 'permis',name: 'permis',component: PermissView,meta: {title: "权限管理",title_path: '/admin/permis',sub_title: "权限信息编辑",roles: ['admin'] // 修正为数组格式}}]}
]

2. 路由初始化

const router = createRouter({history: createWebHistory(process.env.BASE_URL),routes: staticRoutes // 初始只加载静态路由
})

3. 动态路由添加

在用户登录后,根据角色动态添加路由:

// 过滤出用户有权限访问的路由
function filterRoutes(routes: RouteRecordRaw[], userRoles: string[]) {return routes.filter(route => {// 如果没有设置roles,则默认允许访问if (!route.meta?.roles) return true// 检查用户是否有任一所需角色return route.meta.roles.some(role => userRoles.includes(role))})
}// 添加动态路由
function addDynamicRoutes(userRoles: string[]) {const accessibleRoutes = filterRoutes(dynamicRoutes, userRoles)// 先移除可能已存在的动态路由router.getRoutes().forEach(route => {if (dynamicRoutes.some(dr => dr.name === route.name)) {router.removeRoute(route.name!)}})// 添加新路由accessibleRoutes.forEach(route => {router.addRoute(route)})
}

4. 路由守卫实现

router.beforeEach((to, from, next) => {const store = useStore()const userRoles = store.state.roles || []// 1. 检查是否需要权限if (to.meta.roles) {const requiredRoles = to.meta.rolesconst hasPermission = requiredRoles.some(role => userRoles.includes(role))if (!hasPermission) {return next('/404') // 无权限跳转404}}// 2. 检查是否已登录但访问登录页if (to.name === 'Login' && store.state.token) {return next('/admin') // 已登录用户跳转首页}next()
})

三、完整工作流程

  1. 应用初始化
    • 只加载静态路由(登录页、404页)
    • 用户访问时首先进入登录页

  2. 用户登录

    vuex:store/index.ts :

    import { createStore } from 'vuex'export default createStore({state: {roles: [],},getters: {getRoles: state => state.roles // 添加一个getter来获取roles},mutations: {SET_ROLES(state, roles) { // 添加mutation来修改rolesstate.roles = roles}},actions: {setRoles({ commit }, roles) { // 正确的action写法commit('SET_ROLES', roles) // 通过commit调用mutation}},modules: {}
    })
    

    view/LoginView.vue :

    import { dynamicRoutes } from '@/router'; // 导入动态路由
    // 检查当前用户角色的操作---可以放在一个js文件中导入。这里直接写入这个文件
    function filterRoutesByRole(routes: any[], userRoles: string[]) {return routes.filter((route) => {// 如果没有设置roles,或者用户角色包含任一所需角色return (!route.meta?.roles ||route.meta.roles.some((role: string) => userRoles.includes(role)));});
    }
    function generateDynamicRoutes() {const userRoles = store.state.roles || [];console.log(userRoles)const accessibleRoutes = filterRoutesByRole(dynamicRoutes, userRoles);// 先移除可能已存在的动态路由router.getRoutes().forEach((r) => {if (dynamicRoutes.some((dr) => dr.name === r.name)) {router.removeRoute(r.name!);}});// 添加新路由accessibleRoutes.forEach((route) => {router.addRoute(route);});
    }
    // 完结...// 登录按钮
    const submitForm = async () => {try {// 尝试对表单进行验证,如果验证不通过,则不会执行后续代码await formRef.value?.validate();// 使用 axios 发送 POST 请求到服务器的登录接口// 将用户名和密码作为请求体发送const response = await axios.post("/api/users/login", {username: ruleForm.value.usermobile, // 用户名字段在请求体中password: ruleForm.value.userpwd, // 密码字段在请求体中});// 打印响应对象到控制台console.log(response);// 弹出响应消息提示用户message.info(response.data.msg);// alert(response.data.msg);// 从响应数据中提取 token、用户 ID 和用户名const role_code: string = response.data.data.userInfo.role_code; // 角色的唯一标识// 注意:这里存在vuex中的数据应该是一个数组,方便后续处理   let role_code_arr = ref<string[]>([]);role_code_arr.value.push(role_code);// 将角色的编码存到vuex中store.commit("SET_ROLES", role_code_arr);// 生成动态路由generateDynamicRoutes();} catch (error: any) {// 如果请求失败或验证失败,则捕获错误并打印到控制台console.error("请求失败:", error);message.error(error.response.data.msg);}
    };
    

    导航守卫:router/index.ts :

    import { createRouter, createWebHistory, type RouteRecordRaw } from 'vue-router'
    // import { useUserStore } from '@/store/user'
    import { useStore } from "vuex";
    // 路由组件导入
    const HomeView = () => import('../views/HomeView.vue')
    const UserView = () => import('../views/UserView.vue')
    const RoleView = () => import('@/views/RoleView.vue')
    const PermissView = () => import('@/views/PermissView.vue')
    const LoginView = () => import('../views/LoginView.vue')
    const NotFoundView = () => import('../views/NotFound.vue')// 静态路由
    const staticRoutes = [{path: '/login',name: 'Login',component: LoginView},{path: '/404',name: 'NotFound',component: NotFoundView}
    ];// 动态路由
    export const dynamicRoutes = [{path: '/admin',name: 'admin',component: HomeView,meta: {title: "首页",title_path: '/admin',sub_title: "后台管理",roles: ['admin', 'accountA', 'accountB'] // 修正为数组格式},children: [{path: 'user',name: 'user',component: UserView,meta: {title: "用户管理",title_path: '/admin/user',sub_title: "用户信息编辑",roles: ['admin', 'accountA', 'accountB'] // 修正为数组格式}},{path: 'role',name: 'role',component: RoleView,meta: {title: "角色管理",title_path: '/admin/role',sub_title: "角色信息编辑",roles: ['admin', 'accountA'] // 修正为数组格式}},{path: 'permis',name: 'permis',component: PermissView,meta: {title: "权限管理",title_path: '/admin/permis',sub_title: "权限信息编辑",roles: ['admin'] // 修正为数组格式}}]}
    ]const router = createRouter({// history: createWebHistory(import.meta.env.BASE_URL),history: createWebHistory(process.env.BASE_URL),routes:staticRoutes
    })// 导航守卫
    router.beforeEach((to, from, next) => {const store = useStore();const userRoles = to.meta.roles || [];console.log(store.state.roles)// 如果路由需要权限验证if (to.meta.roles) {// const hasPermission = to.meta.roles.some(role => userStore.roles.includes(role));const hasPermission = userRoles.some((role:any) => store.state.roles.includes(role));// const hasPermission = to.meta.roles.includes(store.roles);if (!hasPermission) {return next({ name: 'NotFound' }); // 无权限时跳转到 404 页面}}next(); // 允许访问
    });export default router
    

    生成动态菜单:HomeView.vue:

import { useStore } from "vuex";
const store = useStore();import { useRouter, useRoute } from "vue-router";
const router = useRouter();
const route = useRoute();// 现生成一个动态菜单--需要从meta中拿到
const all_router = router.getRoutes();
// 把全部的路由meta拿到
const all_meta_data = all_router.map((item) => {return item.meta;
});
// 过滤出isMenu === true的meta
const meta_data = all_meta_data.filter((item) => {return item.isMenu === true;
});
// 从vuex中拿到此用户的角色组
const userRoles = store.state.roles || [];
// 过滤出此角色可以访问的路由meta
const finish_meta = meta_data.filter((item) => {return item.roles.some((role: string) => userRoles.includes(role));// return item.roles.includes(store.state.st)
});
// 最后拿到可以给菜单使用的数组菜单
const iconComponents = {  // 图标的命名,由于meta中的icon是字符串,这里也是一种解决方法AppstoreOutlined: AppstoreOutlined,InboxOutlined: InboxOutlined,DesktopOutlined: DesktopOutlined
};
const use_meta = finish_meta.map((item) => {return {key:item.title_path,// icon:item.icon.replace(/^["']|["']$/g, ''),// icon: iconMap[item.icon] || null,icon: iconComponents[item.icon],label:item.title}
});
  1. 路由导航
    • 每次路由跳转时,路由守卫会检查:
    ◦ 目标路由是否需要特定权限
    ◦ 当前用户是否拥有所需权限
    • 根据检查结果允许或拒绝访问

  2. 用户注销

    function logout() {// 清除用户信息store.commit('CLEAR_USER')// 重置路由router.getRoutes().forEach(route => {if (dynamicRoutes.some(dr => dr.name === route.name)) {router.removeRoute(route.name!)}})// 跳转到登录页router.push('/login')
    }
    

检查用户角色操作的作用分析

您提供的代码中关于用户角色检查的部分实际上是非常必要的,它在整个权限控制流程中扮演着关键角色。让我详细解释其作用和必要性:

一、角色检查的核心作用

1. 权限过滤的核心机制
function filterRoutesByRole(routes: any[], userRoles: string[]) {return routes.filter((route) => {return (!route.meta?.roles || // 无权限限制的路由直接放行route.meta.roles.some((role: string) => userRoles.includes(role)) // 检查角色匹配);});
}

这段代码实现了:
路由筛选:根据用户角色过滤出可访问的路由
权限验证:确保用户只能看到自己有权限访问的路由
安全控制:防止越权访问敏感路由

2. 动态路由生成流程
登录模块 路由系统 状态管理 存储用户角色(如['admin']) 触发generateDynamicRoutes 清除旧路由 过滤出可访问路由 添加新路由 登录模块 路由系统 状态管理

二、为什么不能省略?

1. 必要性分析
检查环节作用省略后果
角色存储保留用户权限标识无法识别用户权限级别
路由过滤生成个性化路由表所有用户看到相同菜单
权限验证防止越权访问安全漏洞风险
2. 实际应用场景

假设:
• 用户角色:['editor']
• 路由配置:

[{ path: '/admin', meta: { roles: ['admin'] }},{ path: '/editor', meta: { roles: ['editor'] }}
]

无过滤时:用户可以看到/admin路由但无法正常使用,导致错误
有过滤时:用户只能看到/editor路由,体验更合理

三、代码优化建议

1. 类型安全改进
// 在类型声明文件中
declare module 'vue-router' {interface RouteMeta {roles?: string[];// 其他元字段...}
}// 过滤函数改进
function filterAccessibleRoutes(routes: RouteRecordRaw[], userRoles: string[]): RouteRecordRaw[] {return routes.filter(route => {const requiredRoles = route.meta?.roles || []return requiredRoles.length === 0 || requiredRoles.some(role => userRoles.includes(role))})
}
2. 性能优化
// 避免重复计算
const userRoles = computed(() => store.state.roles)// 路由添加前检查
function needsRefresh(newRoutes: RouteRecordRaw[]) {const currentRoutes = router.getRoutes()return (newRoutes.length !== currentRoutes.length ||newRoutes.some(route => !currentRoutes.some(r => r.path === route.path)))
}
3. 错误处理增强
function generateDynamicRoutes() {try {const userRoles = store.state.roles || []if (!Array.isArray(userRoles)) {throw new Error('角色数据格式错误')}const accessibleRoutes = filterRoutesByRole(dynamicRoutes, userRoles)// 清除旧路由逻辑...// 添加新路由逻辑...} catch (err) {console.error('路由生成失败:', err)router.push('/error?code=route_init_failed')}
}

四、完整工作流程

  1. 用户登录
    • 获取角色信息 → 存储到Vuex

  2. 路由生成

    用户角色
    路由过滤
    清除旧路由
    添加新路由
    菜单渲染
  3. 访问控制
    • 路由守卫二次验证
    • 菜单动态生成

五、总结

您代码中的角色检查操作:
• ✅ 不是多余的:是权限系统的核心逻辑
• ✅ 必不可少:确保路由与权限的精确匹配
• ✅ 可以优化:类型、性能、错误处理等方面

建议保留这部分逻辑,同时参考上述优化建议进行改进,可以使您的权限控制系统更加健壮和安全。

四、最佳实践方案

4.1 路由配置规范

字段类型必填说明
pathstring路由路径
componentComponent组件引用
meta.rolesstring[]允许的角色
meta.titlestring推荐页面标题
meta.iconstring可选菜单图标
meta.cacheboolean是否缓存

4.2 权限检查方案对比

方案优点缺点适用场景
前端控制响应快,体验好安全性较低内部管理系统
前后端结合安全性高实现复杂高安全要求系统
动态接口灵活度高性能开销大权限频繁变更系统

4.3 性能优化建议

  1. 路由懒加载

    component: () => import('@/views/HeavyPage.vue')
    
  2. 路由分组打包

    // vite.config.js
    rollupOptions: {output: {manualChunks: {admin: ['@/views/Admin*.vue']}}
    }
    
  3. 持久化缓存

    // 使用pinia-plugin-persistedstate
    persist: {paths: ['user.roles']
    }
    

五、常见问题解决方案

5.1 问题排查表

现象可能原因解决方案
路由跳转循环守卫逻辑错误检查重定向条件
菜单不更新路由未正确重置确保先remove后add
404错误路由未加载检查addRoute调用
权限失效角色信息丢失检查状态持久化

5.2 典型错误处理

// 安全的路由添加方式
function safeAddRoute(route) {try {if (router.hasRoute(route.name)) {router.removeRoute(route.name)}router.addRoute(route)return true} catch (err) {console.error('路由添加失败:', err)return false}
}

六、完整示例项目结构

/src
├── router
│   ├── index.ts        # 路由入口
│   ├── static.ts       # 静态路由
│   └── dynamic.ts      # 动态路由配置
├── stores
│   └── auth.ts         # 权限状态管理
├── utils
│   └── permission.ts   # 权限验证工具
└── views├── public          # 公共页面└── admin           # 权限页面

通过这套系统化方案,开发者可以快速实现:

  1. 基于角色的精细化权限控制
  2. 动态菜单的自动生成
  3. 安全的访问验证机制
  4. 良好的用户体验和性能表现

http://www.mrgr.cn/news/97515.html

相关文章:

  • ES6 新增特性 箭头函数
  • Redis - 字典(Hash)结构和 rehash 机制
  • 使用LangChain Agents构建Gradio及Gradio Tools(5)——gradio_tools的端到端示例详解
  • 类和对象(下篇)(详解)
  • 蓝桥杯真题——前缀总分、遗迹
  • 【区块链安全 | 第三十四篇】合约审计之重入漏洞
  • 深入解析嵌入式Linux系统架构:从Bootloader到用户空间
  • OpenCv(七)——模板匹配、打包、图像的旋转
  • 【UnityEditor扩展】如何在 Unity 中创建棱柱体(用作VR安全区检测),同时在编辑器插件中实现与撤销/恢复功能
  • HTTP 教程 : 从 0 到 1 全面指南 教程【全文三万字保姆级详细讲解】
  • WEB安全-CTF中的PHP反序列化漏洞
  • 2018年真题
  • SQL:Primary Key(主键)和Foreign Key(外键)
  • 【加密算法】SM4国密算法原理、C++跨平台实现(含完整代码和示例)
  • TCP/IP五层协议
  • 网易运维面试题及参考答案
  • 激光干涉仪学习
  • Linux-CentOS-7—— 安装MySQL 8
  • 设计模式 四、行为设计模式(1)
  • AI烘焙大赛中的算法:理解PPO、GRPO与DPO最简单的方式