Java连接HBase的方法,包含Kerberos认证。
完整代码示例:

package com.example.hbase;

import org.apache.hadoop.conf.Configuration;
import org.apache.hadoop.fs.Path;
import org.apache.hadoop.hbase.HBaseConfiguration;
import org.apache.hadoop.hbase.HColumnDescriptor;
import org.apache.hadoop.hbase.HTableDescriptor;
import org.apache.hadoop.hbase.TableName;
import org.apache.hadoop.hbase.client.*;
import org.apache.hadoop.hbase.io.compress.Compression.Algorithm;
import org.apache.hadoop.hbase.util.Bytes;
import org.apache.hadoop.security.UserGroupInformation;

import java.io.IOException;
import java.util.ArrayList;
import java.util.List;

public class HBaseClientUtil {

    private static final String TABLE_NAME = "test:test";
    private static final String CF_DEFAULT = "cf";

    /**
     * HBase 通用客户端Kerberos认证
     * @param resources 配置文件资源
     * @param krb5Conf krb5.conf文件路径
     * @param principal Kerberos用户主体,eg:xingweidong@BIGDATA.ZXXK.COM
     * @param keytabFile keytab文件路径
     * @return
     * @throws IOException
     */
    public static Connection getHBaseConn(List<String> resources, String krb5Conf, String principal, String keytabFile) throws IOException {
        Configuration config = HBaseConfiguration.create();

        // 添加必要的配置文件 (hbase-site.xml, core-site.xml)
        for (int i = 0; i < resources.size(); i++) {
            config.addResource(new Path(resources.get(i)));
        }

        // Kerberos认证
        // 设置java安全krb5.conf
        System.setProperty("java.security.krb5.conf", krb5Conf);
        // 设置用户主体(Principal)
        config.set("kerberos.principal" , principal);
        // 使用用户keytab文件认证
        config.set("keytab.file" , keytabFile);

        UserGroupInformation.setConfiguration(config);
        try {
            // 登录
            UserGroupInformation.loginUserFromKeytab(principal, keytabFile);
        } catch (IOException e) {
            e.printStackTrace();
        }

        // 创建连接
        return ConnectionFactory.createConnection(config);
    }

    /**
     * HBase2.2.0+ 客户端Kerberos认证 (未验证)
     * @param resources 配置文件资源
     * @param krb5Conf krb5.conf文件路径
     * @param principal Kerberos用户主体,eg:xingweidong@BIGDATA.ZXXK.COM
     * @param keytabFile keytab文件路径
     * @return
     * @throws IOException
     */
    public static Connection getHBaseConn220(List<String> resources, String krb5Conf, String principal, String keytabFile) throws IOException {
        Configuration config = HBaseConfiguration.create();

        // 添加必要的配置文件 (hbase-site.xml, core-site.xml)
        for (int i = 0; i < resources.size(); i++) {
            config.addResource(new Path(resources.get(i)));
        }

        // 设置java安全krb5.conf (未验证是否需要此配置)
        System.setProperty("java.security.krb5.conf", krb5Conf);

        // Kerberos认证
        config.set("hbase.client.kerberos.principal", principal);
        config.set("hbase.client.keytab.file", keytabFile);

        // 创建连接
        return ConnectionFactory.createConnection(config);
    }

    public static void createOrOverwrite(Admin admin, HTableDescriptor table) throws IOException {
        if (admin.tableExists(table.getTableName())) {
            admin.disableTable(table.getTableName());
            admin.deleteTable(table.getTableName());
        }
        admin.createTable(table);
    }

    public static void createSchemaTables(Connection connection) throws IOException {
        try (Admin admin = connection.getAdmin()) {

            HTableDescriptor table = new HTableDescriptor(TableName.valueOf(TABLE_NAME));
            table.addFamily(new HColumnDescriptor(CF_DEFAULT).setCompressionType(Algorithm.NONE));

            System.out.print("Creating table. ");
            createOrOverwrite(admin, table);
            System.out.println(" Done.");
        }
    }

