JAAS - 无法将 Kerberos 票证保留到缓存文件,并且无法从头开始创建缓存..以及其他详细信息

Fab*_*lao 5 java kerberos jaas

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

我的应用程序能够执行“i”,但无法执行“ii”,它正确验证用户身份(创建主题/主体),但不会将 Krb 票证保留到缓存中。

问题

  1. 我如何实现/实现这一目标?
  2. 并且..这是否会在空/不存在时创建 Kerberos 缓存文件?- 如何从 Java 以编程方式实现缓存文件创建/初始化?
  3. 而且..只是出于好奇,Java JaaS 能够管理 linux KEYRING 吗?(目前 Jaas 无法自动管理它们)
  4. Java JaaS 是否只能在缓存中管理/保留默认主体的票证?- 或者,如果我在单个缓存文件中拥有大量主体的票证,我该如何使用 JaaS 进行管理?

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

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

我正在配置了 FreeIPA 客户端和服务器的 Linux Kerberos 领域中测试客户端应用程序。我有一个为 AUTHDEMO.IT 领域提供 KDC 的 Linux VM,以及一个被认可进入 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
Run Code Online (Sandbox Code Playgroud)

这是jaas.conf针对应用程序的:

JaasDemo {
   com.sun.security.auth.module.Krb5LoginModule required 
   useTicketCache=true
   principal=uclient
   debug=true; 
};
Run Code Online (Sandbox Code Playgroud)

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

在 JaasDemo 类实例中,我使用以下login方法执行身份验证:

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;
    }
Run Code Online (Sandbox Code Playgroud)

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

java  -Dsun.security.krb5.debug=true -Dsun.security.jgss.debug=true -Djava.security.auth.login.config=jaas.conf -jar myapp.jar
Run Code Online (Sandbox Code Playgroud)

以下是不同情况下应用程序的输出,请注意,当被询问时,用户以交互方式提供正确的凭据。 第一种情况不存在/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 
Run Code Online (Sandbox Code Playgroud)

存在第二种情况 /tmp/krb5cc_1000文件,其中包含另一个用户的票证(使用 kinit -c 创建);应用程序正确进行身份验证,但获取的票证未保留到缓存文件中。

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
Run Code Online (Sandbox Code Playgroud)

应用程序的输出:

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
Run Code Online (Sandbox Code Playgroud)

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

问候

Fab*_*lao 7

终于找到问题1+2的答案了

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

Kinit代码对于了解内部私有 Kerberos 代码的内部工作原理并对其进行自定义非常有用。例如,KDCOptions您可以手动配置一个实例,以便请求可更新的票证等等。我们来研究一下吧!;-)

请考虑:

  • 不保证内部代码接口在未来的JDK版本中保持不变
  • 不保证不同JDK厂商内部代码接口相同。

我可以确认我的代码在 OpenJDK 和 Oracle JDK 上都能正常工作。

大局观

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

下一步

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

谢谢

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

问候