在 IoT SaaS 系统(物联网多租户云平台)中,权限体系的设计至关重要,因为它不仅要满足传统的 用户-角色-权限 管理,还要处理 设备、产品、项目、组织、租户、数据流、操作权限 等多维场景。 单一的 RBAC 模型在 IoT 场景中往往不够灵活,因此需要引入 RBAC + ABAC 的混合设计。
下面将分 7 个部分为你完整讲解 IoT SaaS 系统中的 RBAC + ABAC 混合权限模型设计,并提供可直接落地的通用范例。
🧩 一、IoT SaaS 系统的权限需求特征
IoT 平台的典型多维结构如下:
租户(Tenant)
└── 项目(Project)
├── 产品(Product)
│ ├── 设备(Device)
│ ├── 功能点(Feature)
│ └── 数据流(Telemetry)
└── 成员(User)
主要权限控制目标:
维度 | 示例需求 |
---|---|
用户 → 资源 | 某个用户是否能查看设备详情 |
用户 → 动作 | 某个用户是否能远程控制设备 |
状态条件 | 设备在线时才允许下发指令 |
数据隔离 | 不同租户、项目、产品的数据隔离 |
动态策略 | 不同产品状态、设备类型、标签影响权限 |
运维与监管 | 部分角色仅能查看但不能操作设备 |
这些场景无法单靠 RBAC 静态角色覆盖,因此引入 ABAC 动态属性判定。
🧱 二、RBAC(角色访问控制)层设计
1. 核心思想
RBAC 控制 “谁可以做什么”,即:
用户 → 拥有角色 → 角色授予权限
在 IoT SaaS 场景中通常分三层:
层级 | 说明 | 示例 |
---|---|---|
全局角色 | SaaS 平台级 | 系统管理员、租户管理员 |
租户角色 | 每个租户范围 | 租户管理员、项目负责人 |
项目角色 | 项目内部 | 产品经理、开发、测试、运维 |
2. RBAC 基础表结构
-- 用户
t_user(id, username, email, tenant_id)
-- 租户
t_tenant(id, name, plan_type, status)
-- 项目
t_project(id, tenant_id, name, description)
-- 角色
t_role(id, scope ENUM('global','tenant','project'), code, name)
-- 权限
t_permission(id, resource, action, description)
-- 示例: ('device', 'view'), ('device', 'control'), ('product', 'edit')
-- 角色-权限 绑定
t_role_permission(role_id, permission_id)
-- 用户-角色-作用域 绑定
t_user_role_scope(
user_id INT,
role_id INT,
tenant_id INT NULL,
project_id INT NULL,
UNIQUE(user_id, role_id, tenant_id, project_id)
)
这一层提供静态权限绑定基础。
🧠 三、ABAC(基于属性的访问控制)层设计
1. ABAC 核心思想
ABAC 不仅关注用户身份,还关注资源和环境的属性。 访问决策基于策略表达式:
if (用户属性 + 资源属性 + 环境属性) 满足策略条件 → 允许访问
2. IoT 场景中的典型属性
属性类型 | 示例 |
---|---|
用户属性 | 所属租户、部门、岗位、标签、地理位置 |
资源属性 | 产品状态(开发中、发布)、设备状态(在线/离线)、所属项目 |
环境属性 | 当前时间、请求来源、IP、操作通道(Web/API/MQTT) |
3. 策略存储设计
t_policy(
id INT PRIMARY KEY,
effect ENUM('allow','deny'),
subject_expr VARCHAR, -- 用户属性表达式
resource_expr VARCHAR, -- 资源属性表达式
action VARCHAR, -- 动作
condition_json JSON, -- 其他条件(时间、通道等)
description TEXT
)
示例策略:
策略ID | effect | subject_expr | resource_expr | action | condition_json |
---|---|---|---|---|---|
1 | allow | user.role == “developer” | resource.type == “device” | control | {“device_status”:“online”} |
2 | deny | user.role == “tester” | resource.status == “released” | edit | {} |
3 | allow | user.department == “iot_ops” | resource.tenant_id == user.tenant_id | view | {} |
⚙️ 四、混合 RBAC + ABAC 判定逻辑
权限决策引擎流程:
用户请求 → 鉴权中间件
↓
提取用户身份(user_id、role、tenant、project)
↓
RBAC:判断角色是否有操作该资源的基础权限
↓
ABAC:检查附加属性条件(资源状态、时间、通道等)
↓
→ 允许 / 拒绝
伪代码示例:
def check_permission(user, action, resource):
# Step 1: 基于 RBAC
if not has_rbac_permission(user, resource, action):
return False
# Step 2: 加载 ABAC 策略
policies = load_abac_policies(action)
for p in policies:
if eval_expr(p.subject_expr, user) and eval_expr(p.resource_expr, resource):
if match_condition(p.condition_json, user, resource):
return p.effect == "allow"
return False
🔐 五、IoT SaaS 中的典型权限策略示例
场景 | 策略描述 | 类型 |
---|---|---|
产品经理可以编辑“开发中”的产品 | role=“product_manager” 且 product.status=“developing” 时允许 edit | RBAC + ABAC |
开发工程师只能控制自己负责的设备 | role=“developer” 且 device.owner=user.id 时允许 control | ABAC |
运维仅能查看“在线”设备 | role=“operator” 且 device.status=“online” 时允许 view | ABAC |
租户管理员可管理租户下所有资源 | role=“tenant_admin” → all actions allowed within tenant_id | RBAC |
第三方API仅在工作时间可访问 | env.time ∈ [09:00,18:00] | ABAC |
🧩 六、系统架构设计建议
模块 | 功能 | 推荐技术 |
---|---|---|
鉴权服务(Auth Service) | 统一权限校验(RBAC + ABAC 引擎) | 独立微服务,API 网关前置 |
策略引擎 | 解析 JSON/表达式策略 | Casbin、OPA (Open Policy Agent)、AWS Cedar |
策略存储 | 存储策略规则与映射关系 | MySQL / PostgreSQL / Redis Cache |
UI 管理界面 | 策略配置和可视化 | 前端策略编辑器(JSON 或条件表达式树) |
缓存层 | 缓存角色与策略匹配结果 | Redis + TTL |
审计日志 | 记录权限决策过程 | Elasticsearch / ClickHouse |
🧠 七、通用设计范例(可落地实现)
以 FastAPI + Casbin + MySQL 为例:
1. Casbin Policy Model
model.conf
:
[request_definition]
r = sub, obj, act, attr
[policy_definition]
p = sub, obj, act, attr, eft
[policy_effect]
e = some(where (p.eft == allow))
[matchers]
m = r.sub.role == p.sub && r.obj.type == p.obj && r.act == p.act && eval(p.attr)
2. Policy Example
policy.csv
:
p, product_manager, product, edit, r.obj.status == "developing"
p, developer, device, control, r.obj.owner == r.sub.id && r.obj.status == "online"
p, operator, device, view, r.obj.status == "online"
3. 应用逻辑
def authorize(user, resource, action):
adapter = casbin_sql_adapter.Adapter("mysql://...")
e = casbin.Enforcer("model.conf", adapter)
attr = {"obj": resource, "sub": user}
return e.enforce(user.role, resource.type, action, attr)
✅ 八、总结对比
特性 | RBAC | ABAC | 混合模式优势 |
---|---|---|---|
简单性 | ✅ | ❌ | ✅(基本用RBAC) |
灵活性 | ❌ | ✅ | ✅ |
动态条件支持 | ❌ | ✅ | ✅ |
维护成本 | 低 | 高 | 中 |
适合IoT多维访问控制 | 一般 | 优秀 | 最优 |
🚀 九、结语
RBAC 负责静态角色绑定,ABAC 负责动态策略判断。 在 IoT SaaS 系统中,这种组合能完美平衡性能与灵活性,支持:
- 多租户隔离
- 多角色分层控制
- 动态资源状态判定
- 操作通道差异化限制
- 可扩展策略表达式
混合 RBAC + ABAC — Node.js + MongoDB 实现(可直接落地)
下面给出一个开箱即用的参考实现,使用 Express + Mongoose + jsonlogic-js
做 ABAC 条件评估,结合 RBAC 的角色-权限静态映射。设计目标:简单、可扩展、便于在真实 IoT SaaS 中落地。
主要内容与文件结构(示例):
project/
├─ package.json
├─ src/
│ ├─ index.js # 启动
│ ├─ models/
│ │ ├─ User.js
│ │ ├─ Role.js
│ │ ├─ Permission.js
│ │ ├─ Project.js
│ │ ├─ Product.js
│ │ └─ Policy.js # ABAC 策略
│ ├─ services/
│ │ └─ enforcer.js # 权限决策引擎(RBAC+ABAC)
│ ├─ middleware/
│ │ └─ auth.js # 简单 JWT -> req.user
│ └─ routes/
│ └─ product.js
└─ seed.js # 生成测试数据
1. 依赖(package.json)
{
"name": "rbac-abac-node-mongo",
"version": "1.0.0",
"main": "src/index.js",
"scripts": {
"start": "node src/index.js",
"seed": "node seed.js"
},
"dependencies": {
"express": "^4.18.2",
"mongoose": "^7.0.0",
"jsonwebtoken": "^9.0.0",
"jsonlogic-js": "^1.2.2",
"lodash": "^4.17.21",
"body-parser": "^1.20.2"
}
}
安装:
npm install
2. Mongoose 模型(核心)
src/models/Role.js
const mongoose = require('mongoose');
const Schema = mongoose.Schema;
const RoleSchema = new Schema({
code: { type: String, unique: true }, // e.g. "tenant_admin", "product_manager", "developer"
name: String,
scope: { type: String, enum: ['global','tenant','project'], default: 'project' }
});
module.exports = mongoose.model('Role', RoleSchema);
src/models/Permission.js
const mongoose = require('mongoose');
const Schema = mongoose.Schema;
const PermissionSchema = new Schema({
resource: String, // e.g. "product", "device"
action: String, // e.g. "view","edit","control"
description: String
});
module.exports = mongoose.model('Permission', PermissionSchema);
src/models/User.js
const mongoose = require('mongoose');
const Schema = mongoose.Schema;
const UserRoleSchema = new Schema({
role: { type: Schema.Types.ObjectId, ref: 'Role' },
tenantId: { type: Schema.Types.ObjectId, ref: 'Tenant', required: false },
projectId: { type: Schema.Types.ObjectId, ref: 'Project', required: false }
}, { _id: false });
const UserSchema = new Schema({
username: String,
email: String,
passwordHash: String,
tenantId: { type: Schema.Types.ObjectId, ref: 'Tenant' },
attributes: Schema.Types.Mixed, // 任意用户属性(department, tags, etc)
roles: [UserRoleSchema]
});
module.exports = mongoose.model('User', UserSchema);
src/models/Project.js
const mongoose = require('mongoose');
const Schema = mongoose.Schema;
const ProjectSchema = new Schema({
tenantId: { type: Schema.Types.ObjectId, ref: 'Tenant' },
name: String
});
module.exports = mongoose.model('Project', ProjectSchema);
src/models/Product.js
const mongoose = require('mongoose');
const Schema = mongoose.Schema;
const ProductSchema = new Schema({
projectId: { type: Schema.Types.ObjectId, ref: 'Project' },
name: String,
status: { type: String, enum: ['developing','testing','released','archived'], default: 'developing' },
ownerUserId: { type: Schema.Types.ObjectId, ref: 'User' },
attributes: Schema.Types.Mixed
});
module.exports = mongoose.model('Product', ProductSchema);
src/models/Policy.js
(ABAC 策略)
const mongoose = require('mongoose');
const Schema = mongoose.Schema;
/*
Policy doc fields:
- name
- effect: 'allow'|'deny'
- subject: e.g. "role == 'developer' || attributes.department == 'iot_ops'"
- resource: e.g. "resource.type == 'product'"
- action: e.g. "edit"
- condition: JSONLogic expression for runtime check involving user, resource, env
- priority: higher wins (optional)
*/
const PolicySchema = new Schema({
name: String,
effect: { type: String, enum: ['allow','deny'], default: 'allow' },
subjectExpr: String,
resourceExpr: String,
action: String,
condition: Schema.Types.Mixed,
priority: { type: Number, default: 0 }
});
module.exports = mongoose.model('Policy', PolicySchema);
注:上面
subjectExpr
/resourceExpr
是用于快速筛选策略的字符串表达式(可用作简单索引),而condition
使用 JSONLogic 来表达复杂条件(便于评估)。
3. 权限决策引擎 — enforcer(核心逻辑)
src/services/enforcer.js
const jsonLogic = require('jsonlogic-js');
const _ = require('lodash');
const Policy = require('../models/Policy');
const Permission = require('../models/Permission');
/**
* checkPermission: 混合 RBAC + ABAC 校验入口
* @param {Object} user - Mongoose user doc (包含 roles[])
* @param {Object} resource - 资源对象(例如 product doc)
* @param {String} action - 动作 (e.g. 'edit')
* @param {Object} env - 环境属性 (e.g. {now: '2025-10-13T..', channel: 'api'})
*/
async function checkPermission(user, resource, action, env = {}) {
// 1) RBAC 初筛:用户是否拥有角色,并且角色拥有基础 permission?
// 这里的简单策略:先把角色对应的 permission 存在 policy 或 role->permission 表中
// 在本示例,我们用 policy 存放高级策略;也可用 role->permission 作为白名单优化。
// 为保持通用性:先允许 RBAC 通过(假设角色允许),再靠 ABAC 精细控制。
// 如果你需要严格的 RBAC 拒绝,则先检查 role_permission 表(未实现示例)
// 2) 加载可能匹配的策略 (action + resource.type)
const resourceType = resource.type || resource._doc && resource._doc.constructor.modelName.toLowerCase() || 'product';
const policies = await Policy.find({ action, $or: [
{ resourceExpr: new RegExp(resourceType, 'i') },
{ resourceExpr: '' }, // 通配
]}).sort({ priority: -1 }).lean();
// 3) Evaluate policies sequentially (priority)
// Build evaluation context
const ctx = {
user: {
id: user._id.toString(),
username: user.username,
tenantId: user.tenantId ? user.tenantId.toString() : null,
roles: (user.roles || []).map(r => r.role && (r.role.code || r.role.toString())),
attributes: user.attributes || {}
},
resource: {
id: resource._id ? resource._id.toString() : null,
type: resourceType,
status: resource.status,
ownerUserId: resource.ownerUserId ? resource.ownerUserId.toString() : null,
attributes: resource.attributes || {}
},
env
};
// If no policies found, fallback deny
if (!policies || policies.length === 0) {
return false;
}
// Evaluate each policy: first match wins (we assume priority sorted)
for (const p of policies) {
// quick subject/resource text match (optional) to skip unnecessary jsonlogic
if (p.subjectExpr) {
// naive eval: allow simple contains / role== checks
// For production, use safe expression evaluators. Here we do simple checks:
// if subjectExpr contains role name, check user.roles includes it
const tokens = p.subjectExpr.match(/\b[a-zA-Z0-9_]+\b/g) || [];
let skip = true;
for (const t of tokens) {
if (t.toLowerCase() === 'role' || t.toLowerCase() === 'roles') { skip = false; break; }
if ((ctx.user.roles || []).includes(t)) { skip = false; break; }
}
// not strict — keep evaluating condition anyway
}
// Evaluate JSONLogic condition if present
let matched = true;
if (p.condition && Object.keys(p.condition).length > 0) {
try {
matched = jsonLogic.apply(p.condition, ctx);
} catch (err) {
matched = false;
}
}
if (matched) {
return p.effect === 'allow';
}
}
// default deny
return false;
}
module.exports = {
checkPermission
};
说明:
- 我们使用
Policy
表来表达 ABAC 规则(JSONLogic)。 - 策略优先级 (priority) 决定先后。
first-match-wins
。(你也可以实现 deny-overrides 逻辑)
4. 简单鉴权中间件(示例)
src/middleware/auth.js
const jwt = require('jsonwebtoken');
const User = require('../models/User');
const SECRET = 'changeme_secret'; // production use env var
async function authMiddleware(req, res, next) {
const auth = req.headers.authorization;
if (!auth) return res.status(401).json({ error: 'missing auth' });
const token = auth.split(' ')[1];
try {
const decoded = jwt.verify(token, SECRET);
const user = await User.findById(decoded.sub).populate('roles.role').lean();
if (!user) return res.status(401).json({ error: 'invalid user' });
req.user = user;
next();
} catch (err) {
return res.status(401).json({ error: 'invalid token' });
}
}
module.exports = authMiddleware;
module.exports.SECRET = SECRET;
5. API 路由示例(产品资源)
src/routes/product.js
const express = require('express');
const router = express.Router();
const Product = require('../models/Product');
const enforcer = require('../services/enforcer');
router.get('/:id', async (req, res) => {
const prod = await Product.findById(req.params.id).lean();
if (!prod) return res.status(404).send('not found');
const allowed = await enforcer.checkPermission(req.user, prod, 'view', {now: new Date().toISOString(), channel: 'api'});
if (!allowed) return res.status(403).send('forbidden');
res.json(prod);
});
router.post('/:id/edit', async (req, res) => {
const prod = await Product.findById(req.params.id);
if (!prod) return res.status(404).send('not found');
const allowed = await enforcer.checkPermission(req.user, prod, 'edit', {now: new Date().toISOString(), channel: 'api'});
if (!allowed) return res.status(403).send('forbidden');
// perform edit (simple example)
prod.name = req.body.name || prod.name;
await prod.save();
res.json(prod);
});
module.exports = router;
6. 启动文件
src/index.js
const express = require('express');
const bodyParser = require('body-parser');
const mongoose = require('mongoose');
const auth = require('./middleware/auth');
const productRoutes = require('./routes/product');
const app = express();
app.use(bodyParser.json());
mongoose.connect('mongodb://localhost:27017/rbac_abac_example', { useNewUrlParser: true, useUnifiedTopology: true });
app.use('/product', auth, productRoutes);
app.listen(3000, () => {
console.log('listening at 3000');
});
7. 策略示例(seed 或 管理界面)
seed.js
(创建示例策略、用户与产品)
const mongoose = require('mongoose');
const Role = require('./src/models/Role');
const User = require('./src/models/User');
const Policy = require('./src/models/Policy');
const Product = require('./src/models/Product');
async function seed() {
await mongoose.connect('mongodb://localhost:27017/rbac_abac_example');
// 清空
await Role.deleteMany({});
await User.deleteMany({});
await Policy.deleteMany({});
await Product.deleteMany({});
// 角色
const devRole = await Role.create({ code: 'developer', name: 'Developer', scope: 'project' });
const pmRole = await Role.create({ code: 'product_manager', name: 'Product Manager', scope: 'project' });
const operatorRole = await Role.create({ code: 'operator', name: 'Operator', scope: 'project' });
// 用户
const alice = await User.create({
username: 'alice',
email: 'alice@example.com',
tenantId: null,
attributes: { department: 'engineering' },
roles: [{ role: devRole._id }]
});
const bob = await User.create({
username: 'bob',
email: 'bob@example.com',
attributes: { department: 'product' },
roles: [{ role: pmRole._id }]
});
// 产品
const p = await Product.create({
name: 'sensor-x',
status: 'developing',
ownerUserId: alice._id,
attributes: { model: 'x100' }
});
// 策略:开发者可以 edit 状态为 developing 的产品(JSONLogic)
await Policy.create({
name: 'dev-can-edit-developing',
effect: 'allow',
subjectExpr: 'developer',
resourceExpr: 'product',
action: 'edit',
condition: {
"and": [
{ "in": [ "developer", { "var": "user.roles" } ] },
{ "==": [ { "var": "resource.status" }, "developing" ] }
]
},
priority: 10
});
// 策略:产品经理可以 edit 所有状态为 developing 或 testing
await Policy.create({
name: 'pm-edit-dev-test',
effect: 'allow',
subjectExpr: 'product_manager',
resourceExpr: 'product',
action: 'edit',
condition: {
"and": [
{ "in": [ "product_manager", { "var": "user.roles" } ] },
{ "in": [ { "var": "resource.status" }, ["developing", "testing"] ] }
]
},
priority: 10
});
// 策略:operator 只能 view online devices (示例展示)
await Policy.create({
name: 'operator-view-online',
effect: 'allow',
subjectExpr: 'operator',
resourceExpr: 'device',
action: 'view',
condition: {
"and": [
{ "in": [ "operator", { "var": "user.roles" } ] },
{ "==": [ { "var": "resource.status" }, "online" ] }
]
},
priority: 5
});
console.log('seeded. user alice id:', alice._id.toString(), 'product id:', p._id.toString());
process.exit(0);
}
seed();
运行:
node seed.js
8. 测试流程(示例)
- Seed 完成后,为用户生成 JWT(此示例跳过生成脚本,手动用 jwt.sign):
const jwt = require('jsonwebtoken');
const token = jwt.sign({ sub: '<alice_id>' }, 'changeme_secret');
console.log(token);
然后使用 Authorization: Bearer <token>
调用:
GET /product/:id
→ 会触发view
策略(如果有)POST /product/:id/edit
→ 将触发edit
策略;alice 是 developer,产品状态为developing
,应允许。
9. 设计说明与注意事项(生产级建议)
策略表达式引擎
- 本例使用
jsonlogic-js
简洁好用,适合存储 JSON 策略。复杂场景可使用 OPA、Cedar、或自研表达式解析器。
- 本例使用
RBAC 与 ABAC 的分工
- 推荐:RBAC 负责“粗粒度”许可(是否可尝试),ABAC 负责“细粒度”准入(状态、所有者、时间、通道、设备在线/离线等)。
- 可先做 RBAC 快速拒绝(若角色没有静态 permission 则拒绝),再走 ABAC 精细判断。
策略优先级与冲突解决
- 明确优先级规则(deny-overrides 常见),例:先判断 deny 策略,再 allow;或按 priority 排序并 first-match-wins。
性能与缓存
- 把常用策略、角色映射缓存到 Redis,权限决策只读取最小必要数据。针对高吞吐场景,使用本地缓存 + TTL。
审计与可追踪
- 每次权限决策都要产生日志(who, when, resource, action, policyMatched),便于事后追溯合规。
策略管理 UI
- 提供可视化编辑器(条件树 + JSON 预览),并提供策略测试工具(输入 user/resource JSON,实时评估)。
多租户隔离
- 所有查询要严格带上 tenant/project 过滤,避免越权访问。
安全
- 不要在
subjectExpr
/resourceExpr
里直接eval
任意字符串(本示例为简化)。使用安全的表达式语言(或 JSONLogic)更可靠。
- 不要在
10. 小结
提供了一个Node.js + MongoDB 的完整参考实现思路:Mongoose 模型、ABAC 策略(JSONLogic)、enforcer 决策流程、Express 中间件与示例路由、Seed 数据与测试说明。
你可以基于此:
- 把
Policy
扩展为支持多资源、更多条件(time windows、IP、request channel、device metrics)。 - 增加 Role→Permission 表做快速 RBAC 拒绝(提高性能)。
- 把策略存储与校验迁移到 OPA / Casbin(若需要更强表达力或规则集成)。
- 把