Ohhnews

分类导航

$ cd ..
Baeldung原文

使用jCasbin进行授权

#jcasbin#casbin#访问控制#rbac#授权

1. 简介

本教程中,我们将了解 jCasbin——流行 Apache Casbin 授权库的 Java 官方移植版。我们将介绍它的概念、使用方法以及能实现的功能。

2. 什么是 jCasbin

jCasbin 是功能强大的 Casbin 访问控制库的 Java 版本,它能帮助我们轻松回答应用中有关资源访问的问题。

jCasbin 以及整个 Casbin 库套件,通过一种配置模型来运行。该模型允许我们描述想要使用的访问控制模型(例如 ACLRBAC),并配合单独的策略数据应用于该配置模型。

通常,这需要定义:

  • 我们希望控制访问的对象
  • 想要访问这些对象的主体
  • 主体希望对对象执行的操作

[LOADING...]

不过,jCasbin 足够灵活,只要能够正确地在配置和策略数据中建模,我们可以使用任何所需的结构。

3. 依赖项

使用 jCasbin 之前,我们需要在构建中包含最新版本,截至撰写本文时为 1.99.0

如果使用 Maven,可以在 pom.xml 文件中加入以下依赖:

$ xml
<dependency>
    <groupId>org.casbin</groupId>
    <artifactId>jcasbin</artifactId>
    <version>1.99.0</version>
</dependency>

至此,即可开始在应用中使用它。

4. 创建 Enforcer

在应用中引入 jCasbin 后,即可开始使用。核心类是 Enforcer,构造它需要两个数据源:我们的配置模型和策略数据。 最简单的方式是传入文件名:

$ java
Enforcer enforcer = new Enforcer("path/to/model.conf", "path/to/policy.csv");

然而,将策略数据放在本地文件可能不太方便。因此,jCasbin 允许在此处使用 Adapter,它提供从任何数据源加载和操作策略数据的手段。我们还有一组标准适配器可供应用使用,包括熟悉的技术如 JDBCHibernateMongoDB 等:

$ java
Adapter jdbcAdapter = new JDBCAdapter(dataSource);
Enforcer enforcer = new Enforcer("path/to/model.conf", jdbcAdapter);

这些适配器具体如何工作取决于具体适配器,不在本文讨论范围。

当以文件名指定策略数据时,内部会使用 FileAdapter。我们也可以通过提供 InputStream 从其他来源加载文件:

$ java
FileAdapter fileAdapter = new FileAdapter(getClass().getResourceAsStream("/com/baeldung/jcasbin/policy.csv"));
Enforcer enforcer = new Enforcer("path/to/model.conf", fileAdapter);

同样,我们可以使用 Model 实例来提供模型数据,从而从其他来源加载模型数据:

$ java
String content = new String(getClass().getClassLoader().getResourceAsStream("com/baeldung/jcasbin/model.conf").readAllBytes());
Model model = new Model();
model.loadModelFromText(content);
Enforcer enforcer = new Enforcer(model, "path/to/policy.csv");

创建好 Enforcer 后,即可开始用它检查权限。

5. 权限强制执行

现在我们已经有了 Enforcer,可以开始检查权限了。通过 enforce() 方法实现,通常需要传入主体、对象和操作:

$ java
if (enforcer.enforce("alice", "data1", "read")) {
    // 允许 alice 读取 data1
} else {
    // 拒绝请求
}

注意 enforce() 方法实际接受任意一组对象,具体取决于配置模型的定义。

权限检查的具体方式取决于配置模型的结构和策略数据。

5.1. 访问控制列表 (ACL)

配置 jCasbin 最简单的方式之一是通过 ACL。 典型的配置模型如下:

$ plaintext
# 请求定义
[request_definition]
r = sub, obj, act
# 策略定义
[policy_definition]
p = sub, obj, act
# 匹配器
[matchers]
m = r.sub == p.sub && r.obj == p.obj && r.act == p.act
# 策略效果
[policy_effect]
e = some(where (p.eft == allow))

其中,request_definition 部分定义了传入 enforcer.enforce() 的数据格式,为传入的参数提供了名称(这里用 "sub"、"obj"、"act" 分别表示 "主体"、"对象"、"操作")。

policy_definition 部分使用相同的名称映射到策略数据。如果没有指定,还有一个隐式字段 "eft"(效果),默认值为 "allow"。

matchers 部分说明如何将策略行与请求匹配。这里,我们简单地要求每个字段的值必须完全相同。

最后,policy_effect 部分说明了如何确定策略的整体效果。只有当此部分返回正匹配时,操作才被允许。

在这种模式下,策略数据就是主体、对象和允许操作的直接列表——由 policy_definition 定义:

$ plaintext
p, alice, data1, read
p, bob, data2, write

这表示主体 "alice" 可以对对象 "data1" 执行 "read" 操作,主体 "bob" 可以对对象 "data2" 执行 "write" 操作。

