一、前言:当权限判断写满业务代码
几乎所有企业系统,都逃不过"权限 "这道关。
从"谁能看"、"谁能改"到"谁能审批",权限逻辑贯穿了业务的方方面面。
起初,大多数项目使用最常见的 RBAC(基于角色的访问控制) 模型
erlang
if (user.hasRole("admin")) {
documentService.update(doc);
}
逻辑简单、上手快,看似能解决 80% 的问题。
但随着业务复杂度上升,RBAC 很快会失控。
比如你可能遇到以下需求 👇
- "文档的作者可以编辑自己的文档";
- "同部门的经理也可以编辑该文档";
- "外部合作方仅能查看共享文档";
- "项目归档后,所有人都只读"。
这些场景无法用"角色"简单定义,
于是权限判断开始蔓延在业务代码各处,像这样:
less
if (user.getId().equals(doc.getOwnerId())
|| (user.getDept().equals(doc.getDept()) && user.isManager())) {
// 编辑文档
} else {
throw new AccessDeniedException("无权限");
}
时间久了,这些判断像杂草一样蔓延。
权限逻辑与业务逻辑纠缠不清,修改一处可能引发连锁反应。
可维护性、可测试性、可演化性统统崩盘。
二、RBAC 的天花板:角色无法描述现实世界
RBAC 的问题在于:它过于静态 。
"角色"可以描述一类人,但描述不了上下文。
举个例子:
研发经理能编辑本部门的文档,但不能编辑市场部的。
在 RBAC 下,你只能再创建新角色:
研发经理
、市场经理
、项目经理
......
角色越来越多,最终爆炸。
而现实世界的权限,往往与"属性"有关:
- 用户的部门
- 资源的拥有者
- 操作发生的时间 / 状态
这些动态因素,是 RBAC 无法覆盖的。
于是我们需要一个更灵活的模型 ------ ABAC。
三、ABAC:基于属性的访问控制
ABAC(Attribute-Based Access Control) 的核心理念是:
授权决策 = 函数(主体属性、资源属性、操作属性、环境属性)
概念 | 含义 | 示例 |
---|---|---|
Subject(主体) | 谁在访问 | 用户A,部门=研发部 |
Object(资源) | 访问什么 | 文档1,ownerId=A,部门=研发部 |
Action(操作) | 做什么 | edit / read / delete |
Policy(策略) | 允许条件 | user.dept == doc.dept && act == "edit" |
一句话总结:
ABAC 不关心用户是谁,而关心"用户和资源具有什么属性"。
举例说明:
"用户可以编辑自己部门的文档,或自己创建的文档。"
简单、直观、灵活。
四、引入 JCasbin:让授权逻辑从代码中消失
JCasbin(github.com/casbin/jcas...) 是一个优秀的 Java 权限引擎,支持多种模型(RBAC、ABAC)。
它最大的价值在于:
把授权逻辑从代码中抽离,让代码只负责执行业务。
在 JCasbin 中,我们通过定义:
- 模型文件(model) :规则框架;
- 策略文件(policy) :具体规则。
然后由 Casbin 引擎来执行判断。
五、核心实现:几行配置搞定动态权限
模型文件 model.conf
ini
[request_definition]
r = sub, obj, act
[policy_definition]
p = sub_rule, obj_rule, act
[policy_effect]
e = some(where (p.eft == allow))
[matchers]
m = eval(p.sub_rule) && eval(p.obj_rule) && r.act == p.act
策略文件 policy.csv
lua
p, r.sub.dept == r.obj.dept, true, edit
p, r.sub.id == r.obj.ownerId, true, edit
p, true, true, read
解释:
- 同部门可编辑;
- 作者可编辑;
- 所有人可阅读。
在代码中调用
ini
Enforcer enforcer = new Enforcer("model.conf", "policy.csv");
User user = new User("u1", "研发部");
Document doc = new Document("d1", "研发部", "u1");
boolean canEdit = enforcer.enforce(user, doc, "edit");
System.out.println("是否有编辑权限:" + canEdit);
输出:
arduino
是否有编辑权限:true
无需任何 if-else,逻辑全在外部配置中定义 。
业务代码只需调用 Enforcer,简单又优雅。
六、在 Spring Boot 中实现"无感校验"
实际项目中,我们希望权限校验能"自动触发",
这可以通过 注解 + AOP 切面 的方式实现。
定义注解
less
@Target(ElementType.METHOD)
@Retention(RetentionPolicy.RUNTIME)
public @interface CheckPermission {
String action();
}
编写切面
less
@Aspect
@Component
public class PermissionAspect {
@Autowired
private Enforcer enforcer;
@Before("@annotation(checkPermission)")
public void checkAuth(JoinPoint jp, CheckPermission checkPermission) {
Object user = getCurrentUser();
Object resource = getRequestResource(jp);
String action = checkPermission.action();
if (!enforcer.enforce(user, resource, action)) {
throw new AccessDeniedException("无权限执行操作:" + action);
}
}
}
在业务代码中使用
less
@CheckPermission(action = "edit")
@PostMapping("/doc/edit")
public void editDoc(@RequestBody Document doc) {
documentService.update(doc);
}
✅ 授权逻辑彻底从业务中解耦,权限统一由 Casbin 引擎处理。
七、策略动态化与分布式支持
在生产环境中,权限策略通常存储在数据库中,而非文件。
JCasbin 支持多种扩展方式:
ini
JDBCAdapter adapter = new JDBCAdapter(dataSource);
Enforcer enforcer = new Enforcer("model.conf", adapter);
支持特性包括:
- 💽 MySQL / PostgreSQL 等持久化;
- 🔄 Redis Watcher 实现多节点策略热更新;
- ⚡ SyncedEnforcer 支持高并发一致性。
这样修改权限规则就无需重新部署代码,权限即改即生效
八、总结
引入 JCasbin 后,项目结构会发生显著变化👇
优势 | 描述 |
---|---|
逻辑解耦 | 授权逻辑完全从业务代码中剥离 |
灵活配置 | 权限规则动态可改、可热更新 |
可扩展 | 可根据属性定义复杂条件 |
统一决策 | 所有权限判断走同一引擎 |
可测试 | 策略可单测,无需跑整套业务流程 |
最重要的是:新增规则无需改代码 。
只要在策略表里加一条记录,就能实现全新的授权逻辑。
权限系统的复杂,不在于"能不能判断",
而在于------"判断逻辑放在哪儿"。
当项目越做越大,你会发现:
真正的架构能力,不是多写逻辑,而是让逻辑有边界。
JCasbin 给了我们一个极好的解法:
一个统一的决策引擎,让权限系统既灵活又有秩序。
它不是银弹,但能让你在权限处理上的代码更纯净、系统扩展性更好。