    public static void main(String... args) throws IOException {
        // 添加必要的配置文件 (hbase-site.xml, core-site.xml)
        List<String> resources = new ArrayList<String>() {
            {
                add("/home/xwd/ws/hbase/hbase-clientconfig/hbase-conf/core-site.xml");
                add("/home/xwd/ws/hbase/hbase-clientconfig/hbase-conf/hbase-site.xml");
                add("/home/xwd/ws/hbase/hbase-clientconfig/hbase-conf/hdfs-site.xml");
            }
        };
        String krb5Conf = "/home/xwd/ws/hbase/krb5/krb5.conf";
        String principal = "xingweidong@BIGDATA.ZXXK.COM";
        String keytabFile = "/home/xwd/ws/hbase/krb5/xingweidong.keytab";

        // HBase操作
        Connection connection = HBaseClientUtil.getHBaseConn(resources, krb5Conf, principal, keytabFile);
        createSchemaTables(connection);

        System.out.println(" Put data ");
        Table table = connection.getTable(TableName.valueOf("test:test"));
        try {
            Put put = new Put(Bytes.toBytes("hbase_client_test"));
            put.addColumn(Bytes.toBytes("cf"), Bytes.toBytes("col"), Bytes.toBytes("hbase_loginUserFromKeytab"));
            table.put(put);
        } finally {
            // 关闭连接
            table.close();
            connection.close();
        }
    }
}

配置要点

依赖包

从目标环境中复制即可,包括hadoop和hbase两个服务的相关jar。
如果是CDH集群,复制 /opt/cloudera/parcels/CDH/jars目录下的jar即可,里面包含了所有CDH服务的jar包。

如果使用IntelliJ IDEA开发,可以快速添加外部依赖库,以cdh依赖包为例。在工程中创建目录 src/libs/cdh,将依赖包放入cdh目录,右键点击cdh目录,选择 Add as Library。

在pom.xml指定外部库,就不需要从maven仓库进行下载了,也不会遇到版本问题,pom.xml示例:

<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
         xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
         xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
    <modelVersion>4.0.0</modelVersion>

    <groupId>com.example.bigdata</groupId>
    <artifactId>devexample</artifactId>
    <version>1.0-SNAPSHOT</version>

    <build>
        <plugins>
            <plugin>
                <groupId>org.apache.maven.plugins</groupId>
                <artifactId>maven-compiler-plugin</artifactId>
                <configuration>
                    <source>1.8</source>
                    <target>1.8</target>
                    <encoding>UTF-8</encoding>
                    <compilerArguments>
                        <extdirs>${basedir}/src/libs/cdh</extdirs><!--指定外部lib-->
                    </compilerArguments>
                </configuration>
            </plugin>
        </plugins>
    </build>

</project>

添加配置文件

添加配置文件,加载目标环境的配置项。
Java通过HBase Client连接HBase的时候,可以加载目标环境的配置文件,以获取正确的配置,比起在代码中显示地设置配置项,加载配置文件资源的方法能够获取完整的目标环境配置,可以减少错误和精简代码。
主要配置文件是hbase-site.xmlcore-site.xml,这里还添加了 hdfs-site.xml,为了避免缺少配置。这三个文件都是HBase的客户端配置下的文件。
获取HBase客户端配置的通用方式是从HBase的配置目录中获取。如果使用Cloudera Manager管理HBase,可以从Cloudera Manager中的HBase服务下载客户端配置:操作 -> 下载客户端配置

Kerberos认证

如果HBase启用了Kerberos,在客户端访问时需要进行Kerberos认证,认证的方法有两种:

1、HBase通用客户端Kerberos认证。

2、HBase2.2.0+客户端Kerberos认证。从HBase 2.2.0开始支持的认证方法。

在 2.2.0 版本之前,客户端环境必须通过kinit命令从 KDC 或 keytab 登录到 Kerberos,然后才能与 HBase 集群进行通信。

从 2.2.0 开始,客户端可以在hbase-site.xml中指定以下配置:

<property>
  <name>hbase.client.keytab.file</name>
  <value>/local/path/to/client/keytab</value>
</property>

<property>
  <name>hbase.client.keytab.principal</name>
  <value>foo@EXAMPLE.COM</value>
</property> 

然后,应用程序可以自动执行登录和凭据续订作业,而不会受到客户端干扰。

它是可选功能,客户端升级到 2.2.0,只要保持hbase.client.keytab.filehbase.client.keytab.principal未设置,它仍然可以保留旧版本中的登录和凭证更新逻辑。

请注意,如果客户端和服务器端站点文件中的hbase.security.authentication不匹配,则客户端将无法与群集通信。

HBase通用客户端Kerberos认证

示例代码:

/**
 * HBase 通用客户端Kerberos认证
 * @param resources 配置文件资源
 * @param krb5Conf krb5.conf文件路径
 * @param principal Kerberos用户主体,eg:xingweidong@BIGDATA.ZXXK.COM
 * @param keytabFile keytab文件路径
 * @return
 * @throws IOException
 */