$ java
assertTrue(enforcer.enforce("alice", "data1", "read"));
assertTrue(enforcer.enforce("bob", "data2", "write"));

其他任何检查都会被拒绝,因为它们不匹配我们的配置。

5.2. 超级用户

有时可能需要超级用户——无论策略数据如何,都可以执行任何操作的用户。

我们可以通过调整 matchers 部分的定义来在配置模型中表达这一点:

$ plaintext
[matchers]
m = r.sub == p.sub && r.obj == p.obj && r.act == p.act || r.sub == "root"

这里,除了之前的匹配逻辑外,当主体值精确为 "root" 时也会匹配,无论策略表达式的其余部分如何。这样,主体为 "root" 的用户始终能通过所有检查:

$ java
assertTrue(enforcer.enforce("root", "data2", "write"));

这会给权限检查引入一定风险,因此需要谨慎使用。

5.3. 基于角色的访问控制 (RBAC)

RBAC 为配置模型引入了额外的间接层。 在此情况下,我们为用户分配角色,并基于角色定义权限。

首先添加 role_definition 部分:

$ plaintext
[role_definition]
g = _, _

这定义了可用于定义角色成员关系的 g() 函数。

同时需要更新 matchers 部分以利用这一点:

$ plaintext
[matchers]
m = g(r.sub, p.sub) && r.obj == p.obj && r.act == p.act

这里不再是直接比较策略主体与请求,而是要求它们必须通过角色定义匹配。

现在可以在策略数据中使用它:

$ plaintext
p, alice, data1, read
p, data2_admin, data2, read
p, data2_admin, data2, write
g, bob, data2_admin

所有以 p 开头的行定义了权限分配方式(直接分配给用户或角色)。这里我们将一个权限直接分配给主体 "alice",将两个权限分配给角色 "data2_admin"。

g 开头的行(来自上面的 role_definition 部分)定义了角色成员关系。这里我们定义主体 "bob" 被分配了角色 "data2_admin"。这意味着该主体隐式拥有此角色的所有权限:

$ java
assertTrue(enforcer.enforce("bob", "data2", "write"));

我们还可以嵌套角色,即将一个角色分配给另一个角色,从而生成完整的权限层次结构:

$ plaintext
g, superuser, data2_admin
g, carol, superuser

这里,角色 "data2_admin" 被分配给角色 "superuser",而角色 "superuser" 又被分配给主体 "carol"。这意味着该主体拥有最终的所有权限:

$ java
assertTrue(enforcer.enforce("carol", "data2", "read"));

我们从未直接或间接授予 "carol" 对 "data2" 的权限。她通过自己的角色 "superuser" 继承而来。

6. 管理 API

除了检查权限,我们还有用于管理配置的管理 API。

这些操作通过我们已经使用的同一个 Enforcer 实例完成。

6.1. 查询主体、对象和操作

最简单的操作是查询策略数据中的所有主体和对象。 使用 getAllSubjects()getAllObjects()getAllActions() 方法:

$ java
List<String> subjects = enforcer.getAllSubjects();
List<String> objects = enforcer.getAllObjects();
List<String> actions = enforcer.getAllActions();

我们还可以查询任意主体对任意对象可以执行的操作:

$ java
Set<String> subjects = enforcer.getPermittedActions("alice", "data1");

这对 ACL 和 RBAC 设置都适用,也适用于层级角色。

6.2. 查询 RBAC 角色

在 RBAC 设置下,我们还可以查询哪些角色被分配给了哪些主体:

$ java
List<String> roles = enforcer.getRolesForUser("carol");
// superuser

这仅返回直接分配的角色,不包括层级中包含的角色。

不过,我们也可以类似地查询角色之间的分配:

$ java
List<String> roles = enforcer.getRolesForUser("superuser");
// data2_admin

这意味着如果需要,我们可以遍历层级结构。

6.3. 管理权限和角色

我们还有一组方法可以用来直接管理主体拥有的权限和角色。

我们可以直接添加和删除主体与对象之间的权限:

$ java
enforcer.addPermissionForUser("alice", "data2", "read");
enforcer.deletePermissionForUser("alice", "data2", "read");

这些操作一旦调用立即生效,权限检查会马上受到影响。

在 RBAC 模式下工作时,我们还可以从主体添加和删除角色:

$ java
enforcer.addRoleForUser("alice", "superuser");
enforcer.deleteRoleForUser("alice", "superuser");

所有这些更改都在内存中进行。我们需要使用 savePolicy() 方法将它们写回后端存储:

$ java
enforcer.savePolicy();

但这只有在使用特定适配器时才能实现,也取决于数据的加载方式。例如,如果从 InputStream 加载,则无法写回。

7. 总结

本文快速介绍了 jCasbin。它还有更多功能可以探索。下次需要为应用管理访问控制时,不妨尝试一下。

和往常一样,本文的所有代码可在 GitHub 上找到。