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 |
overrideNameRules) {
|
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 及原始碼