Kerberos认证代码分析Can't get Kerberos realm
1. Can't get Kerberos realm
原因分析:
原始代码为:
1 2 |
|
首先根据传进来的Hadoop配置conf,去设置UserGroupInformation(UGI),方法的调用关系如下(删除了部分不相关代码):
1 2 3 |
|
initialize方法如下
1 2 3 4 5 6 7 8 9 10 11 12 |
|
setConfiguration方法如下
1 2 3 4 5 6 7 8 9 10 11 12 13 14 |
|
getDefaultRealm使用了反射,目的是为了兼容两套jdk,即IBM(com.ibm.security.krb5.internal.Config) 和 Oracle(sun.security.krb5.Config)
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 |
|
从上述代码来看,先获取Config类引用,然后getInstanceMethod是获得getInstance方法,再次getDefaultRealmMethod是获得getDefaultRealm方法。
因此,假设我们是使用的Oracle的JDK,那么最后是调用的sun.security.krb5.getDefaultRealm()。接下来看一下sun.security.krb5.getDefaultRealm()是如何实现的。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 |
|
我们假设defaultRealm = null,看一下如何从var2 = this.getRealmFromDNS();来获取defaultRealm
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 |
|
mapHostToRealm()方法如下:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 |
|
这里会获取Config的单例对象,
1 2 3 4 5 6 7 |
|
再看Config.getInstance();的具体动作就是判断单例对象是否为null,不为null直接返回,为null重新new一个Config对象。
同时,Config类中还有一个方法refresh,其代码如下:
1 2 3 4 |
|
从refresh的代码看,只要调用refresh()方法,就会重新生成Config的单例对象。这个refresh()方法,也是我们代码里面要调用的。
再回顾一下我们的原始代码:
1 2 |
|
回到getInstance()方法,假设singleton单例是null,会生成Config的单例对象。以后,再次调用getInstance方法都会直接返回这个单例对象了,没有再new的机会了。有人开始质疑没有机会new Config()对象了? 调用Config.refresh()方法不是可以new吗? 答案是可以new,但是如果我们的UserGroupInformation.setConfiguration(conf)会抛出异常,是不是Config.refresh()方法就不会被调用了! 我们的错误就是出现在这里,后面分析UserGroupInformation.setConfiguration(conf)怎么抛出异常了。
在我们来看一下new Config()具体做了什么事情。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 |
|
我们的问题就出在var2 = this.loadConfigFile(var3); 位置,因为加载/etc/krb5.conf文件的时候,恰好/etc/krb5.conf文件不存在,因为我们会把修改的krb5.conf去替换/etc/krb5.conf文件,在替换的时间内,恰好去loadConfigFile(),该方法就报了FileNotFoundException的异常。这个异常一直throw到UserGroupInformation.setConfiguration(conf)调用的地方,导致我们永远调用不到Config.refresh()方法。
2. 报错com.google.common.util.concurrent.UncheckedTimeoutException: java.util.concurrent.TimeoutException
原因分析:首先这个异常是因为调试上述报错产生的,所以顺便分析下原因。
上述报错是Can't get Kerberos realm,网上查一下,大概是因为拿不到kdc和realm。
因此,我在JVM启动参数中添加了如下3个参数:
1 2 3 |
|
指定了krb5.conf文件,kdc地址,realm值。然后重启程序,发现可以正常使用,然后把/etc/krb5.conf文件删除了(上个错误其实猜想到了是因为读不到krb5.conf造成的)。
程序竟然报错 java.util.concurrent.TimeoutException,打jstack
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 |
|
从jstack中看到UDPClient.receive卡主了,为什么卡主了,不知道! 问大神,大神说加入JVM调试参数-Dsun.security.krb5.debug=true,可以打印日志到console中。在console中看到如下日志:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 |
|
看到默认去连了KDC的88端口,默认端口被改成了1088,所以连接失败,导致超时。 听说没有参数可以设置KDC的端口, 不知道真假,在-Djava.security.krb5.kdc参数中指定kdc端口无效。
参考: https://docs.oracle.com/javase/7/docs/technotes/guides/security/jgss/tutorials/KerberosReq.html 及源代码