public static Connection getHBaseConn(List<String> resources, String krb5Conf, String principal, String keytabFile) throws IOException {
    Configuration config = HBaseConfiguration.create();

    // 添加必要的配置文件 (hbase-site.xml, core-site.xml)
    for (int i = 0; i < resources.size(); i++) {
        config.addResource(new Path(resources.get(i)));
    }

    // Kerberos认证
    // 设置java安全krb5.conf
    System.setProperty("java.security.krb5.conf", krb5Conf);
    // 设置用户主体(Principal)
    config.set("kerberos.principal" , principal);
    // 使用用户keytab文件认证
    config.set("keytab.file" , keytabFile);

    UserGroupInformation.setConfiguration(config);
    try {
        // 登录
        UserGroupInformation.loginUserFromKeytab(principal, keytabFile);
    } catch (IOException e) {
        e.printStackTrace();
    }

    // 创建连接
    return ConnectionFactory.createConnection(config);
}

这个方法的核心是在代码中使用UserGroupInformation.loginUserFromKeytab() 进行Kerberos认证,主要配置说明:

配置说明
java.security.krb5.confkrb5.conf文件路径。该文件从目标集群下载,文件在服务器的默认路径:/etc/krb5.conf。
keytab.file用户的keytab文件,使用keytab文件可以避免交互式输入密码。
kerberos.principalKerberos用户主体名。
UserGroupInformation.loginUserFromKeytab使用keytab方式登录。

上述示例代码主要描述了Kerberos认证,但是在实际应用中,Kerberos keytab是需要自动更新的,否则一旦keytab过期,应用就会出现认证失败的问题。
通过分析UserGroupInformation的源码,发现了启用keytab自动更新的配置,主要源码如下:

private static synchronized void initialize(Configuration conf, boolean overrideNameRules) {
    authenticationMethod = SecurityUtil.getAuthenticationMethod(conf);
    if (overrideNameRules || !HadoopKerberosName.hasRulesBeenSet()) {
        try {
            HadoopKerberosName.setConfiguration(conf);
        } catch (IOException var7) {
            throw new RuntimeException("Problem with Kerberos auth_to_local name configuration", var7);
        }
    }

    try {
        kerberosMinSecondsBeforeRelogin = 1000L * conf.getLong("hadoop.kerberos.min.seconds.before.relogin", 60L);
    } catch (NumberFormatException var6) {
        throw new IllegalArgumentException("Invalid attribute value for hadoop.kerberos.min.seconds.before.relogin of " + conf.get("hadoop.kerberos.min.seconds.before.relogin"));
    }

    kerberosKeyTabLoginRenewalEnabled = conf.getBoolean("hadoop.kerberos.keytab.login.autorenewal.enabled", false);
    if (!(groups instanceof UserGroupInformation.TestingGroups)) {
        groups = Groups.getUserToGroupsMappingService(conf);
    }

    UserGroupInformation.conf = conf;
    if (metrics.getGroupsQuantiles == null) {
        int[] intervals = conf.getInts("hadoop.user.group.metrics.percentiles.intervals");
        if (intervals != null && intervals.length > 0) {
            int length = intervals.length;
            MutableQuantiles[] getGroupsQuantiles = new MutableQuantiles[length];

            for(int i = 0; i < length; ++i) {
                getGroupsQuantiles[i] = metrics.registry.newQuantiles("getGroups" + intervals[i] + "s", "Get groups", "ops", "latency", intervals[i]);
            }

            metrics.getGroupsQuantiles = getGroupsQuantiles;
        }
    }

}

其中的kerberosKeyTabLoginRenewalEnabled这个配置就是keytab自动更新的开关了,默认值为false,我们可以通过在应用代码中设置为true,来启用这个功能,示例代码如下:

// 启用keytab renewal
config.set("hadoop.kerberos.keytab.login.autorenewal.enabled", "true");

HBase2.2.0+客户端Kerberos认证

从HBase2.2.0版本开始,支持了一种新的Kerberos认证方法,只需要添加两个客户端配置,HBase会自动处理认证登录和凭证更新。

Hbase官方API org.apache.hadoop.hbase.client.ConnectionFactory 的说明如下:

Since 2.2.0, Connection created by ConnectionFactory can contain user-specified kerberos credentials if caller has following two configurations set:

  • hbase.client.keytab.file, points to a valid keytab on the local filesystem
  • hbase.client.kerberos.principal, gives the Kerberos principal to use

By this way, caller can directly connect to kerberized cluster without caring login and credentials renewal logic in application.

示例代码:(未验证)

