Kerberos 和 LDAP:为什么 Java 6 在使用 Kerberos 时截断 LDAP 主机名?
Kerberos and LDAP: Why does Java 6 truncate LDAP host names when using Kerberos?
我的任务是通过 Java GSS API 连接到基于 Kerberos 的 LDAP 服务器。我有一个小型示例应用程序,用于测试我的连接、我的 Kerberos 配置、连接参数等,以确保我可以连接到 LDAP 服务器并检索数据。代码如下:
import java.io.FileInputStream;
import java.io.IOException;
import java.security.PrivilegedActionException;
import java.security.PrivilegedExceptionAction;
import java.util.HashMap;
import java.util.Hashtable;
import java.util.Map;
import java.util.Properties;
import javax.naming.Context;
import javax.naming.NamingEnumeration;
import javax.naming.NamingException;
import javax.naming.directory.Attributes;
import javax.naming.directory.DirContext;
import javax.naming.directory.SearchControls;
import javax.naming.directory.SearchResult;
import javax.naming.ldap.Control;
import javax.naming.ldap.InitialLdapContext;
import javax.security.auth.Subject;
import javax.security.auth.login.LoginContext;
import javax.security.auth.login.LoginException;
import javax.security.auth.spi.LoginModule;
public class TestKerberizedLdap implements Runnable {
public static void main(String[] args) {
Runnable rn = null;
if (args.length == 1) {
rn = new TestKerberizedLdap(args[0]);
}
else if (args.length == 0) {
rn = new TestKerberizedLdap("C:/dev/projects/TestKerberizedLdap/src/testkerberizedldap.properties");
}
else {
printUsage();
return;
}
rn.run();
}
private static final String loginModuleClassName = "com.sun.security.auth.module.Krb5LoginModule";
private static final String koid = "1.2.840.113554.1.2.2";
private static final String soid = "1.3.6.1.5.5.2";
private Subject subject;
private DirContext ldapContext;
private String username;
private String password;
private String ldapHost;
private TestKerberizedLdap(String propFileName) {
Properties prop = new Properties();
try {
FileInputStream iStream = new FileInputStream(propFileName);
prop.load(iStream);
iStream.close();
}
catch (IOException e) {
e.printStackTrace();
}
System.out.println("Properties found:");
for (String propName : prop.stringPropertyNames()) {
System.out.println(propName + " = " + prop.getProperty(propName));
}
System.out.println();
System.setProperty("java.security.krb5.conf", prop.getProperty("krbConf"));
System.setProperty("java.security.krb5.debug", "true");
System.setProperty("sun.security.krb5.debug", "true");
ldapHost = prop.getProperty("ldapHost");
username = prop.getProperty("user");
password = prop.getProperty("password");
subject = null;
ldapContext = null;
}
public void run() {
try {
initSubject();
if (subject != null) {
initContextKerberized();
}
}
catch (Exception ex) {
ex.printStackTrace();
}
}
private void initContextKerberized() throws Exception {
Subject.doAs(subject, new PrivilegedExceptionAction<Object>() {
public Object run() throws Exception {
Hashtable<String, String> env = new Hashtable<String, String>();
env.put(Context.INITIAL_CONTEXT_FACTORY, "com.sun.jndi.ldap.LdapCtxFactory");
env.put(Context.PROVIDER_URL, ldapHost);
env.put(Context.SECURITY_AUTHENTICATION, "GSSAPI");
ldapContext = new InitialLdapContext(env, new Control[0]);
// Do stuff with ldapContext here...
searchLdapDirectory();
return null;
}
});
}
private void searchLdapDirectory() throws NamingException {
String base = "CN=Users";
String filter = "(objectclass=user)";
SearchControls sc = new SearchControls();
sc.setSearchScope(SearchControls.SUBTREE_SCOPE);
NamingEnumeration<SearchResult> ne = ldapContext.search(base, filter, sc);
int numElements = 0;
System.out.println();
while (ne.hasMoreElements()) {
SearchResult sr = ne.nextElement();
Attributes attr = sr.getAttributes();
System.out.println(attr.get("name").get());
numElements++;
}
System.out.println("The number of elements returned was " + numElements);
}
private void initSubject() throws InstantiationException, ClassNotFoundException, IllegalAccessException {
LoginModule module = null;
try {
module = (LoginModule) Class.forName(loginModuleClassName).newInstance();
subject = new Subject();
Map<String, String> options = new HashMap<String, String>();
Map<String, Object> sharedState = new HashMap<String, Object>();
if ((username != null) && (password != null)) {
sharedState.put("javax.security.auth.login.password", password.toCharArray());
sharedState.put("javax.security.auth.login.name", username);
options.put("principal", username);
options.put("storeKey", "true");
options.put("useFirstPass", "true");
}
else {
options.put("principal", username);
options.put("useTicketCache", "true");
options.put("doNotPrompt", "true");
options.put("renewTGT", "true");
}
options.put("debug", "true");
options.put("refreshKrb5Config", "true");
module.initialize(subject, null, sharedState, options);
module.login();
module.commit();
}
catch (LoginException ex) {
ex.printStackTrace();
subject = null;
if (module != null) {
try {
module.abort();
}
catch (LoginException ex2) {
ex2.printStackTrace();
}
}
}
}
private static void printUsage() {
System.out.println();
System.out.println("Usage: TestKerberizedLdap <property file name>");
System.out.println();
}
}
在 testkerberized.properties 文件中,"ldapHost" 属性 被定义为 "ldap://dv2k8ad.2k8.hlp.net:389/dc=2k8,dc=hlp,dc=net"。
现在,当我 运行 这个应用程序与 Java 7 时,它就像一个魅力。 Kerberos 身份验证成功,并且从 LDAP 服务器成功检索数据。但是,我无法使用 Java 将其设置为 运行 6。它一直给我以下异常:
Found ticket for user1@2K8.HLP.NET to go to krbtgt/2K8.HLP.NET@2K8.HLP.NET expiring on Thu Jun 04 00:47:07 PDT 2015
Entered Krb5Context.initSecContext with state=STATE_NEW
Found ticket for user1@2K8.HLP.NET to go to krbtgt/2K8.HLP.NET@2K8.HLP.NET expiring on Thu Jun 04 00:47:07 PDT 2015
Service ticket not found in the subject
>>> Credentials acquireServiceCreds: same realm
default etypes for default_tgs_enctypes: 23 16 3 1.
>>> CksumType: sun.security.krb5.internal.crypto.RsaMd5CksumType
>>> EType: sun.security.krb5.internal.crypto.ArcFourHmacEType
>>> KrbKdcReq send: kdc=172.23.5.151 UDP:88, timeout=30000, number of retries =3, #bytes=1289
>>> KDCCommunication: kdc=172.23.5.151 UDP:88, timeout=30000,Attempt =1, #bytes=1289
>>> KrbKdcReq send: #bytes read=92
>>> KrbKdcReq send: #bytes read=92
>>> KdcAccessibility: remove 172.23.5.151:88
>>> KDCRep: init() encoding tag is 126 req type is 13
>>>KRBError:
sTime is Wed Jun 03 14:47:07 PDT 2015 1433368027000
suSec is 109093
error code is 7
error Message is Server not found in Kerberos database
realm is 2K8.HLP.NET
sname is ldap/2k8.hlp.net
msgType is 30
java.security.PrivilegedActionException: javax.naming.AuthenticationException: GSSAPI [Root exception is javax.security.sasl.SaslException: GSS initiate failed [Caused by GSSException: No valid credentials provided (Mechanism level: Server not found in Kerberos database (7))]]
at java.security.AccessController.doPrivileged(Native Method)
at javax.security.auth.Subject.doAs(Subject.java:396)
at TestKerberizedLdap.initContextKerberized(TestKerberizedLdap.java:95)
at TestKerberizedLdap.run(TestKerberizedLdap.java:85)
at TestKerberizedLdap.main(TestKerberizedLdap.java:39)
Caused by: javax.naming.AuthenticationException: GSSAPI [Root exception is javax.security.sasl.SaslException: GSS initiate failed [Caused by GSSException: No valid credentials provided (Mechanism level: Server not found in Kerberos database (7))]]
at com.sun.jndi.ldap.sasl.LdapSasl.saslBind(LdapSasl.java:150)
at com.sun.jndi.ldap.LdapClient.authenticate(LdapClient.java:214)
at com.sun.jndi.ldap.LdapCtx.connect(LdapCtx.java:2694)
at com.sun.jndi.ldap.LdapCtx.<init>(LdapCtx.java:293)
at com.sun.jndi.ldap.LdapCtxFactory.getUsingURL(LdapCtxFactory.java:175)
at com.sun.jndi.ldap.LdapCtxFactory.getUsingURLs(LdapCtxFactory.java:193)
at com.sun.jndi.ldap.LdapCtxFactory.getLdapCtxInstance(LdapCtxFactory.java:136)
at com.sun.jndi.ldap.LdapCtxFactory.getInitialContext(LdapCtxFactory.java:66)
at javax.naming.spi.NamingManager.getInitialContext(NamingManager.java:667)
at javax.naming.InitialContext.getDefaultInitCtx(InitialContext.java:288)
at javax.naming.InitialContext.init(InitialContext.java:223)
at javax.naming.ldap.InitialLdapContext.<init>(InitialLdapContext.java:134)
at TestKerberizedLdap.run(TestKerberizedLdap.java:101)
... 5 more
Caused by: javax.security.sasl.SaslException: GSS initiate failed [Caused by GSSException: No valid credentials provided (Mechanism level: Server not found in Kerberos database (7))]
at com.sun.security.sasl.gsskerb.GssKrb5Client.evaluateChallenge(GssKrb5Client.java:194)
at com.sun.jndi.ldap.sasl.LdapSasl.saslBind(LdapSasl.java:105)
... 17 more
Caused by: GSSException: No valid credentials provided (Mechanism level: Server not found in Kerberos database (7))
at sun.security.jgss.krb5.Krb5Context.initSecContext(Krb5Context.java:663)
at sun.security.jgss.GSSContextImpl.initSecContext(GSSContextImpl.java:230)
at sun.security.jgss.GSSContextImpl.initSecContext(GSSContextImpl.java:162)
at com.sun.security.sasl.gsskerb.GssKrb5Client.evaluateChallenge(GssKrb5Client.java:175)
... 18 more
Caused by: KrbException: Server not found in Kerberos database (7)
at sun.security.krb5.KrbTgsRep.<init>(KrbTgsRep.java:61)
at sun.security.krb5.KrbTgsReq.getReply(KrbTgsReq.java:185)
at sun.security.krb5.internal.CredentialsUtil.serviceCreds(CredentialsUtil.java:294)
at sun.security.krb5.internal.CredentialsUtil.acquireServiceCreds(CredentialsUtil.java:106)
at sun.security.krb5.Credentials.acquireServiceCreds(Credentials.java:557)
at sun.security.jgss.krb5.Krb5Context.initSecContext(Krb5Context.java:594)
... 21 more
Caused by: KrbException: Identifier doesn't match expected value (906)
at sun.security.krb5.internal.KDCRep.init(KDCRep.java:133)
at sun.security.krb5.internal.TGSRep.init(TGSRep.java:58)
at sun.security.krb5.internal.TGSRep.<init>(TGSRep.java:53)
at sun.security.krb5.KrbTgsRep.<init>(KrbTgsRep.java:46)
... 26 more
值得注意的是,module.commit()成功了,主题也成功获取了,所以没有问题。但我注意到 KrbError 消息中的 sname 字段已关闭。它说 ldap/2k8.hlp.net 而它应该是 ldap/dv2k8ad.2k8.hlp.net.
在此之后,我 运行 一个 Wireshark 会话来捕获所有正在发送的 Kerberos 数据包,并且我在 运行 在 Java 6 和 [=37] 上使用 TestKerberizedLdap 时捕获了数据包=] 7. TGT 获得成功,因为程序在这两种情况下都获得了成功的 AS-REP。但是,发送到 KDC 的 TGS-REQ 具有不同的值。 Java 6 发送的服务器名称是 ldap/2k8.hlp.net 而 Java 7 发送的服务器名称是 ldap/dv2k8ad.2k8.hlp.net
我不知道为什么 Java 6 选择 trim LDAP 主机名。如果有人知道解决方法或修复方法(除了更新 Java),我将不胜感激。
耶,解决了!
这是 Java 1.6.0_22 中的错误。我改用 Java 1.6.0_45,一切都很顺利:)
我的任务是通过 Java GSS API 连接到基于 Kerberos 的 LDAP 服务器。我有一个小型示例应用程序,用于测试我的连接、我的 Kerberos 配置、连接参数等,以确保我可以连接到 LDAP 服务器并检索数据。代码如下:
import java.io.FileInputStream;
import java.io.IOException;
import java.security.PrivilegedActionException;
import java.security.PrivilegedExceptionAction;
import java.util.HashMap;
import java.util.Hashtable;
import java.util.Map;
import java.util.Properties;
import javax.naming.Context;
import javax.naming.NamingEnumeration;
import javax.naming.NamingException;
import javax.naming.directory.Attributes;
import javax.naming.directory.DirContext;
import javax.naming.directory.SearchControls;
import javax.naming.directory.SearchResult;
import javax.naming.ldap.Control;
import javax.naming.ldap.InitialLdapContext;
import javax.security.auth.Subject;
import javax.security.auth.login.LoginContext;
import javax.security.auth.login.LoginException;
import javax.security.auth.spi.LoginModule;
public class TestKerberizedLdap implements Runnable {
public static void main(String[] args) {
Runnable rn = null;
if (args.length == 1) {
rn = new TestKerberizedLdap(args[0]);
}
else if (args.length == 0) {
rn = new TestKerberizedLdap("C:/dev/projects/TestKerberizedLdap/src/testkerberizedldap.properties");
}
else {
printUsage();
return;
}
rn.run();
}
private static final String loginModuleClassName = "com.sun.security.auth.module.Krb5LoginModule";
private static final String koid = "1.2.840.113554.1.2.2";
private static final String soid = "1.3.6.1.5.5.2";
private Subject subject;
private DirContext ldapContext;
private String username;
private String password;
private String ldapHost;
private TestKerberizedLdap(String propFileName) {
Properties prop = new Properties();
try {
FileInputStream iStream = new FileInputStream(propFileName);
prop.load(iStream);
iStream.close();
}
catch (IOException e) {
e.printStackTrace();
}
System.out.println("Properties found:");
for (String propName : prop.stringPropertyNames()) {
System.out.println(propName + " = " + prop.getProperty(propName));
}
System.out.println();
System.setProperty("java.security.krb5.conf", prop.getProperty("krbConf"));
System.setProperty("java.security.krb5.debug", "true");
System.setProperty("sun.security.krb5.debug", "true");
ldapHost = prop.getProperty("ldapHost");
username = prop.getProperty("user");
password = prop.getProperty("password");
subject = null;
ldapContext = null;
}
public void run() {
try {
initSubject();
if (subject != null) {
initContextKerberized();
}
}
catch (Exception ex) {
ex.printStackTrace();
}
}
private void initContextKerberized() throws Exception {
Subject.doAs(subject, new PrivilegedExceptionAction<Object>() {
public Object run() throws Exception {
Hashtable<String, String> env = new Hashtable<String, String>();
env.put(Context.INITIAL_CONTEXT_FACTORY, "com.sun.jndi.ldap.LdapCtxFactory");
env.put(Context.PROVIDER_URL, ldapHost);
env.put(Context.SECURITY_AUTHENTICATION, "GSSAPI");
ldapContext = new InitialLdapContext(env, new Control[0]);
// Do stuff with ldapContext here...
searchLdapDirectory();
return null;
}
});
}
private void searchLdapDirectory() throws NamingException {
String base = "CN=Users";
String filter = "(objectclass=user)";
SearchControls sc = new SearchControls();
sc.setSearchScope(SearchControls.SUBTREE_SCOPE);
NamingEnumeration<SearchResult> ne = ldapContext.search(base, filter, sc);
int numElements = 0;
System.out.println();
while (ne.hasMoreElements()) {
SearchResult sr = ne.nextElement();
Attributes attr = sr.getAttributes();
System.out.println(attr.get("name").get());
numElements++;
}
System.out.println("The number of elements returned was " + numElements);
}
private void initSubject() throws InstantiationException, ClassNotFoundException, IllegalAccessException {
LoginModule module = null;
try {
module = (LoginModule) Class.forName(loginModuleClassName).newInstance();
subject = new Subject();
Map<String, String> options = new HashMap<String, String>();
Map<String, Object> sharedState = new HashMap<String, Object>();
if ((username != null) && (password != null)) {
sharedState.put("javax.security.auth.login.password", password.toCharArray());
sharedState.put("javax.security.auth.login.name", username);
options.put("principal", username);
options.put("storeKey", "true");
options.put("useFirstPass", "true");
}
else {
options.put("principal", username);
options.put("useTicketCache", "true");
options.put("doNotPrompt", "true");
options.put("renewTGT", "true");
}
options.put("debug", "true");
options.put("refreshKrb5Config", "true");
module.initialize(subject, null, sharedState, options);
module.login();
module.commit();
}
catch (LoginException ex) {
ex.printStackTrace();
subject = null;
if (module != null) {
try {
module.abort();
}
catch (LoginException ex2) {
ex2.printStackTrace();
}
}
}
}
private static void printUsage() {
System.out.println();
System.out.println("Usage: TestKerberizedLdap <property file name>");
System.out.println();
}
}
在 testkerberized.properties 文件中,"ldapHost" 属性 被定义为 "ldap://dv2k8ad.2k8.hlp.net:389/dc=2k8,dc=hlp,dc=net"。
现在,当我 运行 这个应用程序与 Java 7 时,它就像一个魅力。 Kerberos 身份验证成功,并且从 LDAP 服务器成功检索数据。但是,我无法使用 Java 将其设置为 运行 6。它一直给我以下异常:
Found ticket for user1@2K8.HLP.NET to go to krbtgt/2K8.HLP.NET@2K8.HLP.NET expiring on Thu Jun 04 00:47:07 PDT 2015
Entered Krb5Context.initSecContext with state=STATE_NEW
Found ticket for user1@2K8.HLP.NET to go to krbtgt/2K8.HLP.NET@2K8.HLP.NET expiring on Thu Jun 04 00:47:07 PDT 2015
Service ticket not found in the subject
>>> Credentials acquireServiceCreds: same realm
default etypes for default_tgs_enctypes: 23 16 3 1.
>>> CksumType: sun.security.krb5.internal.crypto.RsaMd5CksumType
>>> EType: sun.security.krb5.internal.crypto.ArcFourHmacEType
>>> KrbKdcReq send: kdc=172.23.5.151 UDP:88, timeout=30000, number of retries =3, #bytes=1289
>>> KDCCommunication: kdc=172.23.5.151 UDP:88, timeout=30000,Attempt =1, #bytes=1289
>>> KrbKdcReq send: #bytes read=92
>>> KrbKdcReq send: #bytes read=92
>>> KdcAccessibility: remove 172.23.5.151:88
>>> KDCRep: init() encoding tag is 126 req type is 13
>>>KRBError:
sTime is Wed Jun 03 14:47:07 PDT 2015 1433368027000
suSec is 109093
error code is 7
error Message is Server not found in Kerberos database
realm is 2K8.HLP.NET
sname is ldap/2k8.hlp.net
msgType is 30
java.security.PrivilegedActionException: javax.naming.AuthenticationException: GSSAPI [Root exception is javax.security.sasl.SaslException: GSS initiate failed [Caused by GSSException: No valid credentials provided (Mechanism level: Server not found in Kerberos database (7))]]
at java.security.AccessController.doPrivileged(Native Method)
at javax.security.auth.Subject.doAs(Subject.java:396)
at TestKerberizedLdap.initContextKerberized(TestKerberizedLdap.java:95)
at TestKerberizedLdap.run(TestKerberizedLdap.java:85)
at TestKerberizedLdap.main(TestKerberizedLdap.java:39)
Caused by: javax.naming.AuthenticationException: GSSAPI [Root exception is javax.security.sasl.SaslException: GSS initiate failed [Caused by GSSException: No valid credentials provided (Mechanism level: Server not found in Kerberos database (7))]]
at com.sun.jndi.ldap.sasl.LdapSasl.saslBind(LdapSasl.java:150)
at com.sun.jndi.ldap.LdapClient.authenticate(LdapClient.java:214)
at com.sun.jndi.ldap.LdapCtx.connect(LdapCtx.java:2694)
at com.sun.jndi.ldap.LdapCtx.<init>(LdapCtx.java:293)
at com.sun.jndi.ldap.LdapCtxFactory.getUsingURL(LdapCtxFactory.java:175)
at com.sun.jndi.ldap.LdapCtxFactory.getUsingURLs(LdapCtxFactory.java:193)
at com.sun.jndi.ldap.LdapCtxFactory.getLdapCtxInstance(LdapCtxFactory.java:136)
at com.sun.jndi.ldap.LdapCtxFactory.getInitialContext(LdapCtxFactory.java:66)
at javax.naming.spi.NamingManager.getInitialContext(NamingManager.java:667)
at javax.naming.InitialContext.getDefaultInitCtx(InitialContext.java:288)
at javax.naming.InitialContext.init(InitialContext.java:223)
at javax.naming.ldap.InitialLdapContext.<init>(InitialLdapContext.java:134)
at TestKerberizedLdap.run(TestKerberizedLdap.java:101)
... 5 more
Caused by: javax.security.sasl.SaslException: GSS initiate failed [Caused by GSSException: No valid credentials provided (Mechanism level: Server not found in Kerberos database (7))]
at com.sun.security.sasl.gsskerb.GssKrb5Client.evaluateChallenge(GssKrb5Client.java:194)
at com.sun.jndi.ldap.sasl.LdapSasl.saslBind(LdapSasl.java:105)
... 17 more
Caused by: GSSException: No valid credentials provided (Mechanism level: Server not found in Kerberos database (7))
at sun.security.jgss.krb5.Krb5Context.initSecContext(Krb5Context.java:663)
at sun.security.jgss.GSSContextImpl.initSecContext(GSSContextImpl.java:230)
at sun.security.jgss.GSSContextImpl.initSecContext(GSSContextImpl.java:162)
at com.sun.security.sasl.gsskerb.GssKrb5Client.evaluateChallenge(GssKrb5Client.java:175)
... 18 more
Caused by: KrbException: Server not found in Kerberos database (7)
at sun.security.krb5.KrbTgsRep.<init>(KrbTgsRep.java:61)
at sun.security.krb5.KrbTgsReq.getReply(KrbTgsReq.java:185)
at sun.security.krb5.internal.CredentialsUtil.serviceCreds(CredentialsUtil.java:294)
at sun.security.krb5.internal.CredentialsUtil.acquireServiceCreds(CredentialsUtil.java:106)
at sun.security.krb5.Credentials.acquireServiceCreds(Credentials.java:557)
at sun.security.jgss.krb5.Krb5Context.initSecContext(Krb5Context.java:594)
... 21 more
Caused by: KrbException: Identifier doesn't match expected value (906)
at sun.security.krb5.internal.KDCRep.init(KDCRep.java:133)
at sun.security.krb5.internal.TGSRep.init(TGSRep.java:58)
at sun.security.krb5.internal.TGSRep.<init>(TGSRep.java:53)
at sun.security.krb5.KrbTgsRep.<init>(KrbTgsRep.java:46)
... 26 more
值得注意的是,module.commit()成功了,主题也成功获取了,所以没有问题。但我注意到 KrbError 消息中的 sname 字段已关闭。它说 ldap/2k8.hlp.net 而它应该是 ldap/dv2k8ad.2k8.hlp.net.
在此之后,我 运行 一个 Wireshark 会话来捕获所有正在发送的 Kerberos 数据包,并且我在 运行 在 Java 6 和 [=37] 上使用 TestKerberizedLdap 时捕获了数据包=] 7. TGT 获得成功,因为程序在这两种情况下都获得了成功的 AS-REP。但是,发送到 KDC 的 TGS-REQ 具有不同的值。 Java 6 发送的服务器名称是 ldap/2k8.hlp.net 而 Java 7 发送的服务器名称是 ldap/dv2k8ad.2k8.hlp.net
我不知道为什么 Java 6 选择 trim LDAP 主机名。如果有人知道解决方法或修复方法(除了更新 Java),我将不胜感激。
耶,解决了!
这是 Java 1.6.0_22 中的错误。我改用 Java 1.6.0_45,一切都很顺利:)