在 Node.js Express 项目中实现 RBAC(基于角色的访问控制)权限控制,核心在于将权限判断逻辑从业务代码中剥离,通过中间件统一拦截。最稳妥的方式是引入成熟的权限库配合中间件,而不是在每个路由里硬编码判断。
先说结论:对于大多数业务场景,建议使用现成的权限库(如 accesscontrol 或 casl)结合 Express 中间件来实现,避免重复造轮子。
- 适合:需要区分管理员、编辑、普通用户等多角色权限的 Web 应用
- 前提:项目需已有用户认证体系(如 JWT),权限控制需建立在认证之后
- 建议:权限定义尽量与业务逻辑分离,方便后期调整角色而不修改代码
环境准备与依赖安装
确保项目已初始化,安装常用的权限控制库。accesscontrol 是 Node.js 生态中较为成熟的 RBAC 实现库之一。
npm install accesscontrol安装完成后,检查 package.json 确认依赖已正确添加。若使用 TypeScript,还需安装对应的类型定义包。
认证上下文集成
RBAC 依赖当前用户的身份信息(通常是角色 role)。在权限校验前,必须确保请求已通过认证中间件,并将用户信息注入到 req.user 中。
以下是一个简化的 JWT 认证中间件示例,用于展示 req.user 的来源:
app.use((req, res, next) => {
const token = req.headers['authorization']?.split(' ')[1];
if (token) {
// 验证 token 逻辑...
// 假设验证通过后解析出用户信息
req.user = { id: 1, role: 'editor', name: 'user1' };
}
next();
});注意:权限中间件必须挂载在认证中间件之后,否则 req.user 将为 undefined,导致权限校验失败或报错。
权限配置与中间件实现
1. 定义角色与权限
在项目初始化阶段配置权限矩阵。避免在业务代码中硬编码角色名,而是通过配置对象管理。
const AccessControl = require('accesscontrol');
const ac = new AccessControl();
ac.grant('viewer')
.readAny('profile')
.readAny('post');
ac.grant('editor')
.extend('viewer')
.createAny('post')
.updateAny('post');
ac.grant('admin')
.extend('editor')
.deleteAny('post')
.readAny('user');2. 编写可复用中间件
创建一个工厂函数生成权限校验中间件。该函数接收资源(resource)和操作(action),返回标准的 Express 中间件函数。
const checkPermission = (ac, resource, action) => {
return (req, res, next) => {
if (!req.user || !req.user.role) {
return res.status(401).json({ error: 'Unauthorized' });
}
const permission = ac.can(req.user.role)[action](resource);
if (permission.granted) {
next();
} else {
res.status(403).json({ error: 'Access denied' });
}
};
};路由集成
将生成的中间件应用到具体路由上。确保中间件顺序为:认证中间件 -> 权限中间件 -> 业务逻辑。
// 只有 editor 和 admin 可以创建文章
app.post('/posts',
authMiddleware,
checkPermission(ac, 'post', 'createAny'),
(req, res) => {
res.json({ message: 'Post created' });
}
);验证方法
启动服务后,使用 curl 或 Postman 模拟不同角色的请求。关键在于传递正确的 Authorization Header。
测试无权限访问:
使用仅拥有 'viewer' 角色的 Token 尝试创建资源:
curl -X POST http://localhost:3000/posts \
-H "Authorization: Bearer <viewer_token>" \
-H "Content-Type: application/json" \
-d "{\"title\": \"test\"}"预期返回 403 状态码。
测试有权限访问:
更换为 'editor' 角色的 Token 再次请求:
curl -X POST http://localhost:3000/posts \
-H "Authorization: Bearer <editor_token>" \
-H "Content-Type: application/json" \
-d "{\"title\": \"test\"}"预期返回 200 状态码及业务数据。
常见坑
1. 认证与授权顺序颠倒:必须先执行认证中间件获取 req.user,再执行权限中间件。顺序错误会导致无法获取用户角色。
2. 硬编码角色名:避免在业务逻辑中写 if (req.user.role === 'admin')。应通过权限库配置管理,方便后期调整。
3. 忽略异步操作:如果权限校验需要查询数据库(如动态权限),中间件内部必须正确处理异步流程,使用 await 或确保回调正确调用 next()。
4. 权限粒度过细:初期不要设计过细的权限(如精确到字段级),否则维护成本极高。建议按资源模块划分,后续再按需扩展。
参考文档
- AccessControl.js 官方文档:https://www.npmjs.com/package/accesscontrol
- Express 中间件编写指南:https://expressjs.com/en/guide/using-middleware.html