1、 zk权限介绍

1) ZK的节点有5种操作权限:
CREATE、READ、WRITE、DELETE、ADMIN 也就是 增、删、改、查、管理权限,这5种权限简写为crwda(即:每个单词的首字符缩写)
注:这5种权限中,delete是指对子节点的删除权限,其它4种权限指对自身节点的操作权限。

2) 身份的认证有4种方式:
world:默认方式,相当于全世界都能访问
auth:代表已经认证通过的用户(cli中可以通过addauth digest user:pwd 来添加当前上下文中的授权用户)
digest:即用户名:密码这种方式认证,这也是业务系统中最常用的
ip:使用Ip地址认证。

2、设置访问控制

方式一:(推荐)
1)增加一个认证用户
addauth digest 用户名:密码明文
eg. addauth digest user1:password1

2)设置权限
setAcl /path auth:用户名:密码明文:权限
eg. setAcl /test auth:user1:password1:cdrwa

3)查看Acl设置
getAcl /path

方式二:
setAcl /path digest:用户名:密码密文:权限
注:这里的加密规则是SHA1加密,然后base64编码。

3、zookeeper创建ip白名单

(1)设置IP白名单:
格式:setAcl 路径 ip:xxx.xxx.xxx.xx1:cdrwa,ip:xxx.xxx.xxx.xx2:cdrwa
例如:setAcl /zkaa ip:127.0.0.1:cdrwa,ip:10.111.134.6:cdrwa

重点说明:在设置IP白名单时,将本机ip 127.0.0.1也加上,让本机也可以访问及修改,否则到时本机服务器都无法进行查看及修改,到时会很麻烦

(2)取ACL权限(当前连接IP有权限的情况下,如本机127.0.0.1),
格式:setAcl 路径 world:anyone:cdrwa
例如:setAcl /zkee world:anyone:cdrwa

3.x authentication 在 zkclient 和 zkserver 交互概念

客户端与Server建立连接时,会将ZooKeeper.addAuthInfo()方法添加的每个authInfo都发送给ZKServer。

void addAuthInfo(String scheme, byte auth[])

addAuthInfo 方法本身也会直接将authInfo发送给ZKServer。ZKServer接受到authInfo请求后,首先根据scheme找到对应的AP,然后调用其handleAuthentication()方法将auth数据传入。对应的AP将auth数据解析为一个Id,将其加入连接上绑定的authInfo列表(List)中。Server在接入客户端连接时,首先会自动在连接上加上一个
默认的scheme为ip的authIndo:authInfo.add(new Id(“ip”, client-ip));

鉴权时调用AP的matches()方法判断进行该操作的当前连接上绑定的authInfo是否与所操作的znode的ACL列表匹配。

ZK有4个内置的scheme:

• world 只有一个唯一的id:anyone;表示任何人都可以做对应的操作。这个scheme没有对应的鉴权实现。只要一个znode的ACL list中包含有这个scheme的Id,其对应的操作就运行执行。
• auth 没有对应的id,或者只有一个空串””id。这个scheme没有对应的鉴权实现。语义是当前连接绑定的适合做创建者鉴权的autoInfo (通过调用autoInfo的scheme对应的AP的isAuthenticated()得知)都拥有对应的权限。遇到这个auth后,Server会根据当前连接绑定的符合要求的autoInfo生成ACL加入到所操作znode的acl列表中。
digest 使用username:password格式的字符串生成MD5 hash 作为ACL ID。 具体格式为:username:base64 encoded SHA1 password digest.对应内置的鉴权插件:DigestAuthenticationProvider
ip 用IP通配符匹配客户端ip。对应内置鉴权插件IPAuthenticationProvider

只有两类API会改变Znode的ACL列表: 一个是create(),一个是setACL()。

所以这两个方法都要求传入一个List。Server接到这两种更新请求后,会判断指定的每一个ACL中,scheme对应的AuthenticationProvider是否存在,如果存在,调用其isValid(String)方法判断对应的id表达式是否合法。。。具体参见PrepRequestProcessor.fixupACL()方法。上文的那个报错是因为CREATOR_ALL_ACL只包含一个ACL : Perms.ALL, Id(“auth”, “”),而auth要求将连接上适合做创建者鉴权的autoInfo都加入节点的acl中,而此时连接上只有一个默认加入的Id(“ip”, client-ip),其对应的IPAuthenticationProvider的isAuthenticated()是返回false的,表示不用来鉴权node的创建者。

