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 服务票证?表明情况并非如此,但这个Microsoft技术说明说"客户端不需要每次想要访问此特定服务器时都返回 KDC"。

问题 3:来自 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()

爪哇代码

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 kerb-client。

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

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

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

编辑 2:

我应该问的问题是:

如何阻止我的 KDC 因重复的 SGT 请求而被锤击,因为 Java GSS 未使用凭据缓存。

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

经过又一轮深入的调试和测试,我找到了根本问题的可接受的解决方案。

在我的原始解决方案中使用带有JAAS的Java GSS API,而不是没有JAAS的"纯"GSS,这有很大的不同!

是的,凭据缓存中可能存在的现有服务票证 (SGT) 未加载, 也不会将任何新获取的 SGT 写回缓存,但是 KDC 不会经常受到锤击(真正的问题)。

纯 GSS 和带有 JAAS 的 GSS 都使用客户端主体主体。使用者设置了内存中的私有凭据, 用于存储 TGT 和 SGT。

主要区别在于:

  • "纯 GSS":主题 + privateCredentials 在 GSSContext 中创建,并且仅在 GSSContext 存在期间存在。

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

建立的第一个 GSSContext 将查询主题的私有凭据以获取 SGT,而不是找到一个, 然后向 KDC 请求 SGT。

SGT 被添加到主体的私有凭据中,并且由于主体的寿命比 GSSContext 更长, 它和 SGT 一样,在创建以下 GSSContext 时可用。这些将在主题的私有凭据中找到 SGT,并且不需要点击 KDC 以获取新的 SGT。

因此,从我特定的Java Fat Client的角度来看,打开一次并可能运行数小时,一切都很好。 创建的第一个 GSSContext 将命中 KDC 以获取 SGT,然后由所有后续创建的 GSSContext 使用,直到客户端关闭。 凭据缓存未被使用,但这不会造成伤害。

鉴于一个寿命短得多的客户,多次重新开放,也许是并行的, 那么使用/不使用凭据缓存可能是一个更严重的问题。

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) 创建客户端主体主体时,TGT 将从缓存 (Credentials.acquireTGTFromCache()) 加载,并存储在使用者的私有凭证集中。--> (确定)

仅加载 TGT,不会加载 SGT 并将其保存到使用者私有凭据中。-->(不行)

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

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

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

在步骤 2) 中完成此操作,找到并使用现有的 SGT。没有要求KDC提供新的SGT。

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

—> 根本原因问题不是服务票证没有保存到缓存中,而是

a) SGT 未从凭据缓存加载到客户端主体的使用者

b) 没有公共 API 或配置设置来执行此操作。

这会影响有和没有JAAS的GSS-API。

那么这让我何去何从?

i) 将 Java GSS-API/GSS-API 与 JAAS "按原样"使用,每个 SGT 请求都会命中 KDC —>不好。

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

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

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

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

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

对服务票证使用凭据缓存可减少客户端与 KDC 之间的交互。这样做的推论是,在没有缓存服务票证的情况下,每个请求都需要与 KDC 交互,这可能导致 KDC 被锤击。

最新更新