如何在Java中使用GSS API加载缓存的Kerberos TGS票据?
问题:Java GSS API无法读取缓存中的Kerberos TGS票据,报错"Service ticket not found in the subject"
已通过以下步骤生成并确认缓存中有有效票据:
- 使用
kinit生成TGT票据 - 使用
kvno cifs/DC1.proxyrealm.test生成CIFS服务的TGS票据 - 执行
klist确认缓存中存在以下有效票据:
Ticket cache: FILE:/tmp/krb5cc_0 Default principal: Administrator@PROXYREALM.TEST Valid starting Expires Service principal 22/06/25 12:48:10 22/06/25 22:48:10 krbtgt/PROXYREALM.TEST@PROXYREALM.TEST renew until 29/06/25 12:48:06 22/06/25 12:49:02 22/06/25 22:48:10 cifs/DC1.proxyrealm.test@PROXYREALM.TEST renew until 29/06/25 12:48:06
但使用Java GSS API加载缓存票据时,仅能识别TGT,报错Service ticket not found in the subject。Java调试日志显示已读取到CIFS服务票据,但无法匹配使用。
调试日志片段
Java config name: null Native config name: /etc/krb5.conf Loading config file from /etc/krb5.conf # 省略配置加载内容 >>>DEBUG <CCacheInputStream> client principal is Administrator@PROXYREALM.TEST >>>DEBUG <CCacheInputStream> server principal is cifs/DC1.proxyrealm.test@PROXYREALM.TEST >>>DEBUG <CCacheInputStream> key type: 18 >>>DEBUG <CCacheInputStream> auth time: Sun Jun 22 12:48:10 IDT 2025 >>>DEBUG <CCacheInputStream> start time: Sun Jun 22 12:49:02 IDT 2025 >>>DEBUG <CCacheInputStream> end time: Sun Jun 22 22:48:10 IDT 2025 >>>DEBUG <CCacheInputStream> renew_till time: Sun Jun 29 12:48:06 IDT 2025 >>> CCacheInputStream: readFlags() FORWARDABLE; RENEWABLE; PRE_AUTH; get normal credential Found ticket for Administrator@PROXYREALM.TEST to go to krbtgt/PROXYREALM.TEST@PROXYREALM.TEST expiring on Sun Jun 22 22:48:10 IDT 2025 Entered Krb5Context.initSecContext with state=STATE_NEW Service ticket not found in the subject
测试代码
public class GSSKdcProxyClient { public static final Oid KRB5_MECH_OID; static { try { KRB5_MECH_OID = new Oid("1.2.840.113554.1.2.2"); // Kerberos V5 OID } catch (Exception e) { throw new RuntimeException("Failed to initialize Kerberos OID", e); } } public static void main(String[] args) throws GSSException { System.setProperty("javax.security.auth.useSubjectCredsOnly","false"); System.setProperty("sun.security.krb5.debug", "true"); try { GSSManager manager = GSSManager.getInstance(); GSSName serverName = manager.createName("cifs/DC1.proxyrealm.test@PROXYREALM.TEST",GSSName.NT_HOSTBASED_SERVICE); // Acquire credentials and create context GSSContext context = manager.createContext(serverName,KRB5_MECH_OID, null, GSSContext.DEFAULT_LIFETIME); context.requestMutualAuth(false); context.initSecContext(new byte[0], 0, 0); if (context.isEstablished()) { System.out.println("GSS context established with: " + context.getSrcName()); } context.dispose(); } catch (Exception e) { e.printStackTrace(); } }
也曾尝试使用不同的GSSName构造方式:
GSSName serverName = manager.createName("DC1.proxyrealm.test",KRB5_PRINCIPAL_NT);
解决方案
1. 手动加载Kerberos缓存到Subject中
默认情况下,GSSManager可能无法正确从系统缓存中加载所有票据,需要通过LoginContext手动加载缓存凭证并在Subject的上下文中执行GSS操作:
import javax.security.auth.Subject; import javax.security.auth.login.LoginContext; import javax.security.auth.login.LoginException; import java.security.PrivilegedAction; // 在main方法中替换原有逻辑 try { // 配置LoginContext使用Kerberos缓存 System.setProperty("java.security.auth.login.config", "/path/to/krb5_login.conf"); LoginContext lc = new LoginContext("KerberosCache"); lc.login(); Subject subject = lc.getSubject(); // 在Subject上下文中执行GSS操作 Subject.doAs(subject, (PrivilegedAction<Void>) () -> { try { GSSManager manager = GSSManager.getInstance(); // 使用不带REALM的服务主体格式,让GSS自动匹配 GSSName serverName = manager.createName("cifs/DC1.proxyrealm.test", GSSName.NT_HOSTBASED_SERVICE); GSSContext context = manager.createContext(serverName, KRB5_MECH_OID, null, GSSContext.DEFAULT_LIFETIME); context.requestMutualAuth(false); context.initSecContext(new byte[0], 0, 0); if (context.isEstablished()) { System.out.println("GSS context established with: " + context.getSrcName()); } context.dispose(); } catch (GSSException e) { e.printStackTrace(); } return null; }); lc.logout(); } catch (LoginException e) { e.printStackTrace(); }
对应的krb5_login.conf配置内容:
KerberosCache { com.sun.security.auth.module.Krb5LoginModule required useTicketCache=true ticketCache="/tmp/krb5cc_0" doNotPrompt=true; };
2. 正确构造GSS服务主体名
- 使用
GSSName.NT_HOSTBASED_SERVICE类型时,格式应为service/hostname,不需要附加REALM(GSS会根据krb5.conf自动匹配) - 确保主体名与缓存中的服务票据完全一致(注意大小写,Kerberos主体名通常区分大小写)
3. 检查krb5.conf配置
确保domain_realm映射正确,让Java能够将主机名正确关联到对应的Kerberos域:
domain_realm = { .proxyrealm.test = PROXYREALM.TEST proxyrealm.test = PROXYREALM.TEST }
4. 确保缓存权限正确
运行Java程序的用户需要对/tmp/krb5cc_0文件有读取权限,可执行以下命令调整:
chmod 600 /tmp/krb5cc_0 chown <java_user>:<java_group> /tmp/krb5cc_0
内容的提问来源于stack exchange,提问作者Tal Dulberg
相关产品推荐
相关产品推荐

