Java GSS-API服务票证未使用Java保存在凭据高速缓存中 [英] Java GSS-API Service Ticket not saved in Credentials Cache using Java

查看:152
本文介绍了Java GSS-API服务票证未使用Java保存在凭据高速缓存中的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

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



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



我使用 klist查看凭据缓存的内容。



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



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



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



我的假设是缓存的服务票据减少了与KDC的交互,但对如何使用Windows Java客户端保存Kerberos服务票证?表明情况并非如此,但是 this Microsoft技术说明说每次想要访问此特定服务器时,客户端都不需要返回KDC。



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



Python代码

 #!/ usr / bin / python3.5 

从io import导入gssapi
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;

//为简洁尝试删除
GSSName serverName =
manager.createName(HTTP/app-srv.acme.com@ACME.COM,null);

Oid krb5Oid = new Oid(1.2.840.113554.1.2.2);

//使用默认凭证
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:



我应该问的问题是:


如何阻止我的KDC因重复的SGT而被敲定请求,因为Java GSS没有使用凭证缓存。


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



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



在我的原始解决方案中使用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将查询主题的对于SGT的privateCredentials,找不到,
然后从KDC请求SGT。



SGT被添加到主题的privateCredentials中,并且作为主题生活当GSSContexts创建时,它比SGSContext更长,
可用,SGT也是如此。这些将在主题的privateCredentials中找到SGT,并且不需要在新的SGT上点击KDC。



因此,根据我的特定Java Fat Client,打开一次,可能会运行几个小时,一切都很好。
创建的第一个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自动生成的catch块
e.printStackTrace();
}

gssManager = GSSManager.getInstance();

try {
// TODO:LAMB:这个名字应该从config / serviceIdentifier获得config / build
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自动生成的catch块
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();

返回ret;

} catch(例外e){
Log.log(Log.ERROR,e);
抛出新的otms.util.OTMSRuntimeException(开始客户端(Kerberos)失败,原因:+ e.getMessage());
}
}
});

encodedToken = Base64.getEncoder()。encodeToString(token);
返回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并使用。



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



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



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



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



- >根本原因问题不是服务票据不是 SAVED 到缓存;而是



a)SGT从凭证缓存到客户委托人主体的 LOADED





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



无论有没有JAAS,这都会影响GSS-API。



那么这会让我离开?



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



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

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



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



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



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



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


I have created 2 demo Kerberos Clients using the GSS-API. One in Python3, the second in Java. Both clients seem to be broadly equivalent, and both "work" in that I get a service ticket that is accepted by my Java GSS-API Service Principal.

However on testing I noticed that the Python client saves the service ticket in the kerberos credentials cache, whereas the Java client does not seem to save the ticket.

I use "klist" to view the contents of the credential cache.

My clients are running on a Lubuntu 17.04 Virtual Machine, using FreeIPA as the Kerberos environment. I am using OpenJDK 8 u131.

Question 1: Does the Java GSS-API not save service tickets to the credentials cache? Or can I change my code so it does so?

Question 2: Is there any downside to the fact that the service ticket is not saved to the cache?

My assumption is that cached service tickets reduce interaction with the KDC, but comments on How to save Kerberos Service Ticket using a Windows Java client? suggest that is not the case, but this Microsoft technote says "The client does not need to go back to the KDC each time it wants access to this particular server".

Question 3: The cached service tickets from the python client vanish after some minutes - long before the expiry date. What causes them to vanish?

Python code

#!/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 Code

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);

Edit:

While the original question focusses on the use of the Java GSS-API to build a Java Kerberos Client, GSS is not a must. I am open to other Kerberos approaches that work on Java. Right now I am experimenting with Apache Kerby kerb-client.

So far Java GSS-API seems to have 2 problems:

1) It uses the credentials cache to get the TGT (Ok), but not to cache service-tickets (Not Ok).

