Node.js RBAC Tutorial: Role-Based Access Control in Express
本教程为一个用 Express 构建的 REST API 加上基于角色的访问控制(RBAC)。最后,三个角色(admin、editor、viewer)可以对不同路由使用不同的 HTTP 方法,规则放在一个策略文件中,修改时无需改动路由处理函数。
同样的模型和策略在所有 Casbin 实现中都能使用,所以可以在本教程的 Go、Python、Java 和 Rust 版本中复用。如果使用 NestJS,见 在 NestJS 中实现 RBAC。
1. 安装
npm init -y
npm install express casbin
2. 编写模型
模型说明请求是什么样的,以及如何与策略匹配。保存为 model.conf:
[request_definition]
r = sub, obj, act
[policy_definition]
p = sub, obj, act
[role_definition]
g = _, _
[policy_effect]
e = some(where (p.eft == allow))
[matchers]
m = g(r.sub, p.sub) && keyMatch2(r.obj, p.obj) && regexMatch(r.act, p.act)
r = sub, obj, act:每个请求由用户、URL 路径和 HTTP 方法组成。g = _, _:用户 可以被分配到角色。g(r.sub, p.sub):请求的用户必须拥有策略规则中指定的角色。keyMatch2匹配/api/articles/:id这样的路径,regexMatch让一条规则可以列出多个方法。其他内置匹配函数见 函数。
3. 编写策略
保存为 policy.csv:
p, admin, /api/*, (GET)|(POST)|(PUT)|(DELETE)
p, editor, /api/articles, (GET)|(POST)
p, editor, /api/articles/:id, (GET)|(PUT)
p, viewer, /api/articles, GET
p, viewer, /api/articles/:id, GET
g, alice, admin
g, bob, editor
g, carol, viewer
以 p 开头的行授予某个角色对某个路径和一组方法的访问权。以 g 开头的行把用户分配到角色。
4. 添加中间件
中间件用用户、路径和方法调用 enforce,Casbin 判定为否时返回 403 Forbidden。
server.js
const express = require("express");
const { newEnforcer } = require("casbin");
// authorize asks Casbin whether the current user may call this route.
function authorize(enforcer) {
return async (req, res, next) => {
// Replace this with the user from your session or JWT.
const user = req.get("X-User") || "";
try {
const allowed = await enforcer.enforce(user, req.baseUrl + req.path, req.method);
if (!allowed) {
return res.status(403).json({ error: "forbidden" });
}
next();
} catch (err) {
next(err);
}
};
}
async function main() {
const enforcer = await newEnforcer("model.conf", "policy.csv");
const app = express();
const api = express.Router();
api.get("/articles", (req, res) => res.json({ articles: [] }));
api.post("/articles", (req, res) => res.status(201).json({ created: true }));
api.put("/articles/:id", (req, res) => res.json({ updated: req.params.id }));
api.delete("/articles/:id", (req, res) => res.json({ deleted: req.params.id }));
app.use("/api", authorize(enforcer), api);
app.listen(8080, () => console.log("listening on :8080"));
}
main();
备注
Inside a mounted router, req.path does not include the mount point. req.baseUrl + req.path gives the full path (/api/articles) that the policy refers to.
示例用 X-User 请求头保持简短。 Casbin 只负责授权;实际应用中,用户名来自你的认证层,例如经过校验的 JWT 或会话。
5. 试一试
node server.js
curl -i -X POST -H "X-User: bob" http://localhost:8080/api/articles # 201, editors can create
curl -i -X DELETE -H "X-User: bob" http://localhost:8080/api/articles/1 # 403, editors cannot delete
curl -i -X DELETE -H "X-User: alice" http://localhost:8080/api/articles/1 # 200, admins can
curl -i -X POST -H "X-User: carol" http://localhost:8080/api/articles # 403, viewers are read-only
6. 在运行时修改角色
角色和权限都是数据,可以在服务器运行时修改:
await enforcer.addRoleForUser("dave", "editor"); // dave becomes an editor
await enforcer.deleteRoleForUser("bob", "editor"); // bob is no longer an editor
await enforcer.addPolicy("editor", "/api/comments", "POST"); // editors may now post comments
const roles = await enforcer.getRolesForUser("alice"); // ["admin"]
完整列表见 RBAC API 和 Management API。
下一步
- 把策略存到数据库中。 用 Sequelize、TypeORM、Prisma、Mongoose 等的 适配器 代替
policy.csv。这样通过 API 做的策略修改会自动保存。 - 运行多个实例。 使用 watcher,这样任何一个实例修改策略后,其他实例都会重新加载。
- 显示或隐藏界面元素。 前端用法 介绍如何用
casbin.js把用户的权限发送到浏览器。 - 多租户应用。 带域的 RBAC 让用户在不同租户中拥有不同角色。
- 基于属性的规则。 把角色与 ABAC 结合,例如“编辑只能修改自己的文章”。 ABAC 教程 一步步演示了这一点。
- 现成的中间件。 中间件列表 中有 Express、Koa、NestJS、Fastify 等框架的插件。
Comments are powered by giscus and GitHub Discussions. They load from giscus.app and GitHub only after you click the button below.