4、权限校验模型:RBAC模型、ABAC模型、权限缓存策略
权限校验这事儿,说简单也简单,说复杂能让你掉一层头发。我在做消费者数据访问响应流程的时候,权限这块踩过的坑,现在想想都心疼。今天咱们就聊聊三种主流方案:RBAC、ABAC,以及怎么把权限缓存玩明白。
4.1 RBAC模型:角色就是你的通行证
RBAC,全称是Role-Based Access Control。说白了就是:你是什么角色,就能干什么事。比如你是“客服主管”,那你能查所有客户的订单;你是“普通客服”,只能查自己接单的客户。
我最早做权限系统时,用的就是RBAC。结构非常清晰:
- 用户:具体的人,比如张三、李四
- 角色:权限的集合,比如“管理员”、“审核员”
- 权限:具体的操作,比如“查询用户信息”、“导出报表”
它们的关系是这样的:
用户 → 角色 → 权限
张三 → 客服主管 → 查询订单、修改订单、导出报表
李四 → 普通客服 → 查询订单(仅限自己客户)
核心优势:管理简单,权限变更只需要改角色,不用一个个改用户。
但RBAC有个硬伤。我在项目中遇到过这样一个场景:某个数据接口,要求“只有本部门的高级工程师在工作时间才能访问”。你想想看,RBAC怎么表达“本部门”和“工作时间”?它只能表达“高级工程师”这个角色。这就是RBAC的边界——它管不了上下文。
注意:如果你的权限规则里出现了“如果...那么...”、“当...时...”这类条件,RBAC基本就扛不住了。别硬撑,该上ABAC就上。
4.2 ABAC模型:条件才是王道
ABAC,Attribute-Based Access Control。它不看你是什么角色,它看的是你、资源、环境这三者的属性。
举个例子:
规则:允许访问客户数据
条件:
- 用户.部门 == 客户数据.所属部门
- 用户.职级 >= 高级
- 当前时间 BETWEEN 09:00 AND 18:00
你看,ABAC把权限变成了一个条件表达式。每个请求进来,系统会收集三组属性:
| 属性类型 | 示例 |
|---|---|
| 用户属性 | 部门、职级、地区、入职时间 |
| 资源属性 | 数据类型、所属部门、敏感等级 |
| 环境属性 | 当前时间、IP地址、设备类型 |
我做过一个消费者数据访问系统,要求“只有数据所属区域的风控人员在内网才能查看高敏感数据”。用ABAC写出来就是:
IF
user.role == '风控人员'
AND user.region == resource.region
AND env.network == '内网'
AND resource.sensitivity == '高'
THEN
PERMIT
是不是很直观?但ABAC也有代价——性能。每次请求都要解析规则、收集属性、计算表达式。如果规则有几百条,那延迟就上去了。
我的建议:核心业务用ABAC,非核心业务用RBAC。别一刀切。我见过有人把所有权限都写成ABAC,结果一个接口响应时间多了200ms,得不偿失。
4.3 权限缓存策略:别让数据库扛下所有
权限校验是个高频操作。每个API请求进来,都要问一遍“这人能干啥”。如果每次都查数据库,数据库会哭的。
我常用的缓存策略分三层:
4.3.1 本地缓存(Caffeine / Guava Cache)
每个服务节点自己存一份。适合变化不频繁的权限数据。比如角色定义、权限列表。我一般设置过期时间5分钟。
// 伪代码示例
Cache<String, Set<String>> userPermissionCache = Caffeine.newBuilder()
.expireAfterWrite(5, TimeUnit.MINUTES)
.maximumSize(10000)
.build();
Set<String> permissions = userPermissionCache.get(userId, key -> {
return loadPermissionsFromDB(key);
});
4.3.2 分布式缓存(Redis)
适合跨服务共享的权限数据。比如用户角色、资源属性。我习惯用Redis的Hash结构存:
Key: user:permissions:{userId}
Value: {
"roles": ["admin", "operator"],
"department": "finance",
"level": "senior"
}
这里有个坑——缓存一致性。我曾经遇到过:管理员改了某个用户的角色,但缓存没清,结果那个用户拿着旧权限访问了不该看的数据。嗯,后来我加了个Redis的Pub/Sub机制:权限变更时,广播一条消息,所有节点收到后主动清掉本地缓存。
避坑指南:权限缓存一定要设置合理的过期时间。别设成永久。我建议本地缓存5分钟,Redis缓存30分钟。即使广播失效了,过期也能兜底。
4.3.3 请求级缓存(ThreadLocal)
同一个请求内,权限可能被多次校验。比如一个接口要查用户信息、查订单、查积分,每个步骤都要校验。我习惯在请求入口处,把用户的权限一次性加载到ThreadLocal里,后续直接取。
// 请求过滤器
public class PermissionFilter implements Filter {
public void doFilter(request, response, chain) {
String userId = extractUserId(request);
Set<String> permissions = loadPermissions(userId);
PermissionContext.set(permissions);
try {
chain.doFilter(request, response);
} finally {
PermissionContext.clear(); // 记得清理
}
}
}
为什么要用ThreadLocal?因为它是线程隔离的,不会串数据。但千万记得在finally里清理,否则内存泄漏等着你。
4.4 三种策略怎么选?
我个人的经验是:
- 初创项目、权限规则简单:RBAC + 本地缓存就够了。别过度设计。
- 业务复杂、条件多:ABAC + Redis缓存。但要做好规则引擎的性能优化。
- 高并发、低延迟:RBAC为主 + 请求级缓存。ABAC只用在关键节点。
最后说一句:权限模型没有银弹。你想想看,一个电商系统的权限和一个金融系统的权限,能一样吗?别照搬,要结合业务场景来设计。
核心总结:RBAC管角色,ABAC管条件,缓存管性能。三者配合,才能让消费者数据访问既安全又流畅。