前言
作为一个后台管理系统,权限管理是一个绕不开的话题,一个成熟的后端系统离不开一个比较完善的权限管理系统,他是可以为我们提供更好的服务以及体验,话不多说下边我们开始步入正题。
RBAC权限简介
RBAC(Role-Based Access Control),即基于角色的访问控制,是一种广泛应用于信息安全领域的访问控制模型。RBAC的核心思想是将系统中的用户按照其角色进行分类,然后定义不同角色具有的权限,最后指定哪些用户属于哪些角色,从而实现权限管理。
RBAC 的主要组成部分包括角色、权限和用户。角色是指对某一类用户进行抽象的描述,包括他们所拥有的权限;权限是指系统的操作或资源,可以被分配给角色或用户;用户是系统中的使用者,可以被分配到一个或多个角色中。
RBAC 的优点包括简化了权限管理,降低了管理的复杂性;提高了安全性,通过限制用户的权限,减少了系统受到攻击的可能性;提高了系统的灵活性和可扩展性,可以根据需要对角色和权限进行动态调整。
前端技术栈
vue3、vite、element-plus、axios、TypeScript、pinia
后端技术栈
node、express、mongoDB
实现流程
1、后端数据库表设计
1、创建用户信息表
const usersSchema = new Schema({
username: {
type: String,
default: `user_${str}`
},//用户名
password: String,//密码
name: {
type: String,
default: `张三${str}`
},//姓名
role: {
type: [mongoose.Types.ObjectId],
ref: "roles",
default: null
},
//头像
avatar: {
type: String,
default: 'https://img.zcool.cn/community/01786557e4a6fa0000018c1bf080ca.png@1280w_1l_2o_100sh.png'
}
})
2、创建角色信息表
const rolesSchema = new Schema({
rolename: String,
menu: {
type: [mongoose.Types.ObjectId],
ref: "menu"
},
buttons: {
type: [mongoose.Types.ObjectId],
ref: "buttons"
},
})
3、创建路由菜单表
const menuSchema = new Schema({
title: String,
level: Number,
name:{
type:String,
default:null
},
rid: {
type: mongoose.Types.ObjectId,
ref: "menu",
default: null
},
})
4、创建按钮权限表
const buttonsSchema = new Schema({
title: String,
rid: mongoose.Types.ObjectId,
name:{
type:String,
default:null
},
level:{
type:Number,
default:null
},
})
2、 前端设计
1、创建并暴露静态路由表、动态路由表以及任意路由。
静态路由表:登录页、注册页、首页等不需要认证角色的页面。
export const constantRoute = [
// 登录
{
name: "login", //命名路由
path: "/login",
component: () => import("../views/Login.vue"),
meta: {
title: "登录", //菜单标题
hidden: true, //代表路由标题在菜单中是否隐藏 true:隐藏 false:不隐藏
},
},
// 登录成功以后展示数据的路由
{
name: "HomeView", //命名路由
path: "/",
component: () => import("../views/HomeView.vue"),
meta: {
title: "", //菜单标题
hidden: false,
icon: "", //图标
},
redirect: "/home", //让你访问跟路径时,直接重定向home页面
children: [
{
name: "home",
path: "/home",
component: () => import("../views/Home.vue"),
meta: {
title: "首页", //菜单标题
hidden: false,
icon: "HomeFilled", //菜单文字左侧的图标,支持element-plus所以图标
},
},
],
},
// 404
{
name: "404", //命名路由
path: "/404",
component: () => import("../views/404.vue"),
meta: {
title: "404", //菜单标题
hidden: true,
},
},
];
动态路由表:用户信息页、用户管理页、权限设置页等需要认证角色信息的页面。
//动态路由
export const allAsyncRoutes = [
// 用户信息
{
name: "list",
path: "/list",
component: () => import("../views/HomeView.vue"),
meta: {
title: "用户管理",
hidden: false,
icon: "User",
},
redirect: "/list/about",
children: [
{
name: "about",
path: "/list/about",
component: () => import("../views/AboutView.vue"),
meta: {
hidden: false,
title: "用户列表",
icon: "Platform",
},
},
],
},
// 权限管理
{
name: "acl",
path: "/acl",
component: () => import("../views/HomeView.vue"),
meta: {
hidden: false,
title: "权限管理",
icon: "Lock",
},
redirect: "/acl/user",
children: [
{
name: "user",
path: "/acl/user",
component: () => import("../views/acl/user/index.vue"),
meta: {
hidden: false,
title: "用户管理",
icon: "User",
},
},
{
name: "role",
path: "/acl/role",
component: () => import("../views/acl/role/index.vue"),
meta: {
hidden: false,
title: "角色管理",
icon: "UserFilled",
},
},
{
name: "permission",
path: "/acl/permission",
component: () => import("../views/acl/permission/index.vue"),
meta: {
hidden: false,
title: "菜单管理",
icon: "Monitor",
},
},
],
},
];
任意路由:当用户在导航栏输入错误路由时,需要跳转的页面比如404页面。
//任意路由
export const anyRoutes = {
name: "Any",
path: "/:pathMatch(.*)*",
redirect: "/404",
meta: {
title: "任意路由", //菜单标题
hidden: true,
},
};
2、利用pinia创建用户信息仓库
创建仓库用来存储后端返回用户信息的路由权限表、角色表、按钮权限表。并根据这些权限来生成最终的用户界面导航栏,以及按钮是否启用。在这里我们需要引入router实例,以及loadsh库中的clonedeep深拷贝方法,来对静态路由表进行深拷贝。还需要利用递归对动态路由已经后端返回的路由进行匹配,最终生成最新的动态路由菜单。最后对动态路由以及静态路由进行合并并重新赋值。接下来我们看一下实现代码。
// 创建用户小仓库
let useUserStore = defineStore('User', {
// 小仓库存储数据地方
state: () => {
return {
token: localStorage.getItem('roleid'), //admin
name: '',
roles: '',
menuRoutes: constantRoute, //仓库存储生成菜单需要数组(路由)
authBtnList: [], //储存按钮功能的列表
avatar: '',
}
},
// 异步 | 逻辑的地方
actions: {
// 获取用户信息
async getinfo() {
const { data } = await axios.get(
`http://localhost:3000/userinfo?id=${this.token}`,
)
if (data.code === 200) {
this.name = data.name
this.roles = data.rolenames
this.authBtnList = data.buttons
this.avatar = data.avatar
const asyncRoutes = filterAsyncRoutes(
// 过滤路由,过滤出拥有的菜单权限
cloneDeep(allAsyncRoutes),
data.routes,
)
console.log(asyncRoutes);
// 将路由进行拼接,过滤出来的路由以及任意路由(404)
addRoutes([...asyncRoutes, anyRoutes])
this.menuRoutes = [...constantRoute, ...asyncRoutes] //拼接完的在于静态路由进行拼接并赋值
return 'ok'
} else {
return Promise.reject('获取用户信息失败')
}
},
})
//利用递归对异步路由和后台返回的路由进行匹配,最终生成动态菜单路由
function filterAsyncRoutes(allAsyncRoutes, routesName) {
return allAsyncRoutes.filter((route) => {
if (routesName.indexOf(route.name) === -1) {
return false
}
if (route.children && route.children.length > 0) {
//递归
route.children = filterAsyncRoutes(route.children, routesName)
}
return true
})
}
// 动态路由拼接
function addRoutes(routes) {
console.log(routes)
routes.forEach((route) => {
router.addRoute(route)
})
}
3、在完成登陆后调用pinia仓库中的方法来获取用户权限
// 获取用户相关的小仓库
import useUserStore from "../stores/modules/user";
let userStore = useUserStore();
const login = async () => {
const { data } = await axios.get(
`http://localhost:3000/login?username=${username.value}&&password=${password.value}`
);
console.log(data.data[0]._id);
localStorage.setItem("roleid", data.data[0]._id);
userStore.changeToken(data.data[0]._id);
userStore.getinfo();
router.push("/");
};
4、按钮级权限实现
按钮级权限采用自定义指令来实现,通过pinia仓库保存的按钮信息来及逆行判断是否存在该按钮权限,并采用disabled来对按钮进行禁用操作。
import useUserStore from '../stores/modules/user.js'
export const isHasButton = (app) => {
let userStore = useUserStore()
//全局自定义指令
app.directive('has', {
mounted(el, options) {
if (!userStore.authBtnList.includes(options.value)) {
el.parentNode.removeChild(el)
}
},
})
}
以上就是RBAC权限管理的全部过程,如果小伙伴在实现时遇见问题,可以在评论区留言,我看到后会在第一时间帮助大家解决问题。如果这篇文章能够帮助到大家,也希望大家可以给小编点一个免费的小赞。