ABAC 模型在列表查询中的应用

查询接口使用 ABAC 策略模型 ABAC 策略处理逻辑扩展为一个 通用 Node.js 类,它可以根据策略自动生成 MongoDB 查询过滤条件,用于在「列表查询接口」中动态控制数据可见性。 🎯 设计目标 这个类要做到以下几点: 支持加载多条策略; 根据用户信息(roles, attributes 等)和操作类型(action)生成 MongoDB 查询过滤条件; 支持组合策略(多策略叠加); 支持多种条件表达式(==, !=, in, not in, and, or, >,<,>=,<=等); 输出标准 MongoDB 查询对象,可直接传入 Model.find(filter)。 🧩 策略示例 const policies = [ { 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 }, { name: 'manager-view-all', effect: 'allow', subjectExpr: 'manager', resourceExpr: 'device', action: 'view', condition: { in: ['manager', { var: 'user.roles' }] }, priority: 10 } ]; 🧱 通用 ABAC 查询构造类 class ABACQueryBuilder { constructor(policies = []) { this.policies = policies; } /** * 生成 MongoDB 查询过滤条件 * @param {Object} user - 用户信息,如 { id, roles, projectId, department } * @param {String} action - 动作,如 'view', 'edit' * @param {String} resource - 资源类型,如 'device', 'product' * @returns {Object} MongoDB 查询条件 */ buildQuery(user, action, resource) { const matchedPolicies = this.policies .filter(p => p.action === action && p.resourceExpr === resource) .sort((a, b) => b.priority - a.priority); if (matchedPolicies.length === 0) { return { _id: { $exists: false } }; // 无策略则拒绝访问 } const allowFilters = []; const denyFilters = []; for (const policy of matchedPolicies) { const condition = this._evaluateCondition(policy.condition, user); if (!condition) continue; const filter = this._conditionToMongo(policy.condition, user); if (policy.effect === 'allow') { allowFilters.push(filter); } else { denyFilters.push(filter); } } // 最终 MongoDB 查询组合规则 let query = {}; if (allowFilters.length > 0) { query = { $or: allowFilters }; } if (denyFilters.length > 0) { query = { $and: [query, { $nor: denyFilters }] }; } return query; } /** * 将条件表达式转换为 MongoDB 查询过滤条件 */ _conditionToMongo(condition, user) { if (!condition) return {}; if (condition.and) { return { $and: condition.and.map(c => this._conditionToMongo(c, user)) }; } if (condition.or) { return { $or: condition.or.map(c => this._conditionToMongo(c, user)) }; } const key = Object.keys(condition)[0]; const [left, right] = condition[key]; const leftVal = this._resolveVar(left, user); const rightVal = this._resolveVar(right, user); switch (key) { case '==': if (this._isResourceVar(left)) { return { [leftVal]: rightVal }; } else if (this._isResourceVar(right)) { return { [rightVal]: leftVal }; } return {}; case '!=': if (this._isResourceVar(left)) { return { [leftVal]: { $ne: rightVal } }; } return {}; case 'in': if (this._isResourceVar(left)) { return { [leftVal]: { $in: rightVal } }; } else if (this._isResourceVar(right)) { return { [rightVal]: { $in: leftVal } }; } return {}; case 'not in': if (this._isResourceVar(left)) { return { [leftVal]: { $nin: rightVal } }; } return {}; case '>': if (this._isResourceVar(left)) { return { [leftVal]: { $gt: rightVal } }; } break; case '>=': if (this._isResourceVar(left)) { return { [leftVal]: { $gte: rightVal } }; } break; case '<': if (this._isResourceVar(left)) { return { [leftVal]: { $lt: rightVal } }; } break; case '<=': if (this._isResourceVar(left)) { return { [leftVal]: { $lte: rightVal } }; } break; default: return {}; } return {}; } /** * 判断 { var: 'xxx' } 是否为资源变量 */ _isResourceVar(value) { return typeof value === 'object' && value.var && value.var.startsWith('resource.'); } /** * 从 { var: 'user.xxx' } 中解析变量 */ _resolveVar(expr, user) { if (typeof expr !== 'object' || !expr.var) return expr; const path = expr.var.split('.'); if (path[0] === 'user') { return path.slice(1).reduce((obj, key) => obj?.[key], user); } else if (path[0] === 'resource') { return path.slice(1).join('.'); // 资源字段返回为路径字符串 } return expr; } /** * 简单评估条件(判断用户是否符合该策略) */ _evaluateCondition(condition, user) { if (!condition) return true; if (condition.and) return condition.and.every(c => this._evaluateCondition(c, user)); if (condition.or) return condition.or.some(c => this._evaluateCondition(c, user)); const key = Object.keys(condition)[0]; const [left, right] = condition[key]; const leftVal = this._resolveVar(left, user); const rightVal = this._resolveVar(right, user); switch (key) { case '==': return leftVal === rightVal; case '!=': return leftVal !== rightVal; case 'in': return Array.isArray(rightVal) ? rightVal.includes(leftVal) : false; case 'not in': return Array.isArray(rightVal) ? !rightVal.includes(leftVal) : false; case '>': return leftVal > rightVal; case '>=': return leftVal >= rightVal; case '<': return leftVal < rightVal; case '<=': return leftVal <= rightVal; default: return false; } } } ✅ 使用示例 const user = { id: 'u1', roles: ['operator'], projectId: 'p1' }; const qb = new ABACQueryBuilder(policies); // 生成设备列表过滤条件 const mongoFilter = qb.buildQuery(user, 'view', 'device'); console.log(JSON.stringify(mongoFilter, null, 2)); /* 输出: { "$or": [ { "status": "online" } ] } */ // 可直接用于 Mongoose 查询 // DeviceModel.find(mongoFilter) 🧠 特点总结 策略驱动查询:无须在代码中硬编码角色逻辑; 灵活扩展:新增策略只需在数据库或配置中定义; 兼容性好:输出标准 MongoDB 查询条件; 安全可控:未匹配策略默认拒绝访问。 非常好 👍,我们现在在上面的 ABACQueryBuilder 基础上,扩展出一个适用于 Node.js + Express + MongoDB (Mongoose) 的 通用权限中间件系统。 ...

2025年10月18日 · 8 分钟 · Eric

混合型权限系统(RBAC + ABAC Hybrid Access Control)

在 IoT SaaS 系统(物联网多租户云平台)中,权限体系的设计至关重要,因为它不仅要满足传统的 用户-角色-权限 管理,还要处理 设备、产品、项目、组织、租户、数据流、操作权限 等多维场景。 单一的 RBAC 模型在 IoT 场景中往往不够灵活,因此需要引入 RBAC + ABAC 的混合设计。 下面将分 7 个部分为你完整讲解 IoT SaaS 系统中的 RBAC + ABAC 混合权限模型设计,并提供可直接落地的通用范例。 🧩 一、IoT SaaS 系统的权限需求特征 IoT 平台的典型多维结构如下: 租户(Tenant) └── 项目(Project) ├── 产品(Product) │ ├── 设备(Device) │ ├── 功能点(Feature) │ └── 数据流(Telemetry) └── 成员(User) 主要权限控制目标: 维度 示例需求 用户 → 资源 某个用户是否能查看设备详情 用户 → 动作 某个用户是否能远程控制设备 状态条件 设备在线时才允许下发指令 数据隔离 不同租户、项目、产品的数据隔离 动态策略 不同产品状态、设备类型、标签影响权限 运维与监管 部分角色仅能查看但不能操作设备 这些场景无法单靠 RBAC 静态角色覆盖,因此引入 ABAC 动态属性判定。 ...

2025年10月13日 · 10 分钟 · Eric