Java GSS-API 服务票据未使用 Java 保存在凭据缓存中
Java GSS-API Service Ticket not saved in Credentials Cache using Java
我已经使用 GSS-API 创建了 2 个演示 Kerberos 客户端。
Python3 中的一个,Java 中的第二个。
两个客户端似乎大致相同,而且都 "work" 因为我得到了我的 Java GSS-API 服务委托人接受的服务票证。
但是在测试中我注意到 Python 客户端将服务票证保存在 kerberos 凭据缓存中,而 Java 客户端似乎没有保存票证。
我用"klist"查看凭证缓存的内容。
我的客户 运行 在 Lubuntu 17.04 虚拟机上,使用 FreeIPA 作为 Kerberos 环境。我正在使用 OpenJDK 8 u131。
问题 1: Java GSS-API 是否不将服务票证保存到凭据缓存中?或者我可以更改我的代码以便它这样做吗?
问题2:服务票据不保存到缓存有什么坏处吗?
我的假设是缓存的服务票证减少了与 KDC 的交互,但 How to save Kerberos Service Ticket using a Windows Java client? suggest that is not the case, but this Microsoft technote 上的评论说 "The client does not need to go back to the KDC each time it wants access to this particular server"。
问题 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 kerb-client。
到目前为止 Java GSS-API 似乎有 2 个问题:
1) 它使用凭据缓存来获取 TGT(正常),但不缓存服务票证(不正常)。
2) 它无法访问 KEYRING 类型的凭据缓存。 (通过行为、调试 Java 运行时安全性 类 以及该代码中的注释确认。对于 Lubuntu / FreeIPA 组合,我使用的是开箱即用的默认设置。这赢了' 适用于 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.
我将原来的答案留在底部,因为 if 主要集中在原来的问题上。
经过又一轮的深入调试和测试,我找到了一个可以接受的根本问题解决方案。
在我的原始解决方案中使用 Java GSS API 和 JAAS,与不使用 JAAS 的 "pure" GSS 有很大的不同!
是的,可能在凭据缓存中的现有服务票证 (SGT) 未被加载,
也没有任何新获取的 SGT 写回缓存,但是 KDC 并没有经常受到攻击(真正的问题)。
纯 GSS 和带有 JAAS 的 GSS 都使用客户端主体主题。主题有一个内存中的 privateCredentials 集,
用于存储TGT和SGT。
主要区别在于:
"pure GSS": subject + privateCredentials 是在 GSSContext 中创建的,并且只在 GSSContext 存在时存在。
GSS with JAAS: 主题由 JAAS 在 GSSContext 之外创建,因此可以在应用程序的生命周期内存在,
在应用程序的生命周期中跨越许多 GSSContext。
建立的第一个 GSSContext 将查询主题的 privateCredentials 以获得 SGT,找不到,
然后从 KDC 请求 SGT。
SGT 被添加到主题的 privateCredentials 中,并且由于主题的寿命比 GSSContext 长,
当创建以下 GSSContext 时,它与 SGT 一样可用。这些将在主题的 privateCredentials 中找到 SGT,并且不需要点击 KDC 以获得新的 SGT。
所以根据我的特定 Java 胖客户端来看,打开一次并可能 运行 几个小时,一切正常。
创建的第一个 GSSContext 将命中 SGT 的 KDC,然后将由所有后续创建的 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 你 131.
在此示例中,我们有一个凭证缓存,其类型为 Java GSS 可以访问,其中包含有效的票证授予票证 (TGT) 和有效的服务票证 (SGT)。
1) 创建客户端主体 Subject 时,TGT 从缓存中加载 (Credentials.acquireTGTFromCache()),并存储在 Subject 的 privateCredentials 集中。 -->(确定)
仅加载 TGT,未加载 SGT 并将其保存到主题 privateCredentials。 -->(不对)
2) 后来,在 GSSContext.initSecContext() 过程的深处,安全代码实际上尝试从 Subject 的 privateCredentials 中检索服务票证。相关代码为Krb5Context.initSecContext() / KrbUtils.getTicket() / SubjectComber.find()/findAux()。但是,由于在步骤 1) 中从未加载 SGT,因此将找不到 SGT!因此,从 KDC 请求并使用新的 SGT。
对每个服务请求重复此操作。
只是为了好玩,严格来说作为概念验证 hack,我在登录和 initSecContext() 之间添加了几行代码来解析凭据缓存、提取凭据、转换为 Krb 凭据,并将它们添加到主题的私人凭据中。
已完成,在步骤 2) 中找到并使用了现有的 SGT。没有从 KDC 请求新的 SGT。
我不会 post 这个 hack 的代码,因为它调用了我们不应该调用的 sun 内部 类,我也不希望激励其他人这样做。我也不打算将此 hack 用作解决方案。
—>根本原因不是服务票据没有SAVED到缓存;而是
a) SGT 未从凭证缓存加载 到客户端主体的主题
和
b) 没有 public API 或配置设置。
无论有没有 JAAS,这都会影响 GSS-API。
那么这会把我留在哪里?
i) 将 Java GSS-API / GSS-API 与 JAAS “按原样”使用,每个 SGT 请求都会命中 KDC —> 不好。
ii) 正如 Samson 在下面的评论中所建议的那样,仅将 Java GSS-API 用于应用程序的初始登录,然后对于所有进一步的调用,对后续调用使用替代安全机制(一种自建的 kerberos-light)使用令牌或 cookie。
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 受到攻击。
我已经使用 GSS-API 创建了 2 个演示 Kerberos 客户端。 Python3 中的一个,Java 中的第二个。 两个客户端似乎大致相同,而且都 "work" 因为我得到了我的 Java GSS-API 服务委托人接受的服务票证。
但是在测试中我注意到 Python 客户端将服务票证保存在 kerberos 凭据缓存中,而 Java 客户端似乎没有保存票证。
我用"klist"查看凭证缓存的内容。
我的客户 运行 在 Lubuntu 17.04 虚拟机上,使用 FreeIPA 作为 Kerberos 环境。我正在使用 OpenJDK 8 u131。
问题 1: Java GSS-API 是否不将服务票证保存到凭据缓存中?或者我可以更改我的代码以便它这样做吗?
问题2:服务票据不保存到缓存有什么坏处吗?
我的假设是缓存的服务票证减少了与 KDC 的交互,但 How to save Kerberos Service Ticket using a Windows Java client? suggest that is not the case, but this Microsoft technote 上的评论说 "The client does not need to go back to the KDC each time it wants access to this particular server"。
问题 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 kerb-client。
到目前为止 Java GSS-API 似乎有 2 个问题:
1) 它使用凭据缓存来获取 TGT(正常),但不缓存服务票证(不正常)。
2) 它无法访问 KEYRING 类型的凭据缓存。 (通过行为、调试 Java 运行时安全性 类 以及该代码中的注释确认。对于 Lubuntu / FreeIPA 组合,我使用的是开箱即用的默认设置。这赢了' 适用于 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.
我将原来的答案留在底部,因为 if 主要集中在原来的问题上。
经过又一轮的深入调试和测试,我找到了一个可以接受的根本问题解决方案。
在我的原始解决方案中使用 Java GSS API 和 JAAS,与不使用 JAAS 的 "pure" GSS 有很大的不同!
是的,可能在凭据缓存中的现有服务票证 (SGT) 未被加载, 也没有任何新获取的 SGT 写回缓存,但是 KDC 并没有经常受到攻击(真正的问题)。
纯 GSS 和带有 JAAS 的 GSS 都使用客户端主体主题。主题有一个内存中的 privateCredentials 集, 用于存储TGT和SGT。
主要区别在于:
"pure GSS": subject + privateCredentials 是在 GSSContext 中创建的,并且只在 GSSContext 存在时存在。
GSS with JAAS: 主题由 JAAS 在 GSSContext 之外创建,因此可以在应用程序的生命周期内存在, 在应用程序的生命周期中跨越许多 GSSContext。
建立的第一个 GSSContext 将查询主题的 privateCredentials 以获得 SGT,找不到, 然后从 KDC 请求 SGT。
SGT 被添加到主题的 privateCredentials 中,并且由于主题的寿命比 GSSContext 长, 当创建以下 GSSContext 时,它与 SGT 一样可用。这些将在主题的 privateCredentials 中找到 SGT,并且不需要点击 KDC 以获得新的 SGT。
所以根据我的特定 Java 胖客户端来看,打开一次并可能 运行 几个小时,一切正常。 创建的第一个 GSSContext 将命中 SGT 的 KDC,然后将由所有后续创建的 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 你 131.
在此示例中,我们有一个凭证缓存,其类型为 Java GSS 可以访问,其中包含有效的票证授予票证 (TGT) 和有效的服务票证 (SGT)。
1) 创建客户端主体 Subject 时,TGT 从缓存中加载 (Credentials.acquireTGTFromCache()),并存储在 Subject 的 privateCredentials 集中。 -->(确定)
仅加载 TGT,未加载 SGT 并将其保存到主题 privateCredentials。 -->(不对)
2) 后来,在 GSSContext.initSecContext() 过程的深处,安全代码实际上尝试从 Subject 的 privateCredentials 中检索服务票证。相关代码为Krb5Context.initSecContext() / KrbUtils.getTicket() / SubjectComber.find()/findAux()。但是,由于在步骤 1) 中从未加载 SGT,因此将找不到 SGT!因此,从 KDC 请求并使用新的 SGT。
对每个服务请求重复此操作。
只是为了好玩,严格来说作为概念验证 hack,我在登录和 initSecContext() 之间添加了几行代码来解析凭据缓存、提取凭据、转换为 Krb 凭据,并将它们添加到主题的私人凭据中。
已完成,在步骤 2) 中找到并使用了现有的 SGT。没有从 KDC 请求新的 SGT。
我不会 post 这个 hack 的代码,因为它调用了我们不应该调用的 sun 内部 类,我也不希望激励其他人这样做。我也不打算将此 hack 用作解决方案。
—>根本原因不是服务票据没有SAVED到缓存;而是
a) SGT 未从凭证缓存加载 到客户端主体的主题
和
b) 没有 public API 或配置设置。
无论有没有 JAAS,这都会影响 GSS-API。
那么这会把我留在哪里?
i) 将 Java GSS-API / GSS-API 与 JAAS “按原样”使用,每个 SGT 请求都会命中 KDC —> 不好。
ii) 正如 Samson 在下面的评论中所建议的那样,仅将 Java GSS-API 用于应用程序的初始登录,然后对于所有进一步的调用,对后续调用使用替代安全机制(一种自建的 kerberos-light)使用令牌或 cookie。
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 受到攻击。