Java登录Kerberos认证过期问题

问题:

最近CDH集群增加了kerberos认证,发现了JavaWeb应用启动后,超过24小时后,kerberos凭证过期导致查询Hbase失败的问题。

 

Spark程序连接CDH时,通过principal和keytab配置方式,内部会将凭证到hdfs上,供Executor和Driver使用。当凭证Ticket快要失效时,会通过Keytab重新生成凭证。

spark2-submit \
 --master yarn \
 --deploy-mode cluster \
 --num-executors 3 \
 --driver-memory 4g \
 --executor-memory 4g \
 --executor-cores 2 \
 --conf spark.default.parallelism=10 \
 --conf spark.shuffle.file.buffe=1024k \
 --conf spark.executor.memoryOverhead=4096 \
 --class com.hypers.streaming.PanoramaMain \
 --conf "spark.executor.extraJavaOptions=-verbose:gc -XX:+PrintGCDetails" \
 --principal xxxx \
 --keytab xxxx.keytab \
  xxxx.jar

但我们自己开发的JavaWeb服务没有该机制,导致Ticket在程序运行时过期失效,最终查询Hbase时提示没有有效凭证,查询失败。

 

源码调试:

本地调试源码验证后,发现确实是因为Ticket的到期时间每次启动时候都会指定成一个固定的时间,后续也没有进行更新,最终过期导致应用查询Hbase失败。

重新编译了源码,增加了Ticket凭证相关的日志,最终确认了问题就是Ticket没有更新到期有效时间,导致认证过期。打印的日志如下:

checkTGTAndReloginFromKeytab! shouldRenewImmediatelyForTests:false, Time.now():1606492834061, getRefreshTime(tgt):1606526141000, tgt:Ticket (hex) = 
0000: 61 82 01 40 30 82 01 3C   A0 03 02 01 05 A1 0D 1B  a..@0..<........
0010: 0B 45 53 53 45 4E 43 45   2E 43 4F 4D A2 20 30 1E  .ESSENCE.COM. 0.
0020: A0 03 02 01 02 A1 17 30   15 1B 06 6B 72 62 74 67  .......0...krbtg
0030: 74 1B 0B 45 53 53 45 4E   43 45 2E 43 4F 4D A3 82  t..ESSENCE.COM..
0040: 01 02 30 81 FF A0 03 02   01 12 A1 03 02 01 01 A2  ..0.............
0050: 81 F2 04 81 EF B4 0C DB   10 F7 5F 24 41 A8 91 70  .........._$A..p
0060: A0 76 13 DE 32 C2 29 82   D3 2D D0 50 34 E3 A9 B1  .v..2.)..-.P4...
0070: 4B B0 CF 55 CF 2E 83 8C   CB AE 05 2E 77 C5 14 6B  K..U........w..k
0080: 56 B0 63 DD 32 A7 C9 BA   DE 51 9B FF 06 C5 01 6F  V.c.2....Q.....o
0090: 16 01 AD E2 BB C4 6E C5   B3 BC 35 FE 0D AF 20 49  ......n...5... I
00A0: CF F7 4C 90 C7 7F A0 F5   A1 19 A2 FF 3D FF AB 67  ..L.........=..g
00B0: EF 91 FA C4 ED 59 C1 53   38 7A BD B2 FF FD FC 84  .....Y.S8z......
00C0: E5 16 DE 9F 08 62 49 65   57 86 57 6D 03 A2 96 83  .....bIeW.Wm....
00D0: F9 80 1E E6 F7 E4 4B 4F   9C 00 55 B9 4A A2 DA E2  ......KO..U.J...
00E0: F2 01 86 11 1C B5 B1 01   9A F6 29 75 C2 D1 80 8A  ..........)u....
00F0: 90 7F 35 C2 D0 A2 65 C3   9A 8B 9C 00 5E 20 EB 6C  ..5...e.....^ .l
0100: CF 1A 04 FC 20 8C 7B 4B   98 0A 0F 08 36 EC 94 7E  .... ..K....6...
0110: AF 71 4D A1 E1 DA 95 4A   50 5A A8 1B 39 4A F0 B6  .qM....JPZ..9J..
0120: 99 60 71 C7 E4 05 1A 54   0C 05 50 C5 42 B0 97 08  .`q....T..P.B...
0130: 29 5A 48 7E 8F A7 C9 BD   A6 9B 19 E4 A7 2A ED 48  )ZH..........*.H
0140: 8F 5F 1A D2                                        ._..

Client Principal = @xx.COM
Server Principal = xxxx/[email protected]
Session Key = EncryptionKey: keyType=18 keyBytes (hex dump)=
0000: A2 ED 15 B4 25 87 D1 B8   70 23 96 41 48 4B B6 79  ....%...p#.AHK.y
0010: 96 60 1A 1D EF D7 50 C0   31 C6 F5 63 8A 65 56 9E  .`....P.1..c.eV.


