简介
Zookeeper安装与配置可以参考Zookeeper初遇
zookeeper本身提供了ACL机制,表示为scheme:id:permissions 第一个字段表示采用哪一种机制,第二个id表示用户,permissions表示相关权限 zookeeper提供了如下几种机制(scheme) 1. world: 它下面只有一个id, 叫anyone, world:anyone代表任何人,zookeeper中对所有人有权限的结点就是属于world:anyone的 2. auth: 它不需要id, 只要是通过authentication的user都有权限(zookeeper支持通过kerberos来进行authencation, 也支持username/password形式的authentication) 3. digest: 它对应的id为username:BASE64(SHA1(password)),它需要先通过username:password形式的authentication 4. ip: 它对应的id为客户机的IP地址,设置的时候可以设置一个ip段,比如ip:192.168.1.0/16, 表示匹配前16个bit的IP段 5. x509
Zookeeper根目录/的权限是任何用户都有create,delete,read,write,admin(cdrwa) admin权限是设置ACL的权限。
如上图所示是zookeeper根目录/的ACL
注意:后面的zookeeper可能代指2个不同的东西,一个是zookeeper本身,另一个是org.apach.zookeeper包。
注意:znode是没有继承关系的,也就是说如果没有父目录如果有子目录权限可以直接访问子目录
WORLD
先来一个小例子:
import java.io.IOException;
import java.util.ArrayList;
import java.util.List;
import org.apache.zookeeper.ZooDefs.Perms;
import org.apache.zookeeper.CreateMode;
import org.apache.zookeeper.KeeperException;
import org.apache.zookeeper.ZooKeeper;
import org.apache.zookeeper.data.ACL;
import org.apache.zookeeper.data.Id;
import org.junit.Test;
public class ZookeeperWorldACLTest {
private static final String WORLD = "world";
@Test
public void testWorldSchema(){
List<ACL> acls = getWorldACL();
ZooKeeper zookeeper = null;
try {
zookeeper = new ZooKeeper("localhost:2181",3000,null);
} catch (IOException e) {
e.printStackTrace();
}
create(zookeeper,acls);
getData(zookeeper,acls);
}
private void create(ZooKeeper zookeeper,List<ACL> acls){
try {
String actualPath = zookeeper.create("/world", "world scheme".getBytes(), acls, CreateMode.EPHEMERAL);
System.out.println(actualPath);
actualPath = zookeeper.create("/world/xxx", "violate".getBytes(), acls, CreateMode.PERSISTENT);
System.out.println(actualPath);
} catch (KeeperException e) {
e.printStackTrace();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
private void getData(ZooKeeper zookeeper,List<ACL> acls){
try {
byte[] data = zookeeper.getData("/world", false, null);
System.out.println(new String(data));
} catch (KeeperException e) {
e.printStackTrace();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
private List<ACL> getWorldACL(){
Id id = new Id(WORLD,"anyone");//id = Ids.ANYONE_ID_UNSAFE;
ACL acl = new ACL(Perms.READ,id);
ArrayList<ACL> acls = new ArrayList<ACL>();
acls.add(acl);
return acls;
}
}
下面是上面测试的输出:
/world
org.apache.zookeeper.KeeperException$NoAuthException: KeeperErrorCode = NoAuth for /world/xxx
at org.apache.zookeeper.KeeperException.create(KeeperException.java:104)
at org.apache.zookeeper.KeeperException.create(KeeperException.java:42)
at org.apache.zookeeper.ZooKeeper.create(ZooKeeper.java:637)
at cn.freemethod.zk.ZookeeperWorldACLTest.create(ZookeeperWorldACLTest.java:38)
at cn.freemethod.zk.ZookeeperWorldACLTest.testWorldSchema(ZookeeperWorldACLTest.java:29)
at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
at sun.reflect.NativeMethodAccessorImpl.invoke(Unknown Source)
at sun.reflect.DelegatingMethodAccessorImpl.invoke(Unknown Source)
at java.lang.reflect.Method.invoke(Unknown Source)
at org.junit.runners.model.FrameworkMethod$1.runReflectiveCall(FrameworkMethod.java:45)
at org.junit.internal.runners.model.ReflectiveCallable.run(ReflectiveCallable.java:15)
at org.junit.runners.model.FrameworkMethod.invokeExplosively(FrameworkMethod.java:42)
at org.junit.internal.runners.statements.InvokeMethod.evaluate(InvokeMethod.java:20)
at org.junit.runners.ParentRunner.runLeaf(ParentRunner.java:263)
at org.junit.runners.BlockJUnit4ClassRunner.runChild(BlockJUnit4ClassRunner.java:68)
at org.junit.runners.BlockJUnit4ClassRunner.runChild(BlockJUnit4ClassRunner.java:47)
at org.junit.runners.ParentRunner$3.run(ParentRunner.java:231)
at org.junit.runners.ParentRunner$1.schedule(ParentRunner.java:60)
at org.junit.runners.ParentRunner.runChildren(ParentRunner.java:229)
at org.junit.runners.ParentRunner.access$000(ParentRunner.java:50)
at org.junit.runners.ParentRunner$2.evaluate(ParentRunner.java:222)
at org.junit.runners.ParentRunner.run(ParentRunner.java:300)
at org.eclipse.jdt.internal.junit4.runner.JUnit4TestReference.run(JUnit4TestReference.java:86)
at org.eclipse.jdt.internal.junit.runner.TestExecution.run(TestExecution.java:38)
at org.eclipse.jdt.internal.junit.runner.RemoteTestRunner.runTests(RemoteTestRunner.java:459)
at org.eclipse.jdt.internal.junit.runner.RemoteTestRunner.runTests(RemoteTestRunner.java:675)
at org.eclipse.jdt.internal.junit.runner.RemoteTestRunner.run(RemoteTestRunner.java:382)
at org.eclipse.jdt.internal.junit.runner.RemoteTestRunner.main(RemoteTestRunner.java:192)
world scheme
首先看在getWorldACL方法中的
Id id = new Id(WORLD,"anyone");//id = Ids.ANYONE_ID_UNSAFE;
ACL acl = new ACL(Perms.READ,id);
zookeeper包把zookeeper中的scheme:id:permissions的scheme:id封装成为了org.apache.zookeeper.data.Id,权限封装成为了Perms。每一个scheme下的id都不一样,甚至可能没有id。 上面的就使用的是world scheme(注意不是schema)获取的ACL,表示任何用户都有读取的权限。那么问题来了,为什么第一次创建可以成功呢?
那是因为第一次创建"/world"的路径是在根目录/下,而根目录/是所有用户都有权限的。所以创建成功了。第二次创建以为ACL只有"/world"的read权限,所以抛出了异常。
注意:创建目录的时候,父目录必须存在
DIGEST
老规矩,还是先来一个小例子:
import java.io.IOException;
import java.security.NoSuchAlgorithmException;
import java.util.ArrayList;
import java.util.List;
import org.apache.zookeeper.CreateMode;
import org.apache.zookeeper.KeeperException;
import org.apache.zookeeper.ZooDefs;
import org.apache.zookeeper.ZooKeeper;
import org.apache.zookeeper.data.ACL;
import org.apache.zookeeper.data.Id;
import org.apache.zookeeper.server.auth.DigestAuthenticationProvider;
import org.junit.Before;
import org.junit.Test;
public class ZookeeperDigestACLTest {
private static final String DIGEST = "digest";
private ZooKeeper zookeeper;
@Before
public void setUp() {
try {
zookeeper = new ZooKeeper("127.0.0.1:2181", 3000, null);
} catch (IOException e) {
e.printStackTrace();
}
}
@Test
public void testDigestACL(){
try {
String actualPath = zookeeper.create("/digest", "digest".getBytes(), getDigestACL(), CreateMode.EPHEMERAL);
System.out.println(actualPath);
} catch (KeeperException e) {
e.printStackTrace();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
@Test
public void testGetDigestSchemaData(){
ZooKeeper zookeeper = getDigestZK();
try {
byte[] data = zookeeper.getData("/digest", false, null);
System.out.println(new String(data));
} catch (KeeperException e) {
e.printStackTrace();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
List<ACL> getDigestACL(){
List<ACL> acls = new ArrayList<ACL>();
Id id = null;
try {
//设置ACL的时候使用密文
String sid = DigestAuthenticationProvider.generateDigest("admin:admin");
System.out.println(sid);
id = new Id(DIGEST,sid);
} catch (NoSuchAlgorithmException e) {
e.printStackTrace();
}
ACL acl = new ACL(ZooDefs.Perms.ALL, id);
acls.add(acl);
return acls;
}
ZooKeeper getDigestZK(){
ZooKeeper zk = null;
try {
zk = new ZooKeeper("localhost:2181",2000, null);
} catch (IOException e) {
e.printStackTrace();
}
//连接auth的时候使用明文
zk.addAuthInfo(DIGEST, "admin:admin".getBytes());
return zk;
}
}
digest其实就是一个加密认证,主要需要注意的是设置认证的时候是使用的密文,可以通过zookeeper包的DigestAuthenticationProvider.generateDigest来获取,认证的时候是使用的明文。digest它对应的id为username:BASE64(SHA1(password)) 我们可以看一下DigestAuthenticationProvider.generateDigest的源码就知道了:
static public String generateDigest(String idPassword)
throws NoSuchAlgorithmException {
String parts[] = idPassword.split(":", 2);
byte digest[] = MessageDigest.getInstance("SHA1").digest(
idPassword.getBytes());
return parts[0] + ":" + base64Encode(digest);
}
知道是使用base64对sha1加密后的数据进行编码,我们也可以很容易的自己实现,下面就是使用Apache的codec实现和DigestAuthenticationProvider.generateDigest的对比:
@Test
public void printPass(){
try {
String pass = DigestAuthenticationProvider.generateDigest("admin:admin");
System.out.println(pass);
} catch (NoSuchAlgorithmException e) {
e.printStackTrace();
}
byte[] sha1 = DigestUtils.sha1("admin:admin");
String result = Base64.encodeBase64String(sha1);
System.out.println("admin:"+result);
}
auth
import java.io.IOException;
import java.util.ArrayList;
import java.util.List;
import org.apache.zookeeper.CreateMode;
import org.apache.zookeeper.KeeperException;
import org.apache.zookeeper.ZooDefs;
import org.apache.zookeeper.ZooKeeper;
import org.apache.zookeeper.data.ACL;
import org.apache.zookeeper.data.Id;
import org.junit.Before;
import org.junit.Test;
public class ZookeeperAuthACLTest {
private static final String DIGEST = "digest";
private static final String AUTH = "auth";
ZooKeeper zookeeper;
@Before
public void setUp() {
try {
zookeeper = new ZooKeeper("127.0.0.1:2181", 3000, null);
} catch (IOException e) {
e.printStackTrace();
}
}
@Test
public void testAuthACL(){
// ZooKeeper zookeeper = getDigestZK();
ZooKeeper zookeeper = getAnotherDigestZK();
try {
String actualPath = zookeeper.create("/auth", "auth".getBytes(), getAuthACL(), CreateMode.EPHEMERAL);
System.out.println(actualPath);
} catch (KeeperException e) {
e.printStackTrace();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
@Test
public void testAuthGet(){
ZooKeeper zk = getAnotherDigestZK();
// ZooKeeper zk = getDigestZK();
try {
byte[] data = zk.getData("/auth", false, null);
System.out.println(new String(data));
} catch (KeeperException e) {
e.printStackTrace();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
List<ACL> getAuthACL(){
List<ACL> acls = new ArrayList<ACL>();
Id id = new Id(AUTH,"");
ACL acl = new ACL(ZooDefs.Perms.ALL, id);
acls.add(acl);
return acls;
}
ZooKeeper getDigestZK(){
ZooKeeper zk = null;
try {
zk = new ZooKeeper("localhost:2181",2000, null);
} catch (IOException e) {
e.printStackTrace();
}
//连接auth的时候使用明文
zk.addAuthInfo(DIGEST, "admin:admin".getBytes());
return zk;
}
ZooKeeper getAnotherDigestZK(){
ZooKeeper zk = null;
try {
zk = new ZooKeeper("localhost:2181",2000, null);
} catch (IOException e) {
e.printStackTrace();
}
//连接auth的时候使用明文
zk.addAuthInfo(DIGEST, "xxx:xxx".getBytes());
return zk;
}
}
auth和digest差不多auth也需要使用digest认证,不同的是创建znode节点和访问znode节点的必须是相同的认证。所以使用auth scheme的时候创建节点的时候就必须把认证加上,否则就会出错。
IP
import java.io.IOException;
import java.util.ArrayList;
import java.util.List;
import org.apache.zookeeper.CreateMode;
import org.apache.zookeeper.KeeperException;
import org.apache.zookeeper.ZooDefs;
import org.apache.zookeeper.ZooKeeper;
import org.apache.zookeeper.data.ACL;
import org.apache.zookeeper.data.Id;
import org.junit.Before;
import org.junit.Test;
public class ZookeeperIPACLTest {
private static final String IP = "ip";
ZooKeeper zookeeper;
@Before
public void setUp() {
try {
zookeeper = new ZooKeeper("127.0.0.1:2181", 3000, null);
} catch (IOException e) {
e.printStackTrace();
}
}
@Test
public void testIPACL(){
try {
String actualPath = zookeeper.create("/ip", "ip".getBytes(), getIPACL(), CreateMode.EPHEMERAL);
System.out.println(actualPath);
} catch (KeeperException e) {
e.printStackTrace();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
@Test
public void testIPGet(){
try {
byte[] data = zookeeper.getData("/ip", false, null);
System.out.println(new String(data));
} catch (KeeperException e) {
e.printStackTrace();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
List<ACL> getIPACL(){
List<ACL> acls = new ArrayList<ACL>();
// Id id = new Id(IP,"127.0.0.1");
Id id = new Id(IP,"192.168.1.255");
// Id id = new Id(IP,"192.168.1.0/24");
ACL acl = new ACL(ZooDefs.Perms.ALL, id);
acls.add(acl);
return acls;
}
}
ip scheme是通过客户端ip来过滤权限的。
附录
log4j配置
因为org.apache.zookeeper使用的是log4j,所以可以配置一个日志输出文件,可以获取一些Zookeeper的信息。比如当使用ZooKeeper不设置Watcher的时候就会抛出异常,但是这个异常是在ZooKeeper中处理的,所以如果不配做log4j是获取不到这些错误信息的。
#log4j.rootLogger = [ level ] , appenderName1 , appenderName2
#输出到控制台
#log4j.rootLogger=INFO,CONSOLE
#输出到文件
log4j.rootLogger=INFO,F
log4j.appender.CONSOLE=org.apache.log4j.ConsoleAppender
log4j.appender.CONSOLE.Threshold=INFO
log4j.appender.CONSOLE.layout=org.apache.log4j.PatternLayout
log4j.appender.CONSOLE.layout.ConversionPattern=%d{ISO8601} - %m%n
log4j.appender.F=org.apache.log4j.DailyRollingFileAppender
log4j.appender.F.Threshold=INFO
#可以修改配置日志输出目录
log4j.appender.F.File=F:/log/zookeeper.log
log4j.appender.F.DatePattern='.'yyyy-MM-dd
log4j.appender.F.layout=org.apache.log4j.PatternLayout
log4j.appender.F.layout.ConversionPattern=[%d{yyyy-MM-dd HH:mm:ss}] - %m%n
log4j.logger.org.displaytag=WARN
log4j.logger.org.apache.zookeeper=ERROR
log4j.logger.org.springframework=WARN
log4j.logger.org.I0Itec=WARN
pom
上面zookeeper包的版本使用的是3.3.3,commons-codec是1.10
<dependencies>
<dependency>
<groupId>org.apache.zookeeper</groupId>
<artifactId>zookeeper</artifactId>
<exclusions>
<exclusion>
<groupId>junit</groupId>
<artifactId>junit</artifactId>
</exclusion>
<exclusion>
<artifactId>slf4j-log4j12</artifactId>
<groupId>org.slf4j</groupId>
</exclusion>
</exclusions>
</dependency>
<dependency>
<groupId>commons-codec</groupId>
<artifactId>commons-codec</artifactId>
</dependency>
</dependencies>