4、ABAC模型实战:属性定义与策略引擎设计
ABAC,说白了就是“看人下菜碟”的精细化版本。它不像RBAC那样只盯着角色,而是综合判断谁、对什么、在什么环境下,最终决定能不能干那件事。
我记得刚接手一个跨国项目时,业务方要求“中国区经理在工作时间能看到销售报表,但海外分公司经理只能看本地数据”。用RBAC得建一堆角色,用ABAC呢?三条策略就搞定了。这就是属性的力量。
4.1 三类核心属性定义
ABAC的基石就是属性。我习惯把它们分成三组:主体、资源、环境。你想想看,任何权限判断都逃不出这三个维度。
4.1.1 主体属性(Subject Attributes)
主体就是“谁”。不只是用户ID,而是能描述用户特征的一切标签。
| 属性类别 | 示例 | 说明 |
|---|---|---|
| 身份标识 | userId, employeeId | 唯一标识,基础中的基础 |
| 组织属性 | 部门、职级、汇报线 | 我在项目中常用部门+职级做粗粒度过滤 |
| 地域属性 | 办公城市、国家、时区 | 数据合规场景必备,比如GDPR |
| 信任等级 | 认证强度、设备指纹分 | 高风险操作要求高信任等级 |
| 临时标签 | 是否新人、是否借调 | 动态赋予,用完即弃 |
我的经验:主体属性别一开始就搞太细。我见过有人定义了50多个属性,结果策略写起来自己都晕。先抓核心的5-8个,跑通了再扩展。
4.1.2 资源属性(Resource Attributes)
资源就是“对什么”。文件、API、数据库记录,都得有标签。
// 资源属性定义示例(JSON Schema)
{
"resourceType": "document",
"attributes": {
"classification": { "type": "string", "enum": ["公开", "内部", "机密", "绝密"] },
"ownerDept": { "type": "string" },
"createdAt": { "type": "string", "format": "date-time" },
"region": { "type": "string" },
"tags": { "type": "array", "items": { "type": "string" } }
}
}
嗯,这里要注意:资源属性最好由数据所有者维护,而不是安全团队拍脑袋。我曾经遇到一个项目,安全团队给所有文档打了“机密”标签,结果策略全失效了——因为没人能访问任何文档。
4.1.3 环境属性(Environment Attributes)
环境就是“在什么情况下”。这是ABAC最灵活的地方,也是很多人容易忽略的。
- 时间维度:工作时间、节假日、季度末
- 位置维度:内网IP、办公区WiFi、VPN接入
- 风险维度:当前登录失败次数、异常行为评分
- 设备维度:是否公司设备、操作系统版本、是否越狱
避坑指南:我曾经把环境属性全部放在策略引擎里实时计算,结果每次请求都要查5个外部系统,接口响应时间从20ms飙到800ms。后来加了本地缓存+异步刷新,才压回来。环境属性尽量用本地可获取的数据。
4.2 策略引擎设计
策略引擎是ABAC的大脑。我习惯把它拆成四个模块:策略管理、策略解析、策略评估、决策缓存。
4.2.1 策略模型设计
我推荐用目标-规则-条件三层结构。别把策略写成if-else面条代码。
// 策略模型示例
{
"policyId": "POLICY-001",
"name": "中国区经理访问销售报表",
"target": {
"subject": { "department": "销售部", "level": "经理及以上" },
"resource": { "type": "report", "classification": "内部" },
"environment": { "region": "中国", "timeRange": ["09:00", "18:00"] }
},
"rules": [
{
"effect": "Permit",
"condition": {
"allOf": [
{ "subject.region": { "equals": "中国" } },
{ "resource.ownerDept": { "equals": "销售部" } }
]
}
}
]
}
核心原则:策略的目标(Target)做粗筛,条件(Condition)做细判。目标不匹配直接跳过,性能能提升一个数量级。
4.2.2 评估算法
策略评估不是简单的“找到一条就返回”。我习惯用组合决策算法:
- 策略匹配:根据目标过滤出候选策略集
- 优先级排序:按策略优先级、精确度排序
- 条件求值:逐一评估每条策略的条件表达式
- 决策合并:处理多条匹配策略的冲突(Permit优先 / Deny优先 / 首次匹配)
我曾经踩过的坑:默认用了“Permit优先”,结果安全审计发现一条宽松策略覆盖了所有Deny策略。后来改成“Deny优先+显式Permit”,才符合最小权限原则。默认策略一定要设为Deny。
4.3 动态权限计算实战
动态计算是ABAC的杀手锏。用户属性变了、资源标签改了、环境风险升高了,权限立刻跟着变。不需要改代码,不需要重新部署。
4.3.1 实时计算 vs 预计算
| 模式 | 适用场景 | 延迟 | 一致性 | 我推荐 |
|---|---|---|---|---|
| 实时计算 | 高频变更、风险敏感 | 高(5-50ms) | 强一致 | API鉴权、支付操作 |
| 预计算+缓存 | 低频变更、批量授权 | 低(<1ms) | 最终一致 | 菜单展示、报表列表 |
| 混合模式 | 大部分场景 | 可控 | 可调 | 我目前都在用 |
4.3.2 性能优化技巧
动态计算最怕的就是慢。我分享几个实战经验:
- 属性预取:在策略评估前,批量加载所有需要的属性,避免N+1查询
- 表达式编译:策略加载时就把条件表达式编译成可执行代码,别每次都解析JSON
- 短路评估:遇到Deny直接返回,不再评估后续策略
- 决策缓存:对相同的(主体,资源,环境)三元组缓存结果,设置合理的TTL
// 决策缓存伪代码
public class DecisionCache {
private Cache<DecisionKey, DecisionResult> cache;
public DecisionResult evaluate(RequestContext ctx) {
DecisionKey key = buildKey(ctx);
DecisionResult cached = cache.get(key);
if (cached != null && !cached.isExpired()) {
return cached; // 缓存命中
}
DecisionResult result = policyEngine.evaluate(ctx);
cache.put(key, result, Duration.ofSeconds(30)); // 30秒TTL
return result;
}
}
我的习惯:缓存TTL别设太长。我一般设30-60秒,既扛住峰值流量,又保证属性变更后能快速生效。对于风险敏感操作(比如转账),直接走实时计算,不走缓存。
4.3.3 动态属性注入
有些属性不是静态存储的,而是实时计算出来的。比如“用户当前风险评分”。
// 动态属性提供者接口
public interface DynamicAttributeProvider {
String getAttributeName();
Object resolve(Subject subject, Resource resource, Environment env);
}
// 示例:计算用户风险评分
public class RiskScoreProvider implements DynamicAttributeProvider {
@Override
public String getAttributeName() { return "riskScore"; }
@Override
public Object resolve(Subject subject, Resource resource, Environment env) {
int failedLogins = env.get("recentFailedLogins");
boolean fromNewDevice = !subject.getDevices().contains(env.get("deviceId"));
// 简单评分逻辑
return (failedLogins > 3 ? 30 : 0) + (fromNewDevice ? 20 : 0);
}
}
嗯,这里要注意:动态属性提供者一定要有超时保护。我见过一个风险评分接口挂了,导致所有权限判断都卡住。加个50ms超时,超时了就返回默认值。
4.4 本章小结
ABAC的核心就三件事:定义好属性、设计好策略引擎、实现动态计算。属性是数据基础,策略引擎是逻辑核心,动态计算是价值体现。
我个人建议,刚开始别追求完美。先定义10个以内的核心属性,写5条关键策略,跑通一个业务场景。等团队熟悉了,再逐步扩展。毕竟,能落地的架构才是好架构。