跳转至主要内容

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.