Forwardable Ticket true
Forwarded Ticket false
Proxiable Ticket false
Proxy Ticket false
Postdated Ticket false
Renewable Ticket false
Initial Ticket false
Auth Time = Fri Nov 27 14:03:41 CST 2020
Start Time = Fri Nov 27 14:03:41 CST 2020
End Time = Sat Nov 28 14:03:41 CST 2020
Renew Till = null
Client Addresses  Null 
主要是End Time、Renew Till字段,源码里的解释:
/**
 * Returns the start time for this ticket's validity period.
 *
 * @return the start time for this ticket's validity period
 *         or null if not set.
 */
public final java.util.Date getStartTime() {
    return (startTime == null) ? null : (Date)startTime.clone();
}

/**
 * Returns the expiration time for this ticket's validity period.
 *
 * @return the expiration time for this ticket's validity period.
 */
public final java.util.Date getEndTime() {
    return (endTime == null) ? null : (Date) endTime.clone();
}

/**
 * Returns the latest expiration time for this ticket, including all
 * renewals. This will return a null value for non-renewable tickets.
 *
 * @return the latest expiration time for this ticket.
 */
public final java.util.Date getRenewTill() {
    return (renewTill == null) ? null: (Date)renewTill.clone();
}

 

解决思路:

最后找到的解决方案是需要我们自己去定期刷新(重新登录)凭证来使凭证永久生效,或者指定一个较长的 ticket_lifetime 凭证有效期,来让应用尽可能推迟到期时间。

参考:https://stackoverflow.com/questions/33211134/hbase-kerberos-connection-renewal-strategy/33243360

 

最终解决方案:

在连接Hbase,触发了首次登录Kerberos的时候,启动一个定时任务,每次定时调用checkTGTAndReloginFromKeytab()方法来更新凭证,来达到定时更新的效果。

 

  conf.addResource("hbase-site-test.xml");
                            conf.addResource("core-site-test.xml");
                            conf.addResource("hdfs-site-test.xml");
                            try {
                                System.setProperty("java.security.krb5.conf", Constants.KRB5_CONF_PATH);
                                UserGroupInformation.setConfiguration(conf);
                                UserGroupInformation.loginUserFromKeytab(Constants.KEYTAB_USER, Constants.KEYTAB_PATH);
                                startCheckKeytabTgtAndReloginJob(); // 定时更新凭证任务
                            } catch (IOException e) {
                                logger.error("Login failure", e);
                            }
 /**
     * * 定时更新凭证
     */
    private static void startCheckKeytabTgtAndReloginJob() {
        //10分钟循环 达到距离到期时间一定范围就会更新凭证
        ThreadPool.updateConfigThread.scheduleWithFixedDelay(() -> {
            try {
                UserGroupInformation.getLoginUser().checkTGTAndReloginFromKeytab();
                logger.warn("get tgt:{}", UserGroupInformation.getLoginUser().getTGT());
                logger.warn("Check Kerberos Tgt And Relogin From Keytab Finish.");
            } catch (IOException e) {
                logger.error("Check Kerberos Tgt And Relogin From Keytab Error", e);
            }
        }, 0, 10, TimeUnit.MINUTES);
        logger.warn("Start Check Keytab TGT And Relogin Job Success.");
    }

注:也可通过reloginFromTicketCache()方法进行更新,但依赖于服务器上的认证缓存。或者直接调用kinit -R来更新(参考Hadoop源码中的续期方式)。

            // 参考Hadoop源码中的续期方式
            try {
                String cmd = "kinit";
                Shell.execCommand(cmd, "-R");
                log.warn("exec kinit -R!");
            } catch (IOException e) {
               log.warn("relogin error.",e);
            }
            try {
                UserGroupInformation.getLoginUser().reloginFromTicketCache();
            } catch (IOException e) {
                log.warn("relogin error.",e);
            }

 

观察结论:

Ticket的有效期已经更新,目前运行了几天,一起正常。后续继续观察。

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章