在ZooKeeper的实际使用中,我们的做法往往是搭建一个共用的ZooKeeper集群,统一为若干个应用提供服务。在这种情况下,不同的应用往往是不会存在共享数据的使用场景的,因此需要解决不同应用之间的权限问题。
为了避免存储在ZooKeeper服务器上的数据被其他进程干扰或人为操作修改,需要对ZooKeeper上的数据访问进行权限控制(Access Control)。ZooKeeper提供了ACL的权限控制机制,简单的讲,就是通过设置ZooKeeper服务器上数据节点的ACL,来控制客户端对该数据节点的访问权限:如果iyge客户端符合该ACL控制,那么就可以对其进行访问,否则将无法操作。针对这样的控制机制,ZooKeeper提供了多种权限控制模式(Scheme),分别是world、auth、digest、ip和super。这里主要是在digest模式下如何进行ZooKeeper的权限控制。
开发人员如果要使用ZooKeeper的权限控制功能,需要在完成ZooKeeper会话创建后,给该会话添加上相关的权限信息(AuthInfo)。ZooKeeper客户端提供了相应的API接口来进行权限信息的设置,如下:
addAuthInfo(String scheme, byte[] auth)
API方法的参数说明如下表所示。
参数名 说明 scheme 权限控制模式,分为world、auth、digest、ip和super auth 具体的权限信息
该接口主要用于为当前ZooKeeper会话添加权限信息,之后凡是通过该会话对ZooKeeper服务端进行的任何操作,都会带上该权限信息。
使用包含权限信息的ZooKeeper会话创建数据节点
public class AuthSample implements Watcher{
final static String path ="/zk-book-auth-test";
private static ZooKeeper zk;
private static CountDownLatch cdl = new CountDownLatch(1);
public static void main(String[] args) throws IOException, KeeperException, InterruptedException {
zk =new ZooKeeper("192.168.×××.×××:2181",5000,new AuthSample());
zk.addAuthInfo("digest", "foo:true".getBytes());//设置权限
zk.create(path, "init".getBytes(), Ids.CREATOR_ALL_ACL, CreateMode.EPHEMERAL);
cdl.await();
Thread.sleep(Integer.MAX_VALUE);
}
public void process(WatchedEvent event) {
if(KeeperState.SyncConnected==event.getState()){
System.out.println("连接成功");
cdl.countDown();
}
上面这个示例程序就是一个典型的对ZooKeeper会话添加权限信息的使用方式。在这个示例中,我们采用了digest模式,同时可以看到其包含的具体权限信息是foo:true,这非常类似于username:password的格式。完成权限信息的添加后,该示例还使用客户端会话在ZooKeeper上创建了/zk-book-auth-test节点,这样该节点就受到了权限控制。下面我们来看,针对这个数据节点,ZooKeeper是如何进行权限控制的。
使用错误权限信息的ZooKeeper会话访问含权限信息的数据节点
public class AuthSample implements Watcher{
final static String path ="/zk-book-auth-test";
private static ZooKeeper zk;
private static CountDownLatch cdl = new CountDownLatch(1);
public static void main(String[] args) throws IOException, KeeperException, InterruptedException {
zk =new ZooKeeper("192.168.×××.×××:2181",5000,new AuthSample());
zk.addAuthInfo("digest", "foo:true".getBytes());
zk.create(path, "init".getBytes(), Ids.CREATOR_ALL_ACL, CreateMode.EPHEMERAL);
zk=new ZooKeeper("192.168.×××.×××:2181",5000,new AuthSample());
zk.addAuthInfo("digest", "foo:false".getBytes());
System.out.println(zk.getData(path, false, null));
cdl.await();
Thread.sleep(Integer.MAX_VALUE);
}
public void process(WatchedEvent event) {
if(KeeperState.SyncConnected==event.getState()){
System.out.println("连接成功");
cdl.countDown();
}
}
}
运行程序,输出异常信息如下:
连接成功
Exception in thread "main" org.apache.zookeeper.KeeperException$NoAuthException: KeeperErrorCode = NoAuth for /zk-book-auth-test
at org.apache.zookeeper.KeeperException.create(KeeperException.java:113)
at org.apache.zookeeper.KeeperException.create(KeeperException.java:51)
at org.apache.zookeeper.ZooKeeper.getData(ZooKeeper.java:1212)
at org.apache.zookeeper.ZooKeeper.getData(ZooKeeper.java:1241)
at CSII.Zookeeper.AuthSample.main(AuthSample.java:31)
在上面这个示例程序中,我们首先通过一个包含权限信息的客户端会话创建了一个数据节点,然后使用另一个包含错误权限信息的客户端会话对其进行访问,运行程序后,输出了异常信息:KeeperErrorCode = NoAuth for /zk-book-auth_test。可见,一旦我们对一个数据节点设置了权限信息,那么其他没有权限或者错误权限设置的客户端会话将无法访问该数据节点。ZooKeeper服务端能够为我们实现权限控制。
在ZooKeeper中,几乎所有的API接口操作,其权限控制策略都是和上面几个示例类似的,但是对于删除节点(delete)接口而言,其权限控制比较特殊。
删除节点接口的权限控制
/**
* @author Administrator
*使用包含权限信息的Zookeeper会话创建数据节点
*/
public class AuthSample implements Watcher{
final static String path ="/zk-book-auth-test";
final static String path2 ="/zk-book-auth-test/child";
private static ZooKeeper zk;
private static CountDownLatch cdl = new CountDownLatch(1);
public static void main(String[] args) throws IOException, KeeperException, InterruptedException {
zk =new ZooKeeper("192.168.×××.×××:2181",5000,new AuthSample());
zk.addAuthInfo("digest", "foo:true".getBytes());
zk.create(path, "init".getBytes(), Ids.CREATOR_ALL_ACL, CreateMode .PERSISTENT);
zk.create(path2, "init".getBytes(), Ids.OPEN_ACL_UNSAFE, CreateMode.EPHEMERAL);
try {
zk =new ZooKeeper("192.168.×××.×××:2181",5000,new AuthSample());
zk.delete(path2, -1);
} catch (Exception e) {
System.out.println(" 删除子节点失败:"+e.getMessage());
}
//加权限删除子节点
zk =new ZooKeeper("192.168.×××.×××:2181",5000,new AuthSample());
zk.addAuthInfo("digest", "foo:true".getBytes());//如果有子节点那么在删除的时候权限范围在子节点上
zk.delete(path2, -1);//删除子节点
System.out.println("删除子节点成功:"+path2);
//无权限删除根节点
zk =new ZooKeeper("192.168.×××.×××:2181",5000,new AuthSample());
zk.delete(path, -1);//删除根节点
System.out.println("删除根节点成功:"+path);
cdl.await();
Thread.sleep(Integer.MAX_VALUE);
}
public void process(WatchedEvent event) {
if(KeeperState.SyncConnected==event.getState()){
System.out.println("连接成功");
cdl.countDown();
}
}
}
运行程序,输出结果如下:
删除子节点失败:KeeperErrorCode = NoAuth for /zk-book-auth-test/child
删除子节点成功:/zk-book-auth-test/child
删除根节点成功:/zk-book-auth-test
在上面这个示例程序中,第一次,我们使用没有包含权限信息的客户端会话进行数据节点删除操作,显然,程序运行程序中抛出了异常信息:KeeperErrorCode = NoAuth for /zk-book-auth_test/child。而在第二次接口调用中,由于使用了正确的权限信息,因此成功删除了数据节点,相信这也不难理解。
下面我们着重来看第三次节点删除操作。需要注意的是,在这次删除操作中,我们使用的是没有包含权限信息的客户端会话,但最终却成功删除了数据节点。从这个例子中,我们可以看到,删除节点接口的权限控制比较特殊,当客户端对一个数据节点添加了权限信息后,对于删除操作而言,其作用范围是其子节点。也就是说,当我们对一个数据节点添加信息后,依然可以自由的删除这个节点,但是对于这个节点的子节点,就必须使用相应的权限信息才能够删除掉他。