/**
 * HBase2.2.0+ 客户端Kerberos认证 (未验证)
 * @param resources 配置文件资源
 * @param krb5Conf krb5.conf文件路径
 * @param principal Kerberos用户主体,eg:xingweidong@BIGDATA.ZXXK.COM
 * @param keytabFile keytab文件路径
 * @return
 * @throws IOException
 */
public static Connection getHBaseConn220(List<String> resources, String krb5Conf, String principal, String keytabFile) throws IOException {
    Configuration config = HBaseConfiguration.create();

    // 添加必要的配置文件 (hbase-site.xml, core-site.xml)
    for (int i = 0; i < resources.size(); i++) {
        config.addResource(new Path(resources.get(i)));
    }

    // 设置java安全krb5.conf (未验证是否需要此配置)
    System.setProperty("java.security.krb5.conf", krb5Conf);

    // Kerberos认证
    config.set("hbase.client.kerberos.principal", principal);
    config.set("hbase.client.keytab.file", keytabFile);

    // 创建连接
    return ConnectionFactory.createConnection(config);
}

我使用的是CDH6.3.2,HBase版本是2.1.0,验证发现使用这两个配置项进行连接HBase是无法成功的,但是在Spark Streaming中使用这个配置可以成功将数据写入HBase,比较奇怪,也许有哪个地方不对。

获取keytab文件

使用目标用户登录gateway01.bigdata.zxxk.com主机,例如xingweidong,执行以下命令:

ipa-getkeytab -s utility1.bigdata.zxxk.com -p xingweidong@BIGDATA.ZXXK.COM -k ./xingweidong.keytab --password

输入密码即可获取keytab文件。

参数说明

参数说明示例值
-sFreeIPA服务端主机名utility1.bigdata.zxxk.com
-p用户主体,注意加上域名xingweidong@BIGDATA.ZXXK.COM
-kkeytab文件存储目录./xingweidong.keytab
–password使用密码

问题记录

Server not found in Kerberos database

报错


Caused by: javax.security.sasl.SaslException: Call to worker03.bigdata.zxxk.com/10.111.116.225:16020 failed on local exception: javax.security.sasl.SaslException: GSS initiate failed [Caused by GSSException: No valid credentials provided (Mechanism level: Server not found in Kerberos database (7) - LOOKING_UP_SERVER)] [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) - LOOKING_UP_SERVER)]]
        ... 
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) - LOOKING_UP_SERVER)]
        ...
Caused by: GSSException: No valid credentials provided (Mechanism level: Server not found in Kerberos database (7) - LOOKING_UP_SERVER)
        ...
Caused by: KrbException: Server not found in Kerberos database (7) - LOOKING_UP_SERVER
        ...
Caused by: KrbException: Identifier doesn't match expected value (906)
        ...

问题:调用worker03.bigdata.zxxk.com/10.111.116.225:16020失败,在Kerberos数据库没有找到服务。

分析

查看krb5kdc.log,发现:

Aug 08 09:56:41 utility1.bigdata.zxxk.com krb5kdc[11263](info): TGS_REQ (4 etypes {18 17 16 23}) 10.111.127.23: LOOKING_UP_SERVER: authtime 0,  hbase_zxxk_user@BIGDATA.ZXXK.COM for hbase/10.111.116.225@BIGDATA.ZXXK.COM, Server not found in Kerberos database

kerberos在数据库中无法找到服务:hbase/10.111.116.225@BIGDATA.ZXXK.COM
但是kerberos数据库中存在服务:hbase/worker03.bigdata.zxxk.com@BIGDATA.ZXXK.COM
理论上,应该使用 hbase/worker03.bigdata.zxxk.com@BIGDATA.ZXXK.COM 访问hbase,但是不知道是哪个环节搞错了,所以无法正确访问hbase。

解决

hbase客户端相关的配置如下:

<property>
  <name>hbase.regionserver.kerberos.principal</name>
  <value>hbase/_HOST@BIGDATA.ZXXK.COM</value>
</property>

这本案例中,正常来说,_HOST 会被替换为 worker03.bigdata.zxxk.com,但是根据结果来看,_HOST 被替换成 10.111.116.225 了。
经分析,替换错误的原因是DNS解析错误,只要在网络配置中配置正确的DNS服务即可解决。
在本案例中,配置DNS服务:114.114.114.114 和 8.8.8.8 ,可以正常访问HBase。

Logo

华为开发者空间,是为全球开发者打造的专属开发空间,汇聚了华为优质开发资源及工具,致力于让每一位开发者拥有一台云主机,基于华为根生态开发、创新。

更多推荐