前端权限控制应该怎么做

核心概念

权限系统大概可以分为两类,一个是路由级的权限,也就是控制菜单的显示和隐藏,达到能不能看到菜单,进入页面的权限控制。另一个是按钮级别的权限,实现更精细的页面内的按钮或者组件的显示和隐藏的控制。

关键原理

路由级权限控制

路由级的权限,说白了就是用来控制页面的跳转,不同权限下的用户跳转统一路由因为权限达到成功和失败的结果。想要实现这样的结果,大概可以有两种思路:

  1. 跳转时判断是否有权限
  2. 动态注册已有权限的路由

第一种方式更简单一些,直接在路由的拦截器中去做相应的权限判断,路由的权限信息可以配置到元信息中

js 复制代码
	const routes = [
	  {
	    path: '/admin',
	    component: AdminPanel,
	    meta: { requiresAuth: true, roles: ['admin'] } // 需登录且角色为管理员
	  },
	  {
	    path: '/user',
	    component: UserPanel,
	    meta: { requiresAuth: true, roles: ['user', 'admin'] } // 需登录且角色为用户或管理员
	  }
	];

然后在路由全局守卫中校验当前用户的权限,是否满足路由的配置

js 复制代码
	router.beforeEach((to, from, next) => {
	  const isAuthenticated = !!localStorage.getItem('token');
	  const userRole = store.state.user.role; // 从Vuex/Pinia获取用户角色
	
	  // 检查路由是否需要权限
	  if (to.matched.some(record => record.meta.requiresAuth)) {
	    if (!isAuthenticated) {
	      next('/login'); // 未登录跳转登录页
	    } else if (to.meta.roles && !to.meta.roles.includes(userRole)) {
	      next('/403'); // 无权限跳转错误页
	    } else {
	      next(); // 放行
	    }
	  } else {
	    next(); // 公开路由直接访问
	  }
	});
	[1,6](@ref)

这样的方式看似简单,但有一个致命的缺点,权限都在前端路由配置中写死,无所做到灵活配置 ,当然这样写的方式也太死板了,我们可以把权限不写到meta中,而是通过接口登录后获取,在跳转后匹配前端路由和后端权限返回,共同决定路由是否可以跳转。 还有一个是菜单的显示,上面的方式是把所有的路由都注册了,但是显然我们不能显示所有的菜单,不能跳转的页面先出来也太傻了,这里也需要和后端来功能决定哪些菜单是需要展示的,哪些是隐藏的。 但是,都做到这一步了,我们完全可以用第二种动态路由来实现路由级权限来。。。

动态路由实现

在前端路由的代码里,我们只配置基础路由信息,也就是所有用户都会有的路由权限,而其他的根据后端返回,来进行动态注册

js 复制代码
	// 登录后获取用户权限路由数据
	import router from './router'; // 引入路由实例
	
	async function login() {
	  // ... 登录逻辑
	  const userPermissions = await fetchUserPermissions(); // 从后端获取用户权限
	  // 根据权限数据生成动态路由
	  const dynamicRoutes = generateRoutes(userPermissions);
	  // 动态添加到路由实例
	  dynamicRoutes.forEach(route => {
	    router.addRoute(route); // 动态添加路由
	  });
	  // 跳转到首页或目标页
	  router.push('/dashboard');
	}
	
	// 一个简单的生成路由的函数示例
	function generateRoutes(permissions) {
	  return permissions.map(permission => ({
	    path: permission.path,
	    component: () => import(`./views/${permission.componentName}.vue`), // 动态导入组件  
	    meta: { 
	      title: permission.title,
	      icon: permission.icon
	    }
	  }));
	}

