1.什么是 RBAC?
基于角色的访问控制(Role-Based Access Control,简称 RBAC)是一种访问控制机制,通过为用户分配角色来管理权限。RBAC 模型使得权限的管理更加简便高效,尤其适用于复杂的系统架构。
1.1 RBAC 重要概念
用户 (User)、角色(Role)、权限(Permission)
- 一个用户拥有若干角色
- 一个角色拥有若干的权限
- 这样就构成了 用户 - 角色 - 权限 的授权模型
RBAC 就是用户通过角色与权限进行关联。在这种模型中,用户与角色之间,角色与权限之间一般是多对多的关系

1.2 RBAC 有什么优势?
RBAC 的优势包括权限管理的模块化、易维护性和安全性。它能够减少权限管理的复杂度,使开发者只需管理角色和权限的关系,而不必直接管理用户与权限的关系。
2.核心模块
用户管理
- 用户列表
- 添加用户
- 编辑用户
- 删除用户
- 设置用户角色(可以有多个角色)
角色管理
- 角色列表
- 添加角色
- 编辑角色
- 删除角色
- 分配用户权限(可以有多个权限)
权限管理
- 权限列表
- 添加权限
- 编辑权限
- 删除权限
3.技术栈
实际上,你编写 RBAC 不需要指定的技术去实现。因为 RBAC 它只是一个权限管理模型,没有技术的限制,为了方便,我这里使用的是 Node.js,Express,MongoDB,mongoose,JWT。这里只是举例子,你用什么技术栈都可以(重要的是理解 RBAC 流程)
4.RBAC 结构设计
4.1 后端 API 路由设计
// 用户管理路由
router
.get("/users", userCtrl.list) // 获取用户列表
.post("/users", userCtrl.create) // 新增用户
.put("/users/:id", userCtrl.update) // 修改用户
.delete("/users/:id", userCtrl.delete) // 删除用户
.get("/users/:id", userCtrl.one) // 获取单个用户
.patch("/users/:id/roles", userCtrl.updateRoles); // 给用户分配角色
// 角色管理路由
router
.get("/roles", rolesCtrl.list) // 获取角色列表
.post("/roles", rolesCtrl.create) // 新增角色
.put("/roles/:id", rolesCtrl.update) // 修改角色
.delete("/roles/:id", rolesCtrl.delete) // 删除角色
.get("/roles/:id", rolesCtrl.one) // 获取单个角色
.patch("/roles/:id/menus", rolesCtrl.updateMenus); // 给角色分配权限
// 权限管理路由
router
.get("/menus", menuCtrl.list) // 获取权限列表
.post("/menus", menuCtrl.create) // 新增权限
.put("/menus/:id", menuCtrl.update) // 修改权限
.delete("/menus/:id", menuCtrl.delete) // 删除权限
.get("/menus/:id", menuCtrl.one); // 获取单个权限
4.2 表结构设计
这里拿 User 表举例子
const { Schema } = require("mongoose");
const userSchema = new Schema({
/**
* 用户名
*/
username: {
type: String,
required: true,
unique: true,
},
/**
* 密码
*/
password: {
type: String,
required: true,
select: false,
},
/**
* 邮件
*/
email: {
type: String,
unique: true,
},
/**
* 姓名
*/
name: {
type: String,
default: "",
},
/**
* 角色
*/
roles: {
type: [Schema.Types.ObjectId],
ref: "Role",
},
/**
* 状态
* 0:未启用
* 1:已启用
*/
status: {
type: Number,
default: 0,
},
// 创建时间
createAt: {
type: Date,
default: Date.now,
},
// 更新时间
updateAt: {
type: Date,
default: Date.now,
},
/**
* 是否为超级管理员
*/
isAdmin: {
type: Boolean,
default: false,
},
});
module.exports = userSchema
4.3 表逻辑操作设计
还是 User 表举例
// 用户处理模块
const { User } = require("../model");
// 获取用户列表
exports.list = async (req, res) => {
try {
const ret = await User.find().populate("roles");
res.status(200).json(ret);
} catch (err) {
res.send(err);
}
};
// 新增用户
exports.create = async (req, res) => {
try {
const ret = await new User(req.body).save();
res.status(200).json(ret);
} catch (err) {
res.send(err);
}
};
// 更新用户列表
exports.update = async (req, res) => {
try {
const ret = await User.findById(req.params.id);
// 将客户端提交的数据合并到数据对象中
Object.assign(ret, req.body);
// 将修改之后的数据对象保存到数据库
await ret.save();
res.status(201).json(ret);
} catch (err) {
res.send(err);
}
};
exports.delete = async (req, res) => {
try {
await User.findByIdAndDelete(req.params.id);
res.status(204).end();
} catch (err) {
res.send(err);
}
};
exports.one = async (req, res) => {
try {
const ret = await User.findById(req.params.id);
res.status(200).json(ret);
} catch (err) {
res.send(err);
}
};
exports.updateRoles = async (req, res,next) => {
try{
const ret = await User.findById(req.params.id);
ret.roles = req.body.roles
await ret.save();
res.status(201).json(ret);
}catch{
next(err);
}
};
4.4 配置 连接数据库
const mongoose = require("mongoose");
// 调用 main 函数,并在发生错误的时候捕获错误并打印到控制台
main().catch((err) => console.log(err));
// 连接数据库
async function main() {
await mongoose.connect("mongodb://127.0.0.1:27017/rbac");
}
module.exports = {
// 导入我们的模型,传递两个参数,第一个是模型名称,第二个是模型结构
User: mongoose.model("User", require("./user")),
Role: mongoose.model("Role", require("./role")),
Menu: mongoose.model("Menu", require("./menu")),
Product: mongoose.model("Product", require("./product")),
Categorie: mongoose.model("Categorie", require("./categorie")),
};
4.5 获取用户权限
const { User } = require("../model");
const jwt = require("jsonwebtoken");
exports.login = async (req, res, next) => {
try {
// 根据用户名查询用户
const user = await User.findOne({ username: req.body.username }).select(
"+password"
);
// 如果没用用户,结束响应
if (!user) {
return res.status(400).json({
msg: "用户名不存在",
});
}
// console.log(req.body, user);
if (req.body.password !== user.password) {
return res.status(400).json({
msg: "密码不正确",
});
}
const token = jwt.sign(
{
userId: user._id,
},
"e5a10b19-d3c5-4a0c-8b35-388e8e237e27"
);
console.log(req.user);
res.status(200).json({
token,
...req.user,
user
});
} catch (error) {
next(error);
}
};
exports.permissions = async (req, res, next) => {
try {
// 获取用户的权限
const ret = await req.user.populate({
path: "roles",
populate: {
path: "menus",
},
});
res.status(200).json(ret);
} catch (err) {
next(err);
}
};
4.6 token 验证
如果我们对 token 有要求,可以自己编写登录的逻辑
4.7 动态路由实现
我这里用到的是 判断用户权限 进行路由的添加实现的
import { createRouter, createWebHistory } from "vue-router";
import { useCounterStore } from "@/stores/counter";
import axios from "axios";
import { ElNotification } from "element-plus";
const router = createRouter({
history: createWebHistory(import.meta.env.BASE_URL),
routes: [
{
path: "/",
// 重定向
redirect: "/container",
},
{
path: "/login",
name: "login",
// 按需导入
component: () => import("../views/Login/LoginView.vue"),
},
{
path: "/container",
name: "container",
component: () => import("../views/ContainerView.vue"),
children: [],
},
],
});
const dynamicRoutes = [
{
path: "/container",
redirect: "/home",
},
{
path: "/home",
name: "home",
component: () => import("../views/Main/home/HomeView.vue"),
meta: { public: true },
},
{
path: "/order",
name: "order",
component: () => import("../views/Main/order/OrderView.vue"),
meta: { roles: ["管理员", "客服", "运营人员"] },
},
{
path: "/cashier",
name: "cashier",
component: () => import("../views/Main/order/CashierOrderView.vue"),
meta: { roles: ["管理员", "客服"] },
},
{
path: "/admin",
name: "admin",
component: () => import("../views/Main/set/AdminView.vue"),
meta: { roles: ["管理员"] },
},
{
path: "/system",
name: "system",
component: () => import("../views/Main/set/SystemView.vue"),
meta: { roles: ["管理员"] },
},
{
path: "/trade",
name: "trade",
component: () => import("../views/Main/trade/TradeView.vue"),
meta: { roles: ["管理员", "商品管理者"] },
},
{
path: "/TradeClass",
name: "TradeClass",
component: () => import("../views/Main/trade/TradeClassView.vue"),
meta: { roles: ["管理员", "商品管理者"] },
},
{
path: "/user",
name: "user",
component: () => import("../views/Main/user/UserView.vue"),
meta: { roles: ["管理员", "运营人员"] },
},
{
path: "/UserClass",
name: "UserClass",
component: () => import("../views/Main/user/UserClass.vue"),
meta: { roles: ["管理员", "运营人员"] },
},
];
router.beforeEach(async (to, from, next) => {
const counterStore = useCounterStore();
const token = counterStore.token;
const userRoles = Array.from(counterStore.userRoles);
if (to.path !== "/login") {
if (!token) {
ElNotification({
title: "提醒",
message: "您还没有登录,请先登录",
type: "warning",
});
return next({ path: "/login" });
}
try {
// console.log("我验证过了");
const as = await axios.get("/validata-token", {
headers: {
Authentication: `Bearer ${token}`,
},
});
dynamicRoutes.forEach((route) => {
if (route.name) {
const existingRoute = router
.getRoutes()
.find((r) => r.name === route.name);
if (existingRoute) {
router.removeRoute(route.name);
}
}
});
// 获取用户角色信息
const roles: string[] = userRoles ?? [];
const accessibleRoutes = dynamicRoutes.filter((route) => {
// 打印是否包含在路由中
return (
!route.meta?.roles ||
route.meta.roles.some((role: string) => roles.includes(role))
);
});
// 将符合规则的路由 添加到 container 路由
accessibleRoutes.forEach((route) => {
router.addRoute("container", route);
// console.log(`添加路由:${route.name},路径:${route.path}`);
// 打印添加的路由
});
// 检查目标路由是否存在于已添加的动态路由中
const routeExists = router.getRoutes().some(route => route.path === to.path);
if (!routeExists) {
ElNotification({
title: "权限提醒",
message: "您没有权限访问该页面",
type: "warning",
});
return next(false); // 阻止路由跳转
}
next(); // 如果是有效的token,必须访问目标路由
} catch (error) {
ElNotification({
title: "提醒",
message: "您的token已经过期,请重新登录",
type: "warning",
});
console.log("error", error);
counterStore.setToken(""); //清空无效的token
counterStore.setUser(null);
dynamicRoutes.forEach((route) => {
if (route.name) {
router.removeRoute(route.name);
}
});
next({ path: "/login" }); // 跳转到登录页面
}
} else {
next();
}
});
export default router;
export { dynamicRoutes };
4.8 按钮级别
这里就是根据我们提供的权限自行添加判断(后续会更新)
5.使用场景
RBAC 在多用户的企业级应用、电商平台、金融系统等场景下应用广泛,特别是在系统权限需要多层级、精细化管理的情况下。对于快速发展的业务需求,RBAC 可以通过增加角色或权限来灵活应对。
6.总结
RBAC 模型在权限管理中极大地简化了权限分配的复杂性和维护成本,尤其在角色权限关系复杂的系统中尤为适用。通过用户、角色、权限的分层管理,RBAC 实现了灵活、模块化的权限控制,使得系统在安全性和管理便捷性上都得到了提升。此外,RBAC 的可扩展性也较高,可以根据系统需求进一步扩展角色或权限的粒度,灵活适应不同的业务场景。

9645

被折叠的 条评论
为什么被折叠?



