我们的presto开启Kerberos认证后,通过jdbc访问的时候提示:"Unable to obtain password from user".对于这种错误提示,一般就是principal和keytab文件配置的有问题,但是因为我们对presto认证模块做了修改,业务部门坚持他们没有配错,所以我的亲自动手找出证据了,首先看堆栈信息:
Caused by: javax.security.auth.login.LoginException: Unable to obtain password from user
at com.sun.security.auth.module.Krb5LoginModule.promptForPass(Krb5LoginModule.java:903)
at com.sun.security.auth.module.Krb5LoginModule.attemptAuthentication(Krb5LoginModule.java:766)
at com.sun.security.auth.module.Krb5LoginModule.login(Krb5LoginModule.java:617)
看到这个堆栈我就放心一大半了,报错的是底层Krb5LoginModule,跟我们修改的模块没有关系,我们从login方法开始debug,上代码:
public boolean login() throws LoginException {
...
try {
//堆栈显示的是这里
attemptAuthentication(false);
succeeded = true;
cleanState();
return true;
} catch (LoginException e) {
...
}
}
接着看看attemptAuthentication方法:
private void attemptAuthentication(boolean getPasswdFromSharedState)
throws LoginException {
...
if (ktab == null) {
promptForPass(getPasswdFromSharedState);
builder = new KrbAsReqBuilder(principal, password);
if (isInitiator) {
// XXX Even if isInitiator=false, it might be
// better to do an AS-REQ so that keys can be
// updated with PA info
cred = builder.action().getCreds();
}
if (storeKey) {
encKeys = builder.getKeys(isInitiator);
// When encKeys is empty, the login actually fails.
// For compatibility, exception is thrown in commit().
}
} else {
builder = new KrbAsReqBuilder(principal, ktab);
if (isInitiator) {
cred = builder.action().getCreds();
}
}
builder.destroy();
...
}
明显可以看到ktab为null的时候才会调用promptForPass(getPasswdFromSharedState),ktab为什么是null,程序中配置了keytab文件呀,往上看看:
if (useKeyTab) {
if (!unboundServer) {
KerberosPrincipal kp =
new KerberosPrincipal(principal.getName());
ktab = (keyTabName == null)
? KeyTab.getInstance(kp)
: KeyTab.getInstance(kp, new File(keyTabName));
} else {
ktab = (keyTabName == null)
? KeyTab.getUnboundInstance()
: KeyTab.getUnboundInstance(new File(keyTabName));
}
if (isInitiator) {
if (Krb5Util.keysFromJavaxKeyTab(ktab, principal).length
== 0) {
ktab = null;
if (debug) {
System.out.println
("Key for the principal " +
principal +
" not available in " +
((keyTabName == null) ?
"default key tab" : keyTabName));
}
}
}
}
上面if-else中对ktab的处理没有问题,只有一种可能Krb5Util.keysFromJavaxKeyTab(ktab, principal).length== 0,我们继续看看为啥length会等于0。
看下keysFromJavaxKeyTab方法:
public static EncryptionKey[] keysFromJavaxKeyTab(
KeyTab ktab, PrincipalName cname) {
return snapshotFromJavaxKeyTab(ktab).readServiceKeys(cname);
}
继续:
public static sun.security.krb5.internal.ktab.KeyTab
snapshotFromJavaxKeyTab(KeyTab ktab) {
return KerberosSecrets.getJavaxSecurityAuthKerberosAccess()
.keyTabTakeSnapshot(ktab);
}
继续:
public sun.security.krb5.internal.ktab.KeyTab keyTabTakeSnapshot(
KeyTab ktab) {
return ktab.takeSnapshot();
}
继续:
sun.security.krb5.internal.ktab.KeyTab takeSnapshot() {
try {
return sun.security.krb5.internal.ktab.KeyTab.getInstance(file);
} catch (AccessControlException ace) {
if (file != null) {
// It's OK to show the name if caller specified it
throw ace;
} else {
AccessControlException ace2 = new AccessControlException(
"Access to default keytab denied (modified exception)");
ace2.setStackTrace(ace.getStackTrace());
throw ace2;
}
}
}
debug显示这里的file是程序中配置的keytab文件路径,继续:
public static KeyTab getInstance(File file) {
if (file == null) {
return getInstance();
} else {
return getInstance0(file.getPath());
}
}
继续:
private synchronized static KeyTab getInstance0(String s) {
long lm = new File(s).lastModified();
KeyTab old = map.get(s);
if (old != null && old.isValid() && old.lastModified == lm) {
return old;
}
KeyTab ktab = new KeyTab(s);
if (ktab.isValid()) { // A valid new keytab
map.put(s, ktab);
return ktab;
} else if (old != null) { // An existing old one
return old;
} else {
return ktab; // first read is invalid
}
}
可以看到最终是在getInstance0方法中构造的keytab对象,让我们看看是怎么构造的:
private KeyTab(String filename) {
tabName = filename;
try {
lastModified = new File(tabName).lastModified();
try (KeyTabInputStream kis =
new KeyTabInputStream(new FileInputStream(filename))) {
load(kis);
}
} catch (FileNotFoundException e) {
entries.clear();
isMissing = true;
} catch (Exception ioe) {
entries.clear();
isValid = false;
}
}
构造一个inputStream,然后加载文件内容,到这里就很明显了,debug显示文件filename不存在,一般情况就是应用程序中写的相对路径不对导致的。