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的交互,但How to save Kerberos Service Ticket using a Windows Java client?的评论表明情况并非如此,但this Microsoft technote说“客户端每次想要访问这个特定服务器时都不需要返回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:

我应该问的问题是:

How do I stop my KDC from being hammered for repeated SGT requests because Java GSS is not using the credentials cache.

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

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

在我的原始解决方案中使用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一样可用.这些将在主题的privateCredentials中找到SGT,并且不需要为新SGT命中KDC.

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

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

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<byte[]>(){
        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集中. – &GT (好)

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

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

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

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

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

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

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

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

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

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

那么这会让我离开?

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

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

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

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

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

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

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

上一篇:Spring MVC 常用注解


下一篇:Linux的网络信息