关于kerberos使用keytab安全认证连接hive票据过期的问题及解决方法。

关于kerberos使用keytab安全认证连接hive票据过期的问题及解决方法

  • 问题描述
  • 解决方法

问题描述

本人在使用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认证!");
}

下面是本人新建的学习交流群,欢迎大家进群,请不要发送与行业不相关的信息,尊重彼此的时间精力!
关于kerberos使用keytab安全认证连接hive票据过期的问题及解决方法。_第1张图片

你可能感兴趣的:(技术笔记)