JAAS – 无法将Kerberos票证持久化到缓存文件,无法从头开始创建缓存…以及其他详细信息

我正在开发一个使用JAAS执行身份validation的Java应用程序,应该按如下方式工作:(i)当用户uclient的票证已经在本地缓存中时,它应该在不询问凭证的情况下对用户进行身份validation,(ii)当没有“uclient”的票证时’在缓存中它应该询问用户名/密码并将获取的票证保存到本地缓存中。

我的应用程序能够执行’i’但无法执行’ii’,它正确地validation用户(创建主题/主体),但它不会将Krb票证持久存储到缓存中。

问题

  1. 我如何实现/实现这一目标?
  2. 并且..这是否会在空/不存在时创建Kerberos缓存文件? – 如何从Java中以编程方式实现缓存文件创建/初始化?
  3. 而且……仅仅是为了好奇,Java JaaS是否能够管理linux KEYRING? (目前Jaas无法自动管理它们)
  4. Java JaaS是否只能管理/持久化缓存中的默认主体的票证? – 或者我如何管理JaaS,我在单个缓存文件中有很多主体的票?

请注意,我的应用程序必须在Windows AD和Linux Realms中运行。

关于我的环境和我当前代码的更多数据

我正在使用FreeIPA客户端和服务器配置的Linux Kerberos Realm中测试客户端应用程序。 我有一个Linux VM,它为领域AUTHDEMO.IT提供KDC,以及一个支持AUTHDEMO.IT领域的Linux VM。 krb5.conf配置:

 includedir /var/lib/sss/pubconf/krb5.include.d/ [libdefaults] default_realm = AUTHDEMO.IT dns_lookup_realm = true dns_lookup_kdc = true rdns = false ticket_lifetime = 24h forwardable = true udp_preference_limit = 0 default_ccache_name = KEYRING:persistent:%{uid} [realms] AUTHDEMO.IT = { pkinit_anchors = FILE:/etc/ipa/ca.crt } [domain_realm] .authdemo.it = AUTHDEMO.IT authdemo.it = AUTHDEMO.IT 

这是应用程序的jaas.conf

 JaasDemo { com.sun.security.auth.module.Krb5LoginModule required useTicketCache=true principal=uclient debug=true; }; 

我没有指定默认的缓存文件名,我在调试中validation它默认为: /tmp/krb5cc_1000其中1000是运行用户的uid。

