Java GSS-API服务票证未使用Java保存在凭据高速缓存中

我使用GSS-API创建了2个演示Kerberos客户端。 一个在Python3中,第二个在Java中。 两个客户端似乎大致相同,并且两者都“起作用”,因为我获得了我的Java GSS-API服务主体接受的服务票证。

但是在测试时我注意到Python客户端将服务票据保存在kerberos凭证缓存中,而Java客户端似乎没有保存票证。

我使用“klist”来查看凭证缓存的内容。

我的客户使用FreeIPA作为Kerberos环境在Lubuntu 17.04虚拟机上运行。 我正在使用OpenJDK 8 u131。

问题1: Java GSS-API是否不保存凭证缓存的服务票证? 或者我可以更改我的代码吗?

问题2:服务票据是否未保存到缓存这一事实是否有任何缺点?

我的假设是缓存的服务票据减少了与KDC的交互,但评论了如何使用Windows Java客户端保存Kerberos服务票证? 建议事实并非如此,但是这个Microsoft技术说明“每次想要访问这个特定服务器时,客户端都不需要返回KDC”。

问题3:来自python客户端的缓存服务票证在几分钟后消失 – 在到期日之前很久。 是什么导致他们消失?

Python代码

#!/usr/bin/python3.5 import gssapi from io import BytesIO server_name = 'HTTP/app-srv.acme.com@ACME.COM' service_name = gssapi.Name(server_name) client_ctx = gssapi.SecurityContext(name=service_name, usage='initiate') initial_client_token = client_ctx.step() 

Java代码

 System.setProperty("java.security.krb5.conf","/etc/krb5.conf"); System.setProperty("javax.security.auth.useSubjectCredsOnly","false"); GSSManager manager = GSSManager.getInstance(); GSSName clientName; GSSContext context = null; //try catch removed for brevity GSSName serverName = manager.createName("HTTP/app-srv.acme.com@ACME.COM", null); Oid krb5Oid = new Oid("1.2.840.113554.1.2.2"); //use default credentials context = manager.createContext(serverName, krb5Oid, null, GSSContext.DEFAULT_LIFETIME); context.requestMutualAuth(false); context.requestConf(false); context.requestInteg(true); byte[] token = new byte[0]; token = context.initSecContext(token, 0, token.length); 

编辑:

虽然最初的问题集中在使用Java GSS-API构建Java Kerberos客户端,但GSS不是必须的。 我对其他适用于Java的Kerberos方法持开放态度。 现在我正在试验Apache Kerby curb-client。

到目前为止,Java GSS-API似乎有两个问题:

1)它使用凭证缓存来获取TGT(Ok),但不使用缓存服务票证(Not Ok)。

