前端权限控制应该怎么做

核心概念

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

关键原理

路由级权限控制

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

  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[隐藏或禁用按钮/功能]
相关推荐
gnip2 小时前
企业级配置式表单组件封装
前端·javascript·vue.js
一只叫煤球的猫3 小时前
写代码很6,面试秒变菜鸟?不卖课,面试官视角走心探讨
前端·后端·面试
excel4 小时前
Three.js 材质(Material)详解 —— 区别、原理、场景与示例
前端
掘金安东尼4 小时前
抛弃自定义模态框:原生Dialog的实力
前端·javascript·github
hj5914_前端新手8 小时前
javascript基础- 函数中 this 指向、call、apply、bind
前端·javascript
薛定谔的算法8 小时前
低代码编辑器项目设计与实现:以JSON为核心的数据驱动架构
前端·react.js·前端框架
Hilaku8 小时前
都2025年了,我们还有必要为了兼容性,去写那么多polyfill吗?
前端·javascript·css
yangcode8 小时前
iOS 苹果内购 Storekit 2
前端
LuckySusu8 小时前
【js篇】JavaScript 原型修改 vs 重写:深入理解 constructor的指向问题
前端·javascript
LuckySusu8 小时前
【js篇】如何准确获取对象自身的属性?hasOwnProperty深度解析
前端·javascript