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
)

示例策略:

策略IDeffectsubject_exprresource_expractioncondition_json
1allowuser.role == “developer”resource.type == “device”control{“device_status”:“online”}
2denyuser.role == “tester”resource.status == “released”edit{}
3allowuser.department == “iot_ops”resource.tenant_id == user.tenant_idview{}

⚙️ 四、混合 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” 时允许 editRBAC + ABAC
开发工程师只能控制自己负责的设备role=“developer” 且 device.owner=user.id 时允许 controlABAC
运维仅能查看“在线”设备role=“operator” 且 device.status=“online” 时允许 viewABAC
租户管理员可管理租户下所有资源role=“tenant_admin” → all actions allowed within tenant_idRBAC
第三方API仅在工作时间可访问env.time ∈ [09:00,18:00]ABAC

🧩 六、系统架构设计建议

模块功能推荐技术
鉴权服务(Auth Service)统一权限校验(RBAC + ABAC 引擎)独立微服务,API 网关前置
策略引擎解析 JSON/表达式策略CasbinOPA (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)

✅ 八、总结对比

特性RBACABAC混合模式优势
简单性✅(基本用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. 测试流程(示例)

  1. 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. 设计说明与注意事项(生产级建议)

  1. 策略表达式引擎

    • 本例使用 jsonlogic-js 简洁好用,适合存储 JSON 策略。复杂场景可使用 OPA、Cedar、或自研表达式解析器。
  2. RBAC 与 ABAC 的分工

    • 推荐:RBAC 负责“粗粒度”许可(是否可尝试),ABAC 负责“细粒度”准入(状态、所有者、时间、通道、设备在线/离线等)。
    • 可先做 RBAC 快速拒绝(若角色没有静态 permission 则拒绝),再走 ABAC 精细判断。
  3. 策略优先级与冲突解决

    • 明确优先级规则(deny-overrides 常见),例:先判断 deny 策略,再 allow;或按 priority 排序并 first-match-wins。
  4. 性能与缓存

    • 把常用策略、角色映射缓存到 Redis,权限决策只读取最小必要数据。针对高吞吐场景,使用本地缓存 + TTL。
  5. 审计与可追踪

    • 每次权限决策都要产生日志(who, when, resource, action, policyMatched),便于事后追溯合规。
  6. 策略管理 UI

    • 提供可视化编辑器(条件树 + JSON 预览),并提供策略测试工具(输入 user/resource JSON,实时评估)。
  7. 多租户隔离

    • 所有查询要严格带上 tenant/project 过滤,避免越权访问。
  8. 安全

    • 不要在 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(若需要更强表达力或规则集成)。