2)它无法访问KEYRING类型的凭证缓存。 (通过行为确认,调试Java运行时安全类以及该代码中的注释。对于Lubuntu / FreeIPA组合,我使用KEYRING是开箱即用的默认设置。这不适用于Windows,并且可能不适用于其他Linux Kerberos组合。

编辑2:

我应该问的问题是:

由于Java GSS未使用凭证缓存,如何阻止我的KDC因重复的SGT请求而受到重创。

我将原始答案留在底部,因为如果主要关注原始问题。

经过另一轮深度调试和测试后,我找到了一个可接受的根问题解决方案。

在我的原始解决方案中使用Java GSS API与JAAS相比,而不是没有JAAS的“纯粹”GSS会产生很大的不同!

是的,可能在凭证缓存中的现有服务票证(SGT)没有被加载,也没有任何新获得的SGT写回缓存,但是KDC不会经常被敲打(真正的问题)。

纯GSS和带有JAAS的GSS都使用客户主体。 主题具有内存中的privateCredentials集,用于存储TGT和SGT。

关键的区别是:

  • “纯GSS”:主题+ privateCredentials是在GSSContext中创建的,只有GSSContext存在时才会存在。

  • GSS与JAAS:主题由JAAS在GSSContext之外创建,因此可以在应用程序的生命周期中存在,在应用程序的生命周期中涵盖许多GSSContexts。

建立的第一个GSSContext将查询主题的privateCredentials以查找SGT,而不是查找一个,然后从KDC请求SGT。

SGT被添加到主题的privateCredentials中,并且由于主题的持续时间比GSSContext长,因此在创建后续GSSContexts时,SGT可以使用SGT。 这些将在主题的privateCredentials中找到SGT,并且不需要为新SGT命中KDC。

因此,根据我特定的Java胖客户端看到,打开一次并可能运行数小时,一切都很好。 创建的第一个GSSContext将命中一个SGT的KDC,然后将由所有后续创建的GSSContexts使用,直到客户端关闭。 没有使用凭证缓存,但这并没有伤害。

鉴于生命周期短得多的客户端,重新打开很多次,也许并行,然后使用/不使用凭证缓存可能是一个更严重的问题。

 private void initJAASandGSS() { LoginContext loginContext = null; TextCallbackHandler cbHandler = new TextCallbackHandler(); try { loginContext = new LoginContext("wSOXClientGSSJAASLogin", cbHandler); loginContext.login(); mySubject = loginContext.getSubject(); } catch (LoginException e) { // TODO Auto-generated catch block e.printStackTrace(); } gssManager = GSSManager.getInstance(); try { //TODO: LAMB: This name should be got from config / built from config / serviceIdentifier serverName = gssManager.createName("HTTP/app-srv.acme.com@ACME.COM", null); Oid krb5Oid = new Oid("1.2.840.113554.1.2.2"); } catch (GSSException e) { // TODO Auto-generated catch block e.printStackTrace(); } } private String getGSSwJAASServiceToken() { byte[] token = null; String encodedToken = null; token = Subject.doAs(mySubject, new PrivilegedAction(){ public byte[] run(){ try{ System.setProperty("javax.security.auth.useSubjectCredsOnly","true"); GSSContext context = gssManager.createContext(serverName, krb5Oid, null, GSSContext.DEFAULT_LIFETIME); context.requestMutualAuth(false); context.requestConf(false); context.requestInteg(true); byte[] ret = new byte[0]; ret = context.initSecContext(ret, 0, ret.length); context.dispose(); return ret; } catch(Exception e){ Log.log(Log.ERROR, e); throw new otms.util.OTMSRuntimeException("Start Client (Kerberos) failed, cause: " + e.getMessage()); } } }); encodedToken = Base64.getEncoder().encodeToString(token); return encodedToken; } 

结束编辑2:原始答案如下:

问题1: Java GSS-API是否不保存凭证缓存的服务票证? 或者我可以更改我的代码吗?

编辑:根本原因分析。

经过几个小时调试sun.security。*类之后,我现在明白了GSS和Java安全代码正在做什么/不做什么 – 至少在Java 8 u 131中。

在此示例中,我们有一个Java GSS可以访问的凭证缓存,其中包含有效的票证授予票证(TGT)和有效的服务票证(SGT)。

1)创建客户端主体Subject时,将从缓存(Credentials.acquireTGTFromCache())加载TGT,并将其存储在Subject的privateCredentials集中。 – >(好的)

仅加载TGT,未加载SGT并将其保存到Subject privateCredentials。 – >(不行)

2)稍后,在GSSContext.initSecContext()过程的深处,安全代码实际上尝试从Subject的privateCredentials中检索服务票证。 相关代码是Krb5Context.initSecContext()/ KrbUtils.getTicket()/ SubjectComber.find()/ findAux()。 但是,由于SGT从未在步骤1)中加载,因此将无法找到SGT! 因此,从KDC请求新的SGT并使用。

对每个服务请求重复此操作。

只是为了好玩,并且严格地作为概念validation黑客,我在登录和initSecContext()之间添加了几行代码来解析凭证缓存,提取凭据,转换为Krb凭证,并将它们添加到主体的私人凭证。

这在步骤2)中完成,找到并使用现有的SGT。 没有从KDC请求新的SGT。

我不会发布这个hack的代码,因为它调用了我们不应该调用的sun内部类,我不希望激励其他人这样做。 我也打算用这个黑客作为解决方案。

– >根本原因问题不是服务票据没有保存到缓存; 反而

a)SGT未从凭证缓存加载到客户端主体的主体

b)没有公共API或配置设置。

无论是否使用JAAS,这都会影响GSS-API。

那么这会让我离开?

i)使用JASAS的Java GSS-API / GSS-API“按原样”,每个SGT请求命中KDC – >不好。

ii)正如Samson在下面的评论中所建议的,仅使用Java GSS-API进行应用程序的初始登录,然后对于所有进一步的调用,使用替代安全机制进行后续调用(一种自建的kerberos-light)使用令牌或cookies。

iii)考虑GSS-API的替代方案,例如Apache Kerby curb-client。 这超出了这个答案的范围,可能会certificate是从众所周知的煎锅跳到火上。

我已经向Oracle提交了Javafunction请求,建议应该从缓存中检索SGT并将其存储在Subject凭证中(就像TGT的情况一样)。

http://bugs.java.com/bugdatabase/view_bug.do?bug_id=JDK-8180144

问题2:服务票据是否未保存到缓存这一事实是否有任何缺点?

使用服务票证的凭证缓存可减少客户端与KDC之间的交互。 这样做的必然结果是,在没有缓存服务票据的情况下,每个请求都需要与KDC进行交互,这可能导致KDC受到重创。