在JaasDemo类实例中,我使用此login方法执行身份validation:

 public LoginContext login(){ LoginContext lc = null; try { System.out.println("Initialize logincontext"); lc = new LoginContext("JaasLogin", new TextCallbackHandler()); } catch (LoginException | SecurityException le) { System.err.println("Cannot create LoginContext." + le.getMessage()); return lc; } try { // attempt authentication System.out.println("Attempt login"); lc.login(); } catch (LoginException le) { System.err.println("Authentication failed:"); System.err.println(" " + le.getMessage()); return lc; } System.out.println("Authentication succeeded!"); return lc; } 

我已使用此命令执行了我的应用程序(请注意详细的kerberos日志记录的选项):

 java -Dsun.security.krb5.debug=true -Dsun.security.jgss.debug=true -Djava.security.auth.login.config=jaas.conf -jar myapp.jar 

以下是应用程序在不同情况下的输出,请注意,当被询问时,用户以交互方式提供正确的凭据。 第一种情况不存在/tmp/krb5cc_1000文件:

 Initialize logincontext Attempt login Debug is true storeKey false useTicketCache true useKeyTab false doNotPrompt false ticketCache is null isInitiator true KeyTab is null refreshKrb5Config is false principal is uclient tryFirstPass is false useFirstPass is false storePass is false clearPass is false Acquire TGT from Cache >>>KinitOptions cache name is /tmp/krb5cc_1000 Principal is uclient@AUTHDEMO.IT null credentials from Ticket Cache **Login Handler invoked, providing username and password to login manager..** [Krb5LoginModule] user entered username: uclient Using builtin default etypes for default_tkt_enctypes default etypes for default_tkt_enctypes: 18 17 16 23. >>> KrbAsReq creating message getKDCFromDNS using UDP >>> KrbKdcReq send: kdc=authdemo2.authdemo.it. UDP:88, timeout=30000, number of retries =3, #bytes=143 >>> KDCCommunication: kdc=authdemo2.authdemo.it. UDP:88, timeout=30000,Attempt =1, #bytes=143 >>> KrbKdcReq send: #bytes read=283 >>>Pre-Authentication Data: PA-DATA type = 136 >>>Pre-Authentication Data: PA-DATA type = 19 PA-ETYPE-INFO2 etype = 18, salt = REMOVED 3@, s2kparams = null PA-ETYPE-INFO2 etype = 17, salt = REMOVED, s2kparams = null >>>Pre-Authentication Data: PA-DATA type = 2 PA-ENC-TIMESTAMP >>>Pre-Authentication Data: PA-DATA type = 133 >>> KdcAccessibility: remove authdemo2.authdemo.it.:88 >>> KDCRep: init() encoding tag is 126 req type is 11 >>>KRBError: cTime is Wed Jun 29 17:12:49 CEST 1988 583600369000 sTime is Wed Aug 02 15:53:28 CEST 2017 1501682008000 suSec is 981130 error code is 25 error Message is Additional pre-authentication required cname is uclient@AUTHDEMO.IT sname is krbtgt/AUTHDEMO.IT@AUTHDEMO.IT eData provided. msgType is 30 >>>Pre-Authentication Data: PA-DATA type = 136 >>>Pre-Authentication Data: PA-DATA type = 19 PA-ETYPE-INFO2 etype = 18, salt = REMOVED 3@, s2kparams = null PA-ETYPE-INFO2 etype = 17, salt = REMOVED, s2kparams = null >>>Pre-Authentication Data: PA-DATA type = 2 PA-ENC-TIMESTAMP >>>Pre-Authentication Data: PA-DATA type = 133 KRBError received: NEEDED_PREAUTH KrbAsReqBuilder: PREAUTH FAILED/REQ, re-send AS-REQ Using builtin default etypes for default_tkt_enctypes default etypes for default_tkt_enctypes: 18 17 16 23. Using builtin default etypes for default_tkt_enctypes default etypes for default_tkt_enctypes: 18 17 16 23. >>> EType: sun.security.krb5.internal.crypto.Aes256CtsHmacSha1EType >>> KrbAsReq creating message getKDCFromDNS using UDP >>> KrbKdcReq send: kdc=authdemo2.authdemo.it. UDP:88, timeout=30000, number of retries =3, #bytes=225 >>> KDCCommunication: kdc=authdemo2.authdemo.it. UDP:88, timeout=30000,Attempt =1, #bytes=225 >>> KrbKdcReq send: #bytes read=674 >>> KdcAccessibility: remove authdemo2.authdemo.it.:88 >>> EType: sun.security.krb5.internal.crypto.Aes256CtsHmacSha1EType >>> KrbAsRep cons in KrbAsReq.getReply uclient principal is uclient@AUTHDEMO.IT Commit Succeeded Authentication succeeded! Subject.toString: Principal: uclient@AUTHDEMO.IT Private Credential: Ticket (hex) = REMOVED TICKET DETAILS K. Client Principal = uclient@AUTHDEMO.IT Server Principal = krbtgt/AUTHDEMO.IT@AUTHDEMO.IT Session Key = EncryptionKey: keyType=18 keyBytes (hex dump)= REMOVED Forwardable Ticket true Forwarded Ticket false Proxiable Ticket false Proxy Ticket false Postdated Ticket false Renewable Ticket false Initial Ticket false Auth Time = Wed Aug 02 15:53:28 CEST 2017 Start Time = Wed Aug 02 15:53:28 CEST 2017 End Time = Thu Aug 03 15:53:28 CEST 2017 Renew Till = null Client Addresses Null 

存在第二种情况 /tmp/krb5cc_1000文件,其中包含另一个用户的票证(使用kinit -c创建); 应用程序正确validation,但获取的票证不会持久保存到缓存文件。

klist状态优先于应用程序执行:

 klist -c /tmp/krb5cc_1000 Ticket cache: FILE:/tmp/krb5cc_1000 Default principal: otheruser@AUTHDEMO.IT Valid starting Expires Service principal 08/02/2017 16:05:19 08/03/2017 16:05:13 krbtgt/AUTHDEMO.IT@AUTHDEMO.IT 

申请表输出:

 Initialize logincontext Attempt login Debug is true storeKey false useTicketCache true useKeyTab false doNotPrompt false ticketCache is null isInitiator true KeyTab is null refreshKrb5Config is false principal is uclient tryFirstPass is false useFirstPass is false storePass is false clearPass is false Acquire TGT from Cache >>>KinitOptions cache name is /tmp/krb5cc_1000 java.io.IOException: Primary principals don't match. at sun.security.krb5.internal.ccache.FileCredentialsCache.load(FileCredentialsCache.java:179) at sun.security.krb5.internal.ccache.FileCredentialsCache.acquireInstance(FileCredentialsCache.java:82) at sun.security.krb5.internal.ccache.CredentialsCache.getInstance(CredentialsCache.java:83) at sun.security.krb5.Credentials.acquireTGTFromCache(Credentials.java:333) at com.sun.security.auth.module.Krb5LoginModule.attemptAuthentication(Krb5LoginModule.java:665) at com.sun.security.auth.module.Krb5LoginModule.login(Krb5LoginModule.java:617) at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method) at sun.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:62) at sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43) at java.lang.reflect.Method.invoke(Method.java:498) at javax.security.auth.login.LoginContext.invoke(LoginContext.java:755) at javax.security.auth.login.LoginContext.access$000(LoginContext.java:195) at javax.security.auth.login.LoginContext$4.run(LoginContext.java:682) at javax.security.auth.login.LoginContext$4.run(LoginContext.java:680) at java.security.AccessController.doPrivileged(Native Method) at javax.security.auth.login.LoginContext.invokePriv(LoginContext.java:680) at javax.security.auth.login.LoginContext.login(LoginContext.java:587) at it.kerberosdemo.login.JaasDemo.login(JaasDemo.java:45) at it.kerberosdemo.login.JaasDemo.login(JaasDemo.java:27) at it.male.kerberosdemo.client.ClientMain.main(ClientMain.java:29) Principal is uclient@AUTHDEMO.IT null credentials from Ticket Cache Login Handler invokerd, providing username and password to login manager.. [Krb5LoginModule] user entered username: uclient Using builtin default etypes for default_tkt_enctypes default etypes for default_tkt_enctypes: 18 17 16 23. >>> KrbAsReq creating message getKDCFromDNS using UDP >>> KrbKdcReq send: kdc=authdemo2.authdemo.it. UDP:88, timeout=30000, number of retries =3, #bytes=143 >>> KDCCommunication: kdc=authdemo2.authdemo.it. UDP:88, timeout=30000,Attempt =1, #bytes=143 >>> KrbKdcReq send: #bytes read=283 >>>Pre-Authentication Data: PA-DATA type = 136 >>>Pre-Authentication Data: PA-DATA type = 19 PA-ETYPE-INFO2 etype = 18, salt = REMOVED, s2kparams = null PA-ETYPE-INFO2 etype = 17, salt = REMOVED, s2kparams = null >>>Pre-Authentication Data: PA-DATA type = 2 PA-ENC-TIMESTAMP >>>Pre-Authentication Data: PA-DATA type = 133 >>> KdcAccessibility: remove authdemo2.authdemo.it.:88 >>> KDCRep: init() encoding tag is 126 req type is 11 >>>KRBError: cTime is Mon Sep 22 16:38:56 CEST 2031 1947854336000 sTime is Wed Aug 02 16:07:05 CEST 2017 1501682825000 suSec is 803283 error code is 25 error Message is Additional pre-authentication required cname is uclient@AUTHDEMO.IT sname is krbtgt/AUTHDEMO.IT@AUTHDEMO.IT eData provided. msgType is 30 >>>Pre-Authentication Data: PA-DATA type = 136 ...OMITTED IDENTICAL 

klist确认没有为“uclient”添加到缓存文件中的票证。

问候

你不能。 Java不支持将TGT或服务票证持久保存回基于文件的缓存,该缓存可与MIT Kerberos或Heimdal一起使用。 Oracle有一些私有类可以做到这一点,但我不建议这样做。

最后我找到了问题1 + 2的答案

与java发行版捆绑在一起的kinit命令是一个java应用程序,它将用户validation到域/域中,并将获取的票证保存在ccache文件中。 kinit命令代码可以在OpenJDK的sun.security.krb5.internal.tools包中找到。 主要类是sun.security.krb5.internal.tools.Kinit 。 为了获取(validation)并保留Kerberos票证,您可以将所有tool包复制到您的应用程序中,并通过提供cli参数从Kinit类调用方法main(String[] arv) 。 您也可以像我一样更改Kinit类,以便更好地与代码集成。

Kinit代码非常有用,以便了解内部私有Kerberos代码的内部工作方式以及自定义它。 例如,有一个KDCOptions实例,您可以手动配置以请求可更新的票证等等。 我们来研究吧! 😉

请考虑:

  • 在将来的JDK版本中,无法保证内部代码的接口不会保持不变
  • 不保证不同JDK供应商之间的内部代码接口是相同的。

我可以确认我的代码在OpenJDK和Oracle JDK上都运行良好。

大局

目前,我的应用程序使用Jaas通过查看本地ccache文件中的Krb凭据进行身份validation,如果失败,它将执行上面提到的kinit代码。 然后,它从更新的ccache文件中与Jaas进行身份validation。

下一步

我目前正在尝试直接从主题对象的凭据中将Kerberos票证持久化到ccache。
我将尝试使用sun.security.krb5.internal.ccache.FileCredentialCache类,但它看起来是一种低级别的方法。 让我们来看看在kinit代码中使用CredentialCache抽象类,可能会有用。 如果成功,我会更新post。

谢谢

感谢Michael-O向我展示了sun.security.krb5.internal包,我终于找到了kinit代码。

问候

其他问题。

3 – 只是出于好奇,Java JaaS是否能够管理linux KEYRING? (目前Jaas无法自动管理它们)

不,内部Java Krb类只管理文件而不是KEYRING。

4 – Java JaaS是否只能管理/保留缓存中默认主体的票证? – 或者我如何管理JaaS,我在单个缓存文件中有很多主体的票?

我发现没有简单的方法来管理集合(这是最近的标准),我个人的选择是为每个主体创建一个缓存文件。