ACL 权限控制
ZooKeeper 使用 ACL(Access Control List)来控制对 znode 的访问权限。本章介绍 ACL 的概念和使用方法。
ACL 概述
ZooKeeper 的 ACL 机制类似于 Unix 文件权限,但更加灵活。每个 znode 都有一个 ACL 列表,定义了哪些用户可以执行哪些操作。
ACL 结构
每个 ACL 条目由两部分组成:
scheme:id:permissions
scheme: 认证方案(如 world, auth, digest, ip)
id: 标识符(具体含义取决于 scheme)
permissions: 权限组合
权限类型
ZooKeeper 定义了以下权限:
| 权限 | 缩写 | 说明 |
|---|---|---|
| READ | r | 读取节点数据和子节点列表 |
| WRITE | w | 写入节点数据 |
| CREATE | c | 创建子节点 |
| DELETE | d | 删除子节点 |
| ADMIN | a | 设置 ACL |
权限组合示例:
- cdrwa: 所有权限
- r: 只读
- rw: 读写
- rwcda: 所有权限(完整写法)
认证方案(Scheme)
1. world 方案
world 方案只有一个 id:anyone,表示所有用户。
# 设置 world 方案的 ACL
setAcl /node world:anyone:rwcda
# 这是最开放的权限,任何人都可以执行所有操作
Java 示例:
import org.apache.zookeeper.ZooDefs.Ids;
import org.apache.zookeeper.ZooDefs.Perms;
public class WorldAclDemo {
public static void main(String[] args) throws Exception {
ZooKeeper zk = new ZooKeeper("localhost:2181", 3000, null);
// 使用预定义的 OPEN_ACL_UNSAFE(world:anyone:cdrwa)
zk.create("/open-node", "data".getBytes(),
Ids.OPEN_ACL_UNSAFE, CreateMode.PERSISTENT);
// 使用预定义的 READ_ACL_UNSAFE(world:anyone:r)
zk.create("/read-only", "data".getBytes(),
Ids.READ_ACL_UNSAFE, CreateMode.PERSISTENT);
zk.close();
}
}
2. auth 方案
auth 方案表示当前已认证的用户,不需要指定具体的 id。
# 先添加认证信息
addauth digest user1:password1
# 使用 auth 方案设置 ACL
setAcl /node auth::rwcda
# auth 方案会自动使用当前认证的用户
Java 示例:
import org.apache.zookeeper.data.Id;
import org.apache.zookeeper.data.ACL;
public class AuthAclDemo {
public static void main(String[] args) throws Exception {
ZooKeeper zk = new ZooKeeper("localhost:2181", 3000, null);
// 添加认证
zk.addAuthInfo("digest", "user1:password1".getBytes());
// 创建节点时使用 auth 方案
List<ACL> acls = new ArrayList<>();
acls.add(new ACL(Perms.ALL, Ids.AUTH_IDS));
zk.create("/auth-node", "data".getBytes(), acls, CreateMode.PERSISTENT);
zk.close();
}
}
3. digest 方案
digest 方案使用用户名和密码进行认证,格式为 username:password。
# 使用 digest 方案设置 ACL
# id 格式:username:base64(SHA1(password))
setAcl /node digest:user1:XDkd2dsEuhfe9dsf=:(rwcda)
# 客户端需要先认证才能访问
addauth digest user1:password1
Java 示例:
import org.apache.zookeeper.server.auth.DigestAuthenticationProvider;
import java.security.NoSuchAlgorithmException;
public class DigestAclDemo {
public static void main(String[] args) throws Exception {
ZooKeeper zk = new ZooKeeper("localhost:2181", 3000, null);
// 生成 digest id
String username = "admin";
String password = "admin123";
String digestId = DigestAuthenticationProvider.generateDigest(username + ":" + password);
// 创建 ACL
List<ACL> acls = new ArrayList<>();
acls.add(new ACL(Perms.ALL, new Id("digest", digestId)));
// 创建节点
zk.create("/secure-node", "data".getBytes(), acls, CreateMode.PERSISTENT);
// 访问前需要认证
zk.addAuthInfo("digest", (username + ":" + password).getBytes());
// 现在可以访问
byte[] data = zk.getData("/secure-node", false, null);
System.out.println("Data: " + new String(data));
zk.close();
}
}
4. ip 方案
ip 方案使用客户端 IP 地址进行认证。
# 允许特定 IP 访问
setAcl /node ip:192.168.1.100:rwcda
# 允许 IP 段访问(CIDR 表示法)
setAcl /node ip:192.168.1.0/24:rwcda
Java 示例:
public class IpAclDemo {
public static void main(String[] args) throws Exception {
ZooKeeper zk = new ZooKeeper("localhost:2181", 3000, null);
// 创建 IP 方案的 ACL
List<ACL> acls = new ArrayList<>();
acls.add(new ACL(Perms.ALL, new Id("ip", "192.168.1.0/24")));
acls.add(new ACL(Perms.READ, new Id("ip", "10.0.0.0/8")));
zk.create("/ip-protected", "data".getBytes(), acls, CreateMode.PERSISTENT);
zk.close();
}
}
5. x509 方案
x509 方案使用 SSL 客户端证书进行认证。
# 使用 x509 证书的 Subject DN 作为 id
setAcl /node x509:CN=admin,O=MyOrg,C=CN:rwcda
预定义 ACL
ZooKeeper 提供了一些预定义的 ACL:
import org.apache.zookeeper.ZooDefs.Ids;
// 完全开放(world:anyone:cdrwa)
Ids.OPEN_ACL_UNSAFE
// 只读(world:anyone:r)
Ids.READ_ACL_UNSAFE
// 创建者所有权限(auth::cdrwa)
Ids.CREATOR_ALL_ACL
使用示例:
public class PredefinedAclDemo {
public static void main(String[] args) throws Exception {
ZooKeeper zk = new ZooKeeper("localhost:2181", 3000, null);
// 完全开放
zk.create("/public", "data".getBytes(),
Ids.OPEN_ACL_UNSAFE, CreateMode.PERSISTENT);
// 只读
zk.create("/readonly", "data".getBytes(),
Ids.READ_ACL_UNSAFE, CreateMode.PERSISTENT);
// 创建者所有权限(需要先认证)
zk.addAuthInfo("digest", "admin:admin123".getBytes());
zk.create("/private", "data".getBytes(),
Ids.CREATOR_ALL_ACL, CreateMode.PERSISTENT);
zk.close();
}
}
命令行操作 ACL
查看 ACL
# 获取节点的 ACL
[zk: localhost:2181(CONNECTED) 0] getAcl /node
'world,'anyone
: cdrwa
# 查看带认证的节点 ACL
[zk: localhost:2181(CONNECTED) 1] getAcl /secure-node
'digest,'admin:XDkd2dsEuhfe9dsf=
: cdrwa
设置 ACL
# 设置 world 方案
[zk: localhost:2181(CONNECTED) 2] setAcl /node world:anyone:rwa
# 设置 digest 方案
[zk: localhost:2181(CONNECTED) 3] setAcl /secure-node digest:admin:XDkd2dsEuhfe9dsf=:cdrwa
# 设置多个 ACL
[zk: localhost:2181(CONNECTED) 4] setAcl /node world:anyone:r,ip:192.168.1.100:rwcda
添加认证
# 添加 digest 认证
[zk: localhost:2181(CONNECTED) 5] addauth digest admin:admin123
# 添加后可以访问受保护的节点
[zk: localhost:2181(CONNECTED) 6] get /secure-node
data
Java API 操作 ACL
创建带 ACL 的节点
import org.apache.zookeeper.data.ACL;
import org.apache.zookeeper.data.Id;
import org.apache.zookeeper.ZooDefs.Perms;
public class CreateWithAclDemo {
public static void main(String[] args) throws Exception {
ZooKeeper zk = new ZooKeeper("localhost:2181", 3000, null);
// 构建自定义 ACL
List<ACL> acls = new ArrayList<>();
// 管理员:所有权限
acls.add(new ACL(Perms.ALL, new Id("digest",
DigestAuthenticationProvider.generateDigest("admin:admin123"))));
// 只读用户:只读权限
acls.add(new ACL(Perms.READ, new Id("digest",
DigestAuthenticationProvider.generateDigest("reader:reader123"))));
// 特定 IP:读写权限
acls.add(new ACL(Perms.READ | Perms.WRITE, new Id("ip", "192.168.1.100")));
// 创建节点
zk.create("/custom-acl", "data".getBytes(), acls, CreateMode.PERSISTENT);
zk.close();
}
}
获取和修改 ACL
import org.apache.zookeeper.data.Stat;
public class AclManagementDemo {
public static void main(String[] args) throws Exception {
ZooKeeper zk = new ZooKeeper("localhost:2181", 3000, null);
zk.addAuthInfo("digest", "admin:admin123".getBytes());
// 获取 ACL
Stat stat = new Stat();
List<ACL> acls = zk.getACL("/custom-acl", stat);
System.out.println("当前 ACL:");
for (ACL acl : acls) {
System.out.println(" " + acl.getId().getScheme() + ":" +
acl.getId().getId() + " -> " + acl.getPerms());
}
// 修改 ACL(需要 ADMIN 权限)
List<ACL> newAcls = new ArrayList<>();
newAcls.add(new ACL(Perms.ALL, new Id("digest",
DigestAuthenticationProvider.generateDigest("admin:admin123"))));
zk.setACL("/custom-acl", newAcls, stat.getAversion());
zk.close();
}
}
认证操作
public class AuthenticationDemo {
public static void main(String[] args) throws Exception {
ZooKeeper zk = new ZooKeeper("localhost:2181", 3000, null);
// 添加认证信息
zk.addAuthInfo("digest", "admin:admin123".getBytes());
// 现在可以访问受保护的节点
try {
byte[] data = zk.getData("/secure-node", false, null);
System.out.println("Data: " + new String(data));
} catch (KeeperException.NoAuthException e) {
System.out.println("认证失败或权限不足");
}
zk.close();
}
}
权限检查流程
┌─────────────────────────────────────────────────────────────┐
│ 权限检查流程 │
├─────────────────────────────────────────────────────────────┤
│ │
│ 客户端请求 │
│ │ │
│ ▼ │
│ ┌─────────────────────────────────────┐ │
│ │ 1. 检查会话是否有效 │ │
│ └─────────────────┬───────────────────┘ │
│ │ │
│ ┌─────────────┴─────────────┐ │
│ │ │ │
│ ▼ ▼ │
│ 会话有效 会话无效 │
│ │ │ │
│ ▼ ▼ │
│ ┌─────────────────┐ ┌─────────────────┐ │
│ │ 2. 获取客户端 │ │ 返回会话过期错误 │ │
│ │ 认证信息 │ └─────────────────┘ │
│ └────────┬────────┘ │
│ │ │
│ ▼ │
│ ┌─────────────────────────────────────┐ │
│ │ 3. 获取目标节点的 ACL 列表 │ │
│ └─────────────────┬───────────────────┘ │
│ │ │
│ ▼ │
│ ┌─────────────────────────────────────┐ │
│ │ 4. 遍历 ACL,匹配认证信息 │ │
│ └─────────────────┬───────────────────┘ │
│ │ │
│ ┌─────────────┴─────────────┐ │
│ │ │ │
│ ▼ ▼ │
│ 匹配成功 无匹配 │
│ │ │ │
│ ▼ ▼ │
│ ┌─────────────────┐ ┌─────────────────┐ │
│ │ 5. 检查权限位 │ │ 返回权限不足错误 │ │
│ │ 是否满足 │ └─────────────────┘ │
│ └────────┬────────┘ │
│ │ │
│ ┌────┴────┐ │
│ │ │ │
│ ▼ ▼ │
│ 权限足够 权限不足 │
│ │ │ │
│ ▼ ▼ │
│ 执行操作 返回权限不足错误 │
│ │
└─────────────────────────────────────────────────────────────┘
ACL 最佳实践
1. 最小权限原则
只授予必要的权限:
// 不推荐:授予过多权限
List<ACL> acls = new ArrayList<>();
acls.add(new ACL(Perms.ALL, Ids.ANYONE_ID_UNSAFE));
// 推荐:只授予必要权限
List<ACL> acls = new ArrayList<>();
acls.add(new ACL(Perms.READ, Ids.ANYONE_ID_UNSAFE)); // 只读
acls.add(new ACL(Perms.ALL, new Id("digest", adminDigest))); // 管理员
2. 分离读写权限
// 读写分离
List<ACL> acls = new ArrayList<>();
acls.add(new ACL(Perms.READ, readerId)); // 读者
acls.add(new ACL(Perms.READ | Perms.WRITE, writerId)); // 写者
acls.add(new ACL(Perms.ALL, adminId)); // 管理员
3. 使用认证保护敏感数据
// 敏感数据使用 digest 认证
List<ACL> secureAcls = new ArrayList<>();
secureAcls.add(new ACL(Perms.ALL, new Id("digest",
DigestAuthenticationProvider.generateDigest("admin:securePassword"))));
zk.create("/sensitive-config", secretData.getBytes(),
secureAcls, CreateMode.PERSISTENT);
4. IP 白名单
// 内网访问控制
List<ACL> acls = new ArrayList<>();
acls.add(new ACL(Perms.ALL, new Id("ip", "10.0.0.0/8")));
acls.add(new ACL(Perms.ALL, new Id("ip", "192.168.0.0/16")));
5. 定期审计 ACL
public void auditAcls(ZooKeeper zk, String path) throws Exception {
List<String> children = zk.getChildren(path, false);
for (String child : children) {
String childPath = path + "/" + child;
List<ACL> acls = zk.getACL(childPath, new Stat());
System.out.println("Path: " + childPath);
for (ACL acl : acls) {
System.out.println(" ACL: " + acl.getId().getScheme() +
":" + acl.getId().getId() +
" -> " + acl.getPerms());
}
// 递归检查子节点
auditAcls(zk, childPath);
}
}
常见问题
1. NoAuthException
// 原因:未认证或权限不足
try {
byte[] data = zk.getData("/secure-node", false, null);
} catch (KeeperException.NoAuthException e) {
// 解决方案:添加认证
zk.addAuthInfo("digest", "admin:admin123".getBytes());
}
2. BadVersionException
// 原因:ACL 版本号不匹配
try {
zk.setACL("/node", newAcls, -1); // -1 表示不检查版本
} catch (KeeperException.BadVersionException e) {
// 获取最新版本号后重试
Stat stat = new Stat();
zk.getACL("/node", stat);
zk.setACL("/node", newAcls, stat.getAversion());
}
3. ACL 继承问题
// ZooKeeper 的 ACL 不继承
// 子节点需要单独设置 ACL
// 父节点 ACL
zk.setACL("/parent", parentAcls, -1);
// 子节点需要单独设置
zk.create("/parent/child", data, childAcls, CreateMode.PERSISTENT);
小结
本章学习了 ZooKeeper 的 ACL 权限控制:
- ACL 结构:scheme:id:permissions 格式
- 权限类型:READ、WRITE、CREATE、DELETE、ADMIN
- 认证方案:world、auth、digest、ip、x509
- 预定义 ACL:OPEN_ACL_UNSAFE、READ_ACL_UNSAFE、CREATOR_ALL_ACL
- 最佳实践:最小权限、读写分离、认证保护、IP 白名单
下一章我们将学习如何使用 Java 客户端开发 ZooKeeper 应用。