本人在使用HiveStreaming的过程中,使用kerberos keytab进行安全验证,程序会保持长期连接。(hive jdbc 连接也要同样的问题)
登录主要代码:
LoginContext lc = new LoginContext(clientName, new TextCallbackHandler());
lc.login();
UserGroupInformation.loginUserFromSubject(lc.getSubject());
问题:
根据我们的kerberos设置,我们的票据Kerberos tgt 生命周期为24小时,到了24小时后,程序会报错连接失败。
报错信息:
Caused by: org.ietf.jgss.GSSException: No valid credentials provided (Mechanism level: Failed to find any Kerberos tgt)
at sun.security.jgss.krb5.Krb5InitCredential.getInstance(Krb5InitCredential.java:147)
at sun.security.jgss.krb5.Krb5MechFactory.getCredentialElement(Krb5MechFactory.java:122)
at sun.security.jgss.krb5.Krb5MechFactory.getMechanismContext(Krb5MechFactory.java:187)
at sun.security.jgss.GSSManagerImpl.getMechanismContext(GSSManagerImpl.java:224)
at sun.security.jgss.GSSContextImpl.initSecContext(GSSContextImpl.java:212)
at sun.security.jgss.GSSContextImpl.initSecContext(GSSContextImpl.java:179)
at com.sun.security.sasl.gsskerb.GssKrb5Client.evaluateChallenge(GssKrb5Client.java:192)
... 53 common frames omitted
无票据可用。
跟踪HiveStreaming连接的源码关键调用过程
//代码1 自己写的代码 创建连接
connection = HiveStreamingConnection.newBuilder()
.withDatabase(database)
.withTable(tableName)
.withAgentInfo(tableName + "_hiveStreaming-agent")
.withRecordWriter(writer)
.withHiveConf(hiveConf)
.connect();
//代码2 源码 创建hive客户端
this.msClient = getMetaStoreClient(this.conf, this.metastoreUri, this.secureMode, "streaming-connection");
//代码3 源码 创建hive客户端的动态代理对象
/**
* This constructor is meant for Hive internal use only.
* Please use getProxy(HiveConf conf, HiveMetaHookLoader hookLoader) for external purpose.
*/
public static IMetaStoreClient getProxy(Configuration hiveConf, Class<?>[] constructorArgTypes,
Object[] constructorArgs, ConcurrentHashMap<String, Long> metaCallTimeMap,
String mscClassName) throws MetaException {
@SuppressWarnings("unchecked")
Class<? extends IMetaStoreClient> baseClass =
JavaUtils.getClass(mscClassName, IMetaStoreClient.class);
RetryingMetaStoreClient handler =
new RetryingMetaStoreClient(hiveConf, constructorArgTypes, constructorArgs,
metaCallTimeMap, baseClass);
return (IMetaStoreClient) Proxy.newProxyInstance(
RetryingMetaStoreClient.class.getClassLoader(), baseClass.getInterfaces(), handler);
}
//代码4 源码 动态代理的具体工作
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
Object ret;
int retriesMade = 0;
TException caughtException;
boolean allowReconnect = ! method.isAnnotationPresent(NoReconnect.class);
boolean allowRetry = true;
Annotation[] directives = method.getDeclaredAnnotations();
if(directives != null) {
for(Annotation a : directives) {
if(a instanceof RetrySemantics.CannotRetry) {
allowRetry = false;
}
}
}
while (true) {
try {
reloginExpiringKeytabUser();
if (allowReconnect) {
if (retriesMade > 0 || hasConnectionLifeTimeReached(method)) {
if (this.ugi != null) {
// Perform reconnect with the proper user context
try {
LOG.info("RetryingMetaStoreClient trying reconnect as " + this.ugi);
this.ugi.doAs(
new PrivilegedExceptionAction<Object> () {
@Override
public Object run() throws MetaException {
base.reconnect();
return null;
}
});
} catch (UndeclaredThrowableException e) {
Throwable te = e.getCause();
if (te instanceof PrivilegedActionException) {
throw te.getCause();
} else {
throw te;
}
}
lastConnectionTime = System.currentTimeMillis();
} else {
LOG.warn("RetryingMetaStoreClient unable to reconnect. No UGI information.");
throw new MetaException("UGI information unavailable. Will not attempt a reconnect.");
}
}
}
if (metaCallTimeMap == null) {
ret = method.invoke(base, args);
} else {
// need to capture the timing
long startTime = System.currentTimeMillis();
ret = method.invoke(base, args);
long timeTaken = System.currentTimeMillis() - startTime;
addMethodTime(method, timeTaken);
}
break;
} catch (UndeclaredThrowableException e) {
throw e.getCause();
} catch (InvocationTargetException e) {
Throwable t = e.getCause();
if (t instanceof TApplicationException) {
TApplicationException tae = (TApplicationException)t;
switch (tae.getType()) {
case TApplicationException.UNSUPPORTED_CLIENT_TYPE:
case TApplicationException.UNKNOWN_METHOD:
case TApplicationException.WRONG_METHOD_NAME:
case TApplicationException.INVALID_PROTOCOL:
throw t;
default:
// TODO: most other options are probably unrecoverable... throw?
caughtException = tae;
}
} else if ((t instanceof TProtocolException) || (t instanceof TTransportException)) {
// TODO: most protocol exceptions are probably unrecoverable... throw?
caughtException = (TException)t;
} else if ((t instanceof MetaException) && t.getMessage().matches(
"(?s).*(JDO[a-zA-Z]*|TProtocol|TTransport)Exception.*") &&
!t.getMessage().contains("java.sql.SQLIntegrityConstraintViolationException")) {
caughtException = (MetaException)t;
} else {
throw t;
}
} catch (MetaException e) {
if (e.getMessage().matches("(?s).*(IO|TTransport)Exception.*") &&
!e.getMessage().contains("java.sql.SQLIntegrityConstraintViolationException")) {
caughtException = e;
} else {
throw e;
}
}
if (retriesMade >= retryLimit || base.isLocalMetaStore() || !allowRetry) {
throw caughtException;
}
retriesMade++;
LOG.warn("MetaStoreClient lost connection. Attempting to reconnect (" + retriesMade + " of " +
retryLimit + ") after " + retryDelaySeconds + "s. " + method.getName(), caughtException);
Thread.sleep(retryDelaySeconds * 1000);
}
return ret;
}
代码4 中动态代理的功能是,在hiveClient每次执行操作( ret = method.invoke(base, args))之前先执行
reloginExpiringKeytabUser();该方法会判断是否需要登录,如果需要会调用UserGroupInformation 类的登录方法重新获取票据,
hiveClient重新登录( base.reconnect();)。
但是如果是以这种方式登录的话
LoginContext lc = new LoginContext(clientName, new TextCallbackHandler());
lc.login();
UserGroupInformation.loginUserFromSubject(lc.getSubject());
new UserGroupInformation(lc.getSubject()) ;
reloginExpiringKeytabUser();不满足重新登录的条件,不会执行重新登录。
把登录方式更改如下代码就可以了。
AppConfigurationEntry[] appConfigurationEntry= javax.security.auth.login.Configuration.getConfiguration().getAppConfigurationEntry(clientName);
if(appConfigurationEntry!=null&&appConfigurationEntry.length>=1){
Map<String,?> options=appConfigurationEntry[0].getOptions();
String user=options.get("principal").toString();
String keyTabPath=options.get("keyTab").toString();
UserGroupInformation.loginUserFromKeytab(user,keyTabPath);
}else{
logger.error("找不到keytab,无法通过kerberos认证!");
}