3.x2、ACL super 超级管理员

概述:

zk的权限管理表有一种ACL的模式叫做super,该模式的作用是方便管理节点。一旦我们为某一个节点设置了acl,那么其余的未授权的节点是无法访问或者操作该节点的,那么系统用久了以后,假如忘记了某一个节点的密码,那么就无法再操作这个节点了,所以需要这个super超级管理员用户权限,其作用还是很大的。

假设这个超管是:super:admin,通过代码得到其哈希值:

@Test
public void createSupperAdmin() throws NoSuchAlgorithmException {

    // m 输出结果为 : super:xQJmxLMiHGwaqBvst5y6rkB6HQs=
    String m = DigestAuthenticationProvider.generateDigest("super:admin");
    System.out.println("输出权限 : " + m);
}

那么打开zk目录下的/bin/zkServer.sh服务器脚本文件,找到如下一行:

nohup $JAVA "-Dzookeeper.log.dir=${ZOO_LOG_DIR}" "-Dzookeeper.root.logger=${ZOO_LOG4J_PROP}"

这就是脚本中启动zk的命令,默认只有以上两个配置项,我们需要加一个超管的配置项:

"-Dzookeeper.DigestAuthenticationProvider.superDigest=super:xQJmxLMiHGwaqBvst5y6rkB6HQs="

修改以后这条完整命令变成了:

nohup $JAVA "-Dzookeeper.log.dir=${ZOO_LOG_DIR}" "-Dzookeeper.root.logger=${ZOO_LOG4J_PROP}" "-Dzookeeper.DigestAuthenticationProvider.superDigest=super:xQJmxLMiHGwaqBvst5y6rkB6HQs="\
    -cp "$CLASSPATH" $JVMFLAGS $ZOOMAIN "$ZOOCFG" > "$_ZOO_DAEMON_OUT" 2>&1 < /dev/null &

之后启动zk集群。 就可以使用超级管理员了。

addauth digest super:admin


4、zookeeper ACL案例

4.1 使用addAuthInfo 操作
@Test
public void test1()throws IOException, KeeperException, InterruptedException {

    // 一级节点, 这个会创建成功
    try {
        ZooKeeper zooKeeper = new ZooKeeper("127.0.0.1:2181", 5000, null);
        zooKeeper.addAuthInfo("digest", "zzf:zzf".getBytes());
        zooKeeper.create(PATH, "mydata".getBytes(), ZooDefs.Ids.CREATOR_ALL_ACL,  CreateMode.PERSISTENT);
    } catch (Exception e) {

        System.out.println("创建节点失败 !! ");
        e.printStackTrace();
    }

    // 二级节点, 这个会创建失败, 因为权限(不对)访问不了第一级
    try {
        ZooKeeper zooKeeper2 = new ZooKeeper("127.0.0.1:2181", 5000, null);
        zooKeeper2.addAuthInfo("digest", "zzf:zzf2".getBytes());
        zooKeeper2.create(PATH2, "mydata".getBytes(), ZooDefs.Ids.CREATOR_ALL_ACL,  CreateMode.EPHEMERAL);
    } catch (Exception e) {

        System.out.println("创建节点失败 !! ");
        e.printStackTrace();
    }

    // 可以访问, 并且创建二级节点
    System.out.println("使用正确的用户信息。。。");
    ZooKeeper zooKeeper3 = new ZooKeeper("127.0.0.1:2181", 5000, null);
    zooKeeper3.addAuthInfo("digest", "zzf:zzf".getBytes());
    System.out.println(new String(zooKeeper3.getData(PATH, false, null)));
    zooKeeper3.create(PATH2, "newMyData".getBytes(), ZooDefs.Ids.CREATOR_ALL_ACL, CreateMode.EPHEMERAL);

}
4.2 使用acls操作
/**
 * 使用批量ACL形式, 创建节点,digest 方式
 * @throws Exception
 */
