前端权限控制思路
界面控制
登录请求中获取权限数据,根据权限展示对应菜单。
用户未登录时,手动敲入管理界面地址,需跳转到登录界面
用户已登录时,手动敲入非权限地址,需跳转 404 界面
按钮控制
仅显示有权操作的按钮,或禁用无权操作的按钮
请求与响应控制
对于非常规操作,如通过浏览器调试工具将某些禁用的按钮变成启用状态,此时发请求也应该被前端拦截
Vue 前端实现
界面控制
界面控制的实现方法是动态路由。用户登录后获取用户有权操作的菜单列表,前端在进行路由构建的时候将用户有权操作的路由添加到 vue-router 中,这样即便用户手动输入无权界面的地址也会因为没有在 vue-router 中添加该路由而返回 not found。
1、用户登录获取菜单列表
2、将用户菜单列表存储到 pinia 中
3、通过 pinia 中的数据构建菜单栏
注意事项:网页刷新后 pinia 的数据也会被刷新,如果构建菜单栏时使用的 pinia 的数据,会出现菜单无法构建的情况。
解决方式:通过将用户菜单权限信息存储在 sessionstroage 中或通过 pinia 持久化来解决
登录成功后:
1 2 3 4 5 6 7 8 9
| const store = useDirectoryConfigStore();
const signInSuccess = async () => { setSession("token", userInfo.username); const res = await getDirectory(); store.directory = res.data; initDynamicRoutes(); router.push("/"); };
|
pinia:
1 2 3 4 5 6 7
| export const useDirectoryConfigStore = defineStore('directoryConfig', { state: () => ({ directory: [] as any[] }), persist: { paths: ['directory'] } }
|
这里有个推荐的写法,当获取到后端出来的路由数据,我们需要给 vue-router 进行添加动态路由的操作,若服务器返回结果为:
1 2 3 4 5 6 7 8 9
| [ { "id": 12, "icon": "icon-user", "title": "用户管理", "path": "users", "auth": ["add", "edit"] } ]
|
我们可以让 path 路径自动对应我们的 view 下的页面(仅作示范):
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19
| import { createRouter, createWebHashHistory } from 'vue-router'; const router = createRouter({ history: createWebHashHistory(), routes: staticRoutes, scrollBehavior: () => ({ left: 0, top: 0 }), });
export const initDynamicRoutes = (){ const store = useDirectoryConfigStore(); const pageModule = import.meta.glob("@/views/**/**/*.vue"); store.directory.foreach((dir)=>{ router.addRoute({ path:'/'+dir.path, component:pageModule[`/src/view/${path}/index.vue`], name:dir.path }) }) }
|
注意事项:动态路由是通过点击登录按钮后添加的,如果刷新了页面这个路由就不会添加。
解决方式:在 App.vue 的 created 生命周期中调用 initDynamicRoutes 即可。
按钮控制
按钮控制可以通过自定义指令来实现:
1
| <el-button type="primary" , v-permission="{action:'add'}"> 添加用户 </el-button>
|
在加载动态路由的时候,给路由的 meta 元数据添加一个权限列表:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22
| import { createRouter, createWebHashHistory } from 'vue-router'; const router = createRouter({ history: createWebHashHistory(), routes: staticRoutes, scrollBehavior: () => ({ left: 0, top: 0 }), });
export const initDynamicRoutes = (){ const store = useDirectoryConfigStore(); const pageModule = import.meta.glob("@/views/**/**/*.vue"); store.directory.foreach((dir)=>{ router.addRoute({ path:'/'+dir.path, component:pageModule[`/src/view/${path}/index.vue`], name:dir.path, meta:{ auth:dir.auth } }) }) }
|
自定义指令实现方式:
1 2 3 4 5 6 7 8 9 10 11
| import Vue from 'vue' import router from '@/router'; Vue.directive('permission'.{ inserted(el,binding){ const action =binding.value.action if(router.currentRoute.meta.indexOf(aciton) == -1){ el.parentNode.removeChild(el) } } })
|
请求与响应控制
拦截用户请求的基本思路是通过 axios 的请求拦截器来实现,在我给出的示例中,仅适用于 restful 风格的请求:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21
| import router frm '@/router' const actionMapping = { 'get':'view', 'post':'add', 'put':'edit', 'delete':'delete' } axios.interceprots.request.use(function(req){ const currentUrl = req.url if(currentUrl !== 'login'){ req.headers.Authorzation = sessionStorage.getItem('token') const auth = router.currentRoute.meta.auth const action = actionMapping[req.method] if(auth && auth.indexOf(action) === -1 ){ alert('没有权限') return Promise.reject(new Error('没有权限')) } } })
|
当服务器返回状态码为 401 代表 token 过期或被篡改,此时应强制跳转到登录界面:
1 2 3 4 5 6 7 8
| axios.interceptors.response.use(function (res) { if (res.data.meta.status === 401) { router.push("/login"); sessionStorage.clear(); window.location.reload(); } return res; });
|
位掩码实现权限运算
核心原理
权限用二进制位表示:每个权限对应一个独立的二进制位(如 0001、0010、0100 等)
权限组合:通过按位或(|)运算合并权限
权限校验:通过按位与(&)运算判断是否拥有某个权限
实现步骤
1、定义权限
每个权限用唯一的二进制位表示,通常用1<<n
生成:
1 2 3 4 5 6
| const Permission = { READ: 1 << 0, WRITE: 1 << 1, DELETE: 1 << 2, ADMIN: 1 << 3, };
|
2、分配用户权限
用户权限是多个权限的组合,通过按位或运算合并:
1 2
| const userPermissions = Permission.READ | Permission.WRITE;
|
3、校验权限
用按位与(&)判断用户是否拥有某个权限:
1 2 3 4 5 6 7
| function hasPermission(userPerm, requiredPerm) { return (userPerm & requiredPerm) === requiredPerm; }
console.log(hasPermission(userPermissions, Permission.WRITE)); console.log(hasPermission(userPermissions, Permission.DELETE));
|
如果 userPerm & requiredPerm 的结果等于 requiredPerm,说明用户拥有该权限。
优点
高效 :二进制运算速度极快。
节省空间 :单个数字可表示多达 32 种权限(JavaScript 中位运算使用 32 位整数)。
组合灵活 :支持动态添加/移除权限。
缺点
可读性差 :对不熟悉位运算的开发者不友好。
权限数量限制 :最多支持 32 种独立权限(在 JS 中)。