这样的方式更彻底,菜单直接根据路由表生成,无需把所有的路由都注册,安全性更高。 但是动态路由注册也带来了一些问题

  1. 路由的重复注册 添加新的动态路由之前应该清除旧的路由,多次调用addRoute会导致路由重复,会抛出异常
  2. 页面刷新路由丢失、 动态路由保存在JavaScript内存中,页面刷新后,vue重新初始化,动态路由都会丢失,所以我们需要将动态路由存储起来,可以存储到localStorage或者sessionStorage中,在应用初始化或者路由守卫中读取存储的数据并重新添加路由。 当然也可以每次都去请求登录用户的动态路由接口,重新请求重新添加

按钮级别权限

按钮级别的权限会简单一点,登录后,获取到所有的按钮级别权限的数组,然后在每一个需要控制到按钮或组件的地方,单独判断该按钮的权限字符是否在当前用户的权限字符数组中,可以直接用v-if,或者更好的方式一个专门用来判断按钮权限的指令。

js 复制代码
	Vue.directive('permission', {
	  inserted(el, binding) {
	    const requiredRole = binding.value;
	    const userRole = store.state.user.role;
	    if (userRole !== requiredRole) {
	      el.parentNode?.removeChild(el); // 无权限则移除DOM元素
	    }
	  }
	});
	
	// 使用示例
	<button v-permission="'admin'">删除数据</button>

一点需要注意的管理员权限可以用' * '来替代,我们的指令中可以判断 * 直接返回

图表

权限汇总

flowchart TD A[用户登录] --> B[获取用户权限数据] B --> C[解析权限数据] C --> D[生成动态路由] D --> E[注册动态路由到Vue Router] E --> F[生成动态菜单] F --> G[用户访问路由] G --> H{路由守卫检查权限} H -- 有权限 --> I[渲染页面组件] H -- 无权限 --> J[跳转404/登录页] I --> K[组件内按钮渲染] K --> L{按钮权限指令v-permission} L -- 权限码匹配 --> M[显示按钮] L -- 权限码不匹配 --> N[移除按钮DOM]

路由权限

flowchart TD A[用户访问路由] --> B[路由守卫 beforeEach 拦截] B --> C[检查目标路由 meta.requiresAuth] C -- 是 --> D[检查用户登录状态与权限] C -- 否 --> E[直接放行] D --> F{权限是否匹配?} F -- 是 --> G[正常跳转] F -- 否 --> H[跳转至无权限页面/登录页] I[动态路由模式] --> J[登录后获取用户权限] J --> K[筛选异步路由 asyncRoutes] K --> L[调用 router.addRoutes 添加路由] L --> M[生成侧边栏菜单]

按钮权限

flowchart TD A[组件渲染] --> B[遇到权限控制按钮] B --> C[获取用户权限列表] subgraph D [权限判断方式] D1[自定义指令 v-permission] D2[条件渲染 v-if] D3[封装权限组件] end C --> D D --> E{操作权限是否存在?} E -- 是 --> F[显示按钮/功能] E -- 否 --> G[隐藏或禁用按钮/功能]
相关推荐
袁煦丞27 分钟前
Redis内存闪电侠:cpolar内网穿透第614个成功挑战
前端·程序员·远程工作
BillKu32 分钟前
Vue3组件加载顺序
前端·javascript·vue.js
IT_陈寒40 分钟前
Python性能优化必知必会:7个让代码快3倍的底层技巧与实战案例
前端·人工智能·后端
暖木生晖1 小时前
引入资源即针对于不同的屏幕尺寸,调用不同的css文件
前端·css·媒体查询
袁煦丞1 小时前
DS file文件管家远程自由:cpolar内网穿透实验室第492个成功挑战
前端·程序员·远程工作
用户013741284371 小时前
九个鲜为人知却极具威力的 CSS 功能:提升前端开发体验的隐藏技巧
前端
永远不打烊1 小时前
Window环境 WebRTC demo 运行
前端
风舞2 小时前
一文搞定JS所有类型判断最佳实践
前端·javascript
coding随想2 小时前
哈希值变化的魔法:深入解析HTML5 hashchange事件的奥秘与实战
前端
一树山茶2 小时前
uniapp在微信小程序中实现 SSE进行通信
前端·javascript