@Test
public void testCreate()throws Exception{

    ZooKeeper zooKeeper = new ZooKeeper("127.0.0.1:2181", 5000, null);
    List<ACL> acls = new ArrayList<ACL>();  // 权限列表
    /**
     *  第一个参数是权限scheme,第二个参数是加密后的用户名和密码
     *  需要注意 :必须使用DigestAuthenticationProvider 的 generateDigest 加密
     */
    Id auth1 = new Id("digest", DigestAuthenticationProvider.generateDigest("zzf:zzf"));
    Id auth2 = new Id("digest", DigestAuthenticationProvider.generateDigest("zzf2:zzf2"));
    Id auth3 = new Id("digest", DigestAuthenticationProvider.generateDigest("zzf3:zzf3"));
    acls.add(new ACL(ZooDefs.Perms.ALL, auth1)); // 有所有权限
    acls.add(new ACL(ZooDefs.Perms.READ, auth2)); // 具备读权限
    // 多个权限使用 | 分割(具备: 创建、写入)
    acls.add(new ACL(ZooDefs.Perms.CREATE | ZooDefs.Perms.WRITE, auth3));
    String s = zooKeeper.create(PATH_NEW_DIGEST, "newMyData".getBytes(), acls, CreateMode.PERSISTENT);
    System.out.println("创建后返回的内容是 : " + s);
    Thread.sleep(Integer.MAX_VALUE);
}
/**
* 读取节点
* @throws Exception
*/
@Test
public void testRead()throws Exception{

   // 具备读权限
   ZooKeeper zooKeeper2 = new ZooKeeper("127.0.0.1:2181", 5000, null);
   zooKeeper2.addAuthInfo("digest", "zzf:zzf".getBytes());
   System.out.println(new String(zooKeeper2.getData(PATH_NEW_DIGEST, false, null)));

   // 不具备读权限, 出现KeeperErrorCode = NoAuth for /zk-acls-auth
   ZooKeeper zooKeeper3 = new ZooKeeper("127.0.0.1:2181", 5000, null);
   zooKeeper3.addAuthInfo("digest", "zzf3:zzf3".getBytes());
   System.out.println(new String(zooKeeper3.getData(PATH_NEW_DIGEST, false, null)));

}
/**
 * 使用ip 设置白名单权限
 * @throws Exception
 */
@Test
public void testCreateIpAcl()throws Exception{

    ZooKeeper zooKeeper = new ZooKeeper("127.0.0.1:2181", 5000, null);
    List<ACL> acls = new ArrayList<ACL>();  // 权限列表
    /**
     *  第一个参数是权限scheme,第二个参数是ip
     *  需要注意 :当前主机的必须要加上否则会出现不能操作的情况
     */
    Id auth1 = new Id("ip", "192.168.1.1");
    Id auth2 = new Id("ip", "127.0.0.1");
    acls.add(new ACL(ZooDefs.Perms.ALL, auth1));
    acls.add(new ACL(ZooDefs.Perms.ALL, auth2));
    String s = zooKeeper.create(PATH_NEW_IP, "newMyData".getBytes(), acls, CreateMode.PERSISTENT);
    System.out.println("创建后返回的内容是 : " + s);
    Thread.sleep(Integer.MAX_VALUE);
}

/**
 * 读取IP 权限的数据, 当前的机器ip在上述的IP列表里面就可以直接通过访问
 * @throws Exception
 */
@Test
public void testReadIp()throws Exception{

    // 不具备读权限, 出现KeeperErrorCode = NoAuth for /zk-acls-auth
    ZooKeeper zooKeeper3 = new ZooKeeper("127.0.0.1:2181", 5000, null);
    System.out.println(new String(zooKeeper3.getData(PATH_NEW_IP, false, null)));
}

5、使用curator ACL

6、使用自定义AuthenticationProvider

ZooKeeper提供四种权限模式,也提供给我们能够自定义自己权限的方式实现接口 : org.apache.zookeeper.server.auth.AuthenticationProvider

- 启动参数配置
在ZooKeeper启动参数中配置类似于如下的系统属性
-Dzookeeper.authProvider.1=com.zzf.UserAuthenticationProvider

- 配置文件配置
在zoo.cfg配置文件中配置类似于如下的配置项
authProvider.1=com.zkbook.CustomAuthenticationProvider