setACL源码追踪入口#
同样会和addAuth操作一样,主线程从控制台解析出用户的请求封装进request然后封装进pakcet发送给服务端
setACL服务端的处理逻辑#
请求来到服务端,在遇到第一次checkAcl之间,请求会顺利的来到第一个处理器PrepRequestProcessor
, 所以我们的入口点就是这里
protected void pRequest(Request request) throws RequestProcessorException { // LOG.info("Prep>>> cxid = " + request.cxid + " type = " + // request.type + " id = 0x" + Long.toHexString(request.sessionId)); request.hdr = null; request.txn = null; // todo 下面的不同类型的信息, 对应这不同的处理器方式 try { switch (request.type) { case OpCode.create: // todo 创建每条记录对应的bean , 现在还是空的, 在面的pRequest2Txn 完成赋值 CreateRequest createRequest = new CreateRequest(); // todo 跟进这个方法, 再从这个方法出来,往下运行,可以看到调用了下一个处理器 pRequest2Txn(request.type, zks.getNextZxid(), request, createRequest, true); break; case OpCode.delete: DeleteRequest deleteRequest = new DeleteRequest(); pRequest2Txn(request.type, zks.getNextZxid(), request, deleteRequest, true); break; case OpCode.setData: SetDataRequest setDataRequest = new SetDataRequest(); pRequest2Txn(request.type, zks.getNextZxid(), request, setDataRequest, true); break; case OpCode.setACL: // todo 客户端发送的setAcl命令, 会流经这个选项 SetACLRequest setAclRequest = new SetACLRequest(); /** SetACLRequest的属性 * private String path; * private java.util.List<org.apache.zookeeper.data.ACL> acl; * private int version; */ // todo 继续跟进去 pRequest2Txn(request.type, zks.getNextZxid(), request, setAclRequest, true); break; case OpCode.check:
用户在控制台输入类似 setAcl /node4 digest:zhangsan:jA/7JI9gsuLp0ZQn5J5dcnDQkHA=
请求将被解析运行到上面的case OpCode.setACL:
它new了一个空的对象SetACLRequest,这个对象一会在pRequest2Txn()函数中进行初始化
继续跟进pRequest2Txn(request.type, zks.getNextZxid(), request, setAclRequest, true);
源码如下: 它的解析我写在这段代码的下面
protected void pRequest2Txn(int type, long zxid, Request request, Record record, boolean deserialize) throws KeeperException, IOException, RequestProcessorException { // todo 使用request的相关属性,创建出 事务Header request.hdr = new TxnHeader(request.sessionId, request.cxid, zxid, Time.currentWallTime(), type); switch (type) { case OpCode.create: // todo 校验session的情况 zks.sessionTracker.checkSession(request.sessionId, request.getOwner()); CreateRequest createRequest = (CreateRequest)record; . . . case OpCode.setACL: // todo 检查session的合法性 zks.sessionTracker.checkSession(request.sessionId, request.getOwner()); // todo record; 上一步中new 出来的SetACLRequest空对象, // todo 这样设计的好处就是, 可以进行横向的扩展, 让当前这个方法 PRequest2Tm()中可以被Record的不同实现类复用 SetACLRequest setAclRequest = (SetACLRequest)record; // todo 将结果反序列化进 setAclRequest if(deserialize) ByteBufferInputStream.byteBuffer2Record(request.request, setAclRequest); // todo 获取path 并校验 path = setAclRequest.getPath(); validatePath(path, request.sessionId); // todo 去除重复的acl listACL = removeDuplicates(setAclRequest.getAcl()); if (!fixupACL(request.authInfo, listACL)) { // todo request.authInfo的默认值就是本地ip, 如果没有这个值的话,在server本地,client都连接不上 throw new KeeperException.InvalidACLException(path); } //todo 获取当前节点的record nodeRecord = getRecordForPath(path); // todo 共用的checkACL 方法 // todo 在setAcl时,使用checkACL进行权限的验证 // todo nodeRecord.acl 当前节点的acl // todo 跟进这个方法 checkACL(zks, nodeRecord.acl, ZooDefs.Perms.ADMIN, request.authInfo); version = setAclRequest.getVersion(); currentVersion = nodeRecord.stat.getAversion(); if (version != -1 && version != currentVersion) { throw new KeeperException.BadVersionException(path); } version = currentVersion + 1; request.txn = new SetACLTxn(path, listACL, version); nodeRecord = nodeRecord.duplicate(request.hdr.getZxid()); nodeRecord.stat.setAversion(version); addChangeRecord(nodeRecord); break; // todo createSession///////////////////////////////////////////////////////////////// case OpCode.createSession: . . .
- 先说一下有个亮点, 就是这个函数中倒数第二个参数位置写着需要的参数是record类型的,但是实际上我们传递进来的类型是
SetACLRequest
上面的这个空对象SetACLRequest
这样的设计使得的扩展性变得超级强
这是record的类图
言归正传,来到这个函数算是进入了第二个高潮, 他主要做了这几件事
- 检查session是否合法
- 将数据反序列化进
SetACLRequest
- 校验path是否合法
- 去除重复的acl
- CheckAcl鉴权
我们重点看最后两个地方
去除重复的acl#
fixupACL(request.authInfo, listACL)
这个函数很有趣,举个例子,通过控制台,我们连接上一个服务端,然后通过如下命令往服务端的authInfo集合中添加三条数据
addauth digest lisi1:1 addauth digest lisi2:2 addauth digest lisi3:3
然后给lisi授予针对node1的权限
setAcl /node auth:lisi1:123123:adr
再次查看,会发现lisi2 lisi3同样有了对node1的权限
CheckAcl鉴权#
checkACL(zks, nodeRecord.acl, ZooDefs.Perms.ADMIN,request.authInfo);
源码如下:
这个函数的主要逻辑就是,从头到尾的执行,只要满足了合法的权限就退出,否则运行到最后都没有合法的权限,就抛出没有授权的异常从而中断请求,如果正常返回了,说明权限经过了验证,既然经过了验证request就可以继续在process链上运行,进一步进行处理
static void checkACL(ZooKeeperServer zks, List<ACL> acl, int perm, List<Id> ids) throws KeeperException.NoAuthException { // todo 这是个写在配置文件中的 配置属性 zookeeper.skipACL , 可以关闭acl验证 if (skipACL) { return; } // todo 当前的节点没有任何验证的规则的话,直接通过 if (acl == null || acl.size() == 0) { return; } // todo 如果ids中存放着spuer 超级用户,也直接通过 for (Id authId : ids) { if (authId.getScheme().equals("super")) { return; } } // todo 循环当前节点上存在的acl点 for (ACL a : acl) { Id id = a.getId(); // todo 使用& 位运算 , 去ZooDefs类看看位移的情况 // todo 如果设置的权限为 a.getPerms() =dra = d+r+a = 8+1+16 = 25 // todo perm = 16 /** * 进行&操作 * 25 & 16 * 11001 * 10000 * 结果 * 10000 * 结果不是0 ,进入if { } */ if ((a.getPerms() & perm) != 0) { if (id.getScheme().equals("world") && id.getId().equals("anyone")) { return; } AuthenticationProvider ap = ProviderRegistry.getProvider(id.getScheme()); if (ap != null) { for (Id authId : ids) { if (authId.getScheme().equals(id.getScheme()) && ap.matches(authId.getId(), id.getId())) { return; } } } } } //todo 到最后也没返回回去, 就抛出异常 throw new KeeperException.NoAuthException(); }
几个重要的参数
- acl
- 当前node已经存在的 需要的权限信息
scheme:id;
- perm
- 当前用户的操作需要的权限
- ids
- 我们在上面通过addauth添加进authInfo列表中的信息
- skip跳过权限验证
static boolean skipACL; static { skipACL = System.getProperty("zookeeper.skipACL", "no").equals("yes"); if (skipACL) { LOG.info("zookeeper.skipACL==\"yes\", ACL checks will be skipped"); } }
这里面在验证权限时存在位运算,prem在ZooDFS.java中维护
// todo 位移的操作 @InterfaceAudience.Public public interface Perms { // 左移 int READ = 1 << 0; //1 2的0次方 int WRITE = 1 << 1; //2 2的1次方 int CREATE = 1 << 2; // 4 int DELETE = 1 << 3; // 8 int ADMIN = 1 << 4; // 16 int ALL = READ | WRITE | CREATE | DELETE | ADMIN; //31 /** * 00001 * 00010 * 00100 * 01000 * 10000 * * 结果11111 = 31 * */ }
总结:#
通过跟踪上面的源码,我们知道了zookeeper的权限acl是如何实现的,以及客户端和服务端之间是如何相互配合的
- 客户端同样是经过主线程跟进不同的命令类型,将请求打包packet发送到服务端
- 服务端将addauth添加认证信息保存在内存中
- node会被持久化,因为它需要的认证同样被持久化
- 在进行处理request之前,会进行checkAcl的操作,它是在第一个处理器中完成的,只有经过权限认证,request才能继续在processor链中往下传递