2) It cannot access credential caches of type KEYRING. (Confirmed by behaviour, debugging the Java runtime security classes, and by comments in that code. For the Lubuntu / FreeIPA combination I am using KEYRING was the out-of-the-box default. This won't apply to Windows, and may not apply to other Linux Kerberos combinations.

解决方案

Edit 2:

The question I should have asked is:

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

I leave my original answer in place at the bottom, because if largely focusses on the original question.

After another round of deep debugging and testing, I have found an acceptable solution to the root problem.

Using Java GSS API with JAAS, as opposed to "pure" GSS without JAAS in my original solution makes a big difference!

Yes, existing Service Tickets (SGTs) that may be in the credentials cache are not being loaded, nor are any newly acquired SGTs written back to the cache, however the KDC is not be constantly hammered (the real problem).

Both pure GSS, and GSS with JAAS use a client principal subject. The subject has an in-memory privateCredentials set, which is used to store TGTs and SGTs.

The key difference is:

  • "pure GSS": the subject + privateCredentials is created within the GSSContext, and lives only as long as the GSSContext lives.

  • GSS with JAAS: the subject is created by JAAS, outside the GSSContext, and thus can live for the life of the application, spanning many GSSContexts during the life of the application.

The first GSSContext established will query the subject's privateCredentials for a SGT, not find one, then request a SGT from the KDC.

The SGT is added to the subject's privateCredentials, and as the subject lives longer than the GSSContext, it is available, as is the SGT, when following GSSContexts are created. These will find the SGT in the subject's privateCredentials, and do not need to hit the KDC for a new SGT.

So seen in the light of my particular Java Fat Client, opened once and likely to run for hours, everything is ok. The first GSSContext created will hit the KDC for a SGT which will then be used by all following GSSContexts created until the client is closed. The credentials cache is not being used, but that does not hurt.

In the light of a much shorter lived client, reopened many many times, and perhaps in parallel, then use / non-use of the credentials cache might be a more serious issue.

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;
}

End Edit 2: Original answer below:

Question 1: Does the Java GSS-API not save service tickets to the credentials cache? Or can I change my code so it does so?

Edit: Root Cause Analysis.

After many hours debugging the sun.security.* classes, I now understand what GSS and Java Security code is doing / not doing - at least in Java 8 u 131.

In this example we have a credential cache, of a type Java GSS can access, containing a valid Ticket Granting Ticket (TGT) and a valid Service Ticket (SGT).

1) When the client principal Subject is created, the TGT is loaded from the cache (Credentials.acquireTGTFromCache()), and stored in the privateCredentials set of the Subject. --> (OK)

Only the TGT is loaded, SGTs are NOT loaded and saved to the Subject privateCredentials. -->(NOT OK)

2) Later, deep in the GSSContext.initSecContext() process, the security code actually tries to retrieve a Service Ticket from the privateCredentials of the Subject. The relevant code is Krb5Context.initSecContext() / KrbUtils.getTicket() / SubjectComber.find()/findAux(). However as SGTs were never loaded in step 1) an SGT will not be found! Therefore a new SGT is requested from the KDC and used.

This is repeated for each Service request.

Just for fun, and strictly as a proof-of-concept hack, I added a few lines of code between the login, and the initSecContext() to parse the credentials cache, extract the credentials, convert to Krb Credentials, and add them to the Subject’s private credentials.

This done, in step 2) the existing SGT is found and used. No new SGT is requested from the KDC.

I will not post the code for this hack as it calls sun internal classes that we should not be calling, and I don’t wish to inspire anybody else to do so. Nor do I intend to use this hack as a solution.

—> The root cause problem is not that the service ticket are not SAVED to the cache; but rather

a) that SGTs are not LOADED from the credential cache to the Subject of the client principal

and

b) that there is no public API or configuration settings to do so.

This affects GSS-API both with and without JAAS.

So where does this leave me?

i) Use Java GSS-API / GSS-API with JAAS "as is", with each SGT Request hitting the KDC —> Not good.

ii) As suggested by Samson in the comments below, use Java GSS-API only for initial login of the application, then for all further calls use an alternative security mechanism for subsequent calls (a kind of self-built kerberos-light) using tokens or cookies.

iii) Consider alternatives to GSS-API such as Apache Kerby kerb-client. This has implications outside the scope of this answer, and may well prove to be jumping from the proverbial frying pan to the fire.

I have submitted a Java Feature Request to Oracle, suggesting that SGTs should be retrieved from the cache and stored in the Subject credentials (as already the case for TGTs).

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

Question 2: Is there any downside to the fact that the service ticket is not saved to the cache?

Using the credentials cache for Service Tickets reduces interaction between the client and the KDC. The corollary to this is that where service tickets are not cached, each request will require interaction with the KDC, which could lead to the KDC being hammered.

这篇关于Java GSS-API服务票证未使用Java保存在凭据高速缓存中的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