https://forum.huawei.com/enterprise/zh/thread-337775.html
最近被安排做java本地访问带认证的CDH集群,网上查了各种博客,试了各种方法都无法登录上。最终发现是端口问题,因为kdc默认端口是88,所以必须给88端口为UDP协议
使用API访问开启安全Kerberos的Hdfs
hadoop集群(cdh集群)在开启kerberos安全认证方式后,通常如果在集群shell客户端通过hadoop dfs命令访问的,经过kinit登录kerberos认证即可 ,如下所示
如果不进行kinit登录kerberos用户,则不能进行hdfs操作,如图直接会报安全异常!
而如果进行kinit登录后就能进行hdfs操作了,通过kinit user@YOU-REALM 然后输出密码就能在当前交互下获取kerberos票据授权票据
而如果通过程序在远程进行访问,显然不能再通过kinit来进行登录了,此时需要通过keytab文件来进行访问,keytab文件生成这里不在进行说明,主要说明获取keytab文件后如果通过代码来进行访问
package com.test.hdfs;
import org.apache.hadoop.conf.Configuration;
import org.apache.hadoop.fs.FileSystem;
import org.apache.hadoop.fs.LocatedFileStatus;
import org.apache.hadoop.fs.Path;
import org.apache.hadoop.fs.RemoteIterator;
import org.apache.hadoop.security.UserGroupInformation;
import javax.security.auth.Subject;
import java.io.IOException;
import java.net.URI;
import java.security.PrivilegedExceptionAction;
public class HdfsTest {
public static void main(String[] args) throws Exception {
test1();
}
//kerberos
public static void test1() throws Exception{
//设置java安全krb5配置,其中krb5.conf文件可以从成功开启kerberos的集群任意一台节点/etc/krb5.conf拿到,
//这里应该也可以直接设置一下两个属性获取 ,我没有测试这个
//System.setProperty("java.security.krb5.realm","YOU-REALM.COM");
//System.setProperty("java.security.krb5.KDC","kdc_hostname");
System.setProperty("java.security.krb5.conf", "E:\\test\\krb5.conf")
Configuration conf = new Configuration();
//这里设置namenode新
conf.set("fs.defaultFS", "hdfs://namenode:8020");
//需要增加hadoop开启了安全的配置
conf.setBoolean("hadoop.security.authorization", true);
//配置安全认证方式为kerberos
conf.set("hadoop.security.authentication", "Kerberos");
//设置namenode的principal
conf.set("dfs.namenode.kerberos.principal", "hdfs/[email protected]");
//设置datanode的principal值为“hdfs/[email protected]”
conf.set("dfs.datanode.kerberos.principal", "hdfs/[email protected]");
//通过hadoop security下中的 UserGroupInformation类来实现使用keytab文件登录
UserGroupInformation.setConfiguration(conf);
//设置登录的kerberos principal和对应的keytab文件,其中keytab文件需要kdc管理员生成给到开发人员
UserGroupInformation.loginUserFromKeytab("[email protected]","E:\\test\\user01.keytab");
//获取带有kerberos验证的文件系统类
FileSystem fileSystem1 = FileSystem.get(conf);
//测试访问情况
Path path=new Path("hdfs://namenodehost:8020/user/user01");
if(fileSystem1.exists(path)){
System.out.println("===contains===");
}
RemoteIterator list=fileSystem1.listFiles(path,true);
while (list.hasNext()){
LocatedFileStatus fileStatus=list.next();
System.out.println(fileStatus.getPath());
}
}
}
===============================================================================
开始之前
因为HBase的存储系统是基于Hadoop的存储,现在Hadoop已经增加了Kerberos认证机制,这样HBase的客户端访问HBase数据库的时候也需要进行身份的认证。
Kerberos是一个认证中心,客户端在访问HBase前必须通过认证才能访问,下图是Kerberos的认证图:
我们不需要详细介绍Kerberos的原理,但是大概流程可以说一下:
当HBase客户端访问HBase的时候,首先必须访问KDC获取一个经过授权的票据,以后Client在访问HBase server的时候可以通过这个票据进行访问。
正常情况下当我们通过HBase客户端访问的时候,都需要进行一次认证的过程,认证过后,KDC返回的票据具有有效期,一般默认是10小时,换句话说在这10个小时内你不需要再次登录KDC进行认证。
1,Linux客户端认证方式
如果我们现在处在Linux客户端上想进行HBase Client的Kerberos认证怎么办,我们怎么登录KDC进行认证? 在Linux有一个Kinit可以完成这种认证过程,可以通过kinit之后,传入必要的参数(例如用户名和密码)等就可以认证。 同时Kerberos还提供了另外一种方式就是我们可以通过一个.keytab 文件直接认证,这样我们就不需要记住这些密码了,keytab这个文件从哪来的呢,这也是在配置Hadoop的时候通过一个ktutil工具生成的。我们可以理解成.keytab文件是带有密码的一个文件就可以了,只要拿到这个文件我们就可以在linux机器上运行kinit 完成这个登录认证的过程 (其中hbase.keytab是服务端生成的文件)
kinit -k -t /etc/hadoop/conf/hbase.keytab hbase/[email protected]
认证完成后我们就可以直接用 hbase shell操作Hbase了
2, Java程序认证方式
通过Linux kinit 命令我们很容易的完成认证,但是如果要是用Java程序编码怎么完成这种登录认证呢?
2.1 我们把服务器端的 hbase.keytab 文件 copy到 本地一个磁盘,例如 c:\
2.2 我们copy远程集群一个hbase-site.xml文件放到你运行环境的classpath下,这个和正常没有kerberos的HBase访问是一样的
2.3 接下来我们需要用到 UserGroupInformation API来进行访问,这个API需要一个参数 principal 和一个你的 keytab 文件,这里文件里面存储的是相关的密码
代码:
package com.hbasedemo;
import java.io.IOException;
import org.apache.hadoop.conf.Configuration;
import org.apache.hadoop.hbase.HBaseConfiguration;
import org.apache.hadoop.hbase.KeyValue;
import org.apache.hadoop.hbase.client.HTable;
import org.apache.hadoop.hbase.client.Result;
import org.apache.hadoop.hbase.client.ResultScanner;
import org.apache.hadoop.hbase.client.Scan;
import org.apache.hadoop.security.UserGroupInformation;
public class Test {
private static Configuration conf = null;
static {
// 这个配置文件主要是记录 kerberos的相关配置信息,例如KDC是哪个IP?默认的realm是哪个?
// 如果没有这个配置文件这边认证的时候肯定不知道KDC的路径喽
// 这个文件也是从远程服务器上copy下来的
System. setProperty("java.security.krb5.conf", "C:/Users/dongzeguang/Downloads/krb5.conf" );
conf = HBaseConfiguration.create();
conf.set("hadoop.security.authentication" , "Kerberos" );
// 这个hbase.keytab也是从远程服务器上copy下来的, 里面存储的是密码相关信息
// 这样我们就不需要交互式输入密码了
conf.set("keytab.file" , "C:/Users/Downloads/hbase.keytab" );
// 这个可以理解成用户名信息,也就是Principal
conf.set("kerberos.principal" , "hbase/[email protected]" );
UserGroupInformation. setConfiguration(conf);
try {
UserGroupInformation. loginUserFromKeytab("hbase/[email protected]", "C:/Users/Downloads/hbase.keytab" );
} catch (IOException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
}
public static void scanSpan(final String tableName) throws Exception {
HTable table = new HTable(conf, tableName);
System. out.println("tablename:" +new String(table.getTableName()));
Scan s = new Scan();
ResultScanner rs = table.getScanner(s);
for (Result r : rs) {
System. out.println(r.toString());
KeyValue[] kv = r. raw();
for (int i = 0; i < kv.length; i++) {
System. out.print(new String(kv[i].getRow()) + "");
System. out.print(new String(kv[i].getFamily()) + ":");
System. out.print(new String(kv[i].getQualifier() ) + "" );
System. out.print(kv[i].getTimestamp() + "" );
System. out.println(new String(kv[i].getValue() ));
}
}
}
/**
* @param args
*/
public static void main(String[] args) {
// TODO Auto-generated method stub
try {
Test. scanSpan("h_span");
} catch (Exception e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
}
}
---------------------
Java访问带有Kerberos认证的Hive
package com.asiainfo.c3.http.service.mvc;
import com.asiainfo.c3.util.Config;
import com.asiainfo.c3.util.HDFSUtil;
import org.apache.hadoop.conf.Configuration;
import org.apache.hadoop.security.UserGroupInformation;
import java.io.IOException;
import java.sql.*;
import java.util.List;
/**
* 简单的jdbc连接hive实例(已开启kerberos服务)
*/
public class HiveSimple{
/**
* 用于连接Hive所需的一些参数设置 driverName:用于连接hive的JDBC驱动名 When connecting to
* HiveServer2 with Kerberos authentication, the URL format is:
* jdbc:hive2://:/;principal=
*
*/
private static String driverName = "org.apache.hive.jdbc.HiveDriver";
// 注意:这里的principal是固定不变的,其指的hive服务所对应的principal,而不是用户所对应的principal
private static String url = "jdbc:hive2://gzhdp-nn03:9087/tenant_999;principal=aiinfo/[email protected];hive.server2.proxy.user=aiinfo";
private static String sql = "";
private static ResultSet res;
private static Configuration conf = null;
private static UserGroupInformation ugi = null;
public static Connection get_conn() throws SQLException, ClassNotFoundException, IOException {
/** 使用Hadoop安全登录 **/
conf = new Configuration();
// 这个hbase.keytab也是从远程服务器上copy下来的, 里面存储的是密码相关信息
//System.setProperty("java.security.krb5.conf", "C:/Windows/krb5.ini");
System.setProperty("java.security.krb5.kdc","gzhdp-nn01");
System.setProperty("java.security.krb5.realm","BONC.COM");
// 开启登陆调试日志
System.setProperty("sun.security.krb5.debug", "true");
conf.addResource(HDFSUtil.class.getResourceAsStream("/conf/core-site.xml"));
conf.addResource(HDFSUtil.class.getResourceAsStream("/conf/hdfs-site.xml"));
//conf.set("fs.hdfs.impl",org.apache.hadoop.hdfs.DistributedFileSystem.class.getName());
conf.set("hadoop.security.authentication", "Kerberos");
/* if (System.getProperty("os.name").toLowerCase().startsWith("win")) {
// 默认:这里不设置的话,win默认会到 C盘下读取krb5.init
System.setProperty("java.security.krb5.conf", "E:/hivekey/krb5.conf");
} // linux 会默认到 /etc/krb5.conf 中读取krb5.conf,本文笔者已将该文件放到/etc/目录下,因而这里便不用再设置了
*/
try {
UserGroupInformation.setConfiguration(conf);
String user = Config.getObject("kerberos.principal");
String path = Config.getObject("keytab.file");
ugi = UserGroupInformation.loginUserFromKeytabAndReturnUGI(user, path);
UserGroupInformation.setLoginUser(ugi);
} catch (IOException e1) {
e1.printStackTrace();
}
Class.forName(driverName);
Connection conn = DriverManager.getConnection(url,"603ec1b520c0245bd92a3782b05e0b39","6567a0bd1b8f0921e1b823ccbdc9a820c385a60745e237c51086106d4b14673d");
return conn;
}
/**
* 查看数据库下所有的表
*
* @param statement
* @return
*/
public static boolean show_tables(Statement statement) {
sql = "SHOW TABLES";
System.out.println("Running:" + sql);
try {
ResultSet res = statement.executeQuery(sql);
System.out.println("执行“+sql+运行结果:");
while (res.next()) {
System.out.println(res.getString(1));
}
return true;
} catch (SQLException e) {
e.printStackTrace();
}
return false;
}
/**
* 获取表的描述信息
*
* @param statement
* @param tableName
* @return
*/
public static boolean describ_table(Statement statement, String tableName) {
sql = "DESCRIBE " + tableName;
try {
res = statement.executeQuery(sql);
System.out.print(tableName + "描述信息:");
while (res.next()) {
System.out.println(res.getString(1) + "\t" + res.getString(2));
}
return true;
} catch (SQLException e) {
e.printStackTrace();
}
return false;
}
/**
* 删除表
*
* @param statement
* @param tableName
* @return
*/
public static boolean drop_table(Statement statement, String tableName) {
sql = "DROP TABLE IF EXISTS " + tableName;
System.out.println("Running:" + sql);
try {
statement.execute(sql);
System.out.println(tableName + "删除成功");
return true;
} catch (SQLException e) {
System.out.println(tableName + "删除失败");
e.printStackTrace();
}
return false;
}
/**
* 查看表数据
*
* @param preparedStatement
* @return
*/
public static boolean queryData(PreparedStatement preparedStatement, String tableName) {
sql = "SELECT * FROM " + tableName + " LIMIT 10";
System.out.println("Running:" + sql);
try {
res = preparedStatement.executeQuery(sql);
System.out.println("执行“+sql+运行结果:");
while (res.next()) {
System.out.println(res.getString(1) + "," + res.getString(2) + "," + res.getString(3) + ","+"," + res.getString(12));
}
return true;
} catch (SQLException e) {
e.printStackTrace();
}
return false;
}
/**
* 创建表
*
* @param statement
* @return
*/
public static boolean createTable(Statement statement, String tableName) {
sql = "CREATE TABLE test_1m_test2 AS SELECT * FROM test_1m_test"; // 为了方便直接复制另一张表数据来创建表
System.out.println("Running:" + sql);
try {
boolean execute = statement.execute(sql);
System.out.println("执行结果 :" + execute);
return true;
} catch (SQLException e) {
e.printStackTrace();
}
return false;
}
public static void main(String[] args) {
try {
Connection conn = get_conn();
ugi.checkTGTAndReloginFromKeytab();
PreparedStatement stmt = conn.prepareStatement(sql);
// 创建的表名
String tableName = "user_info_360";
// show_tables(stmt);
queryData(stmt,tableName);
// describ_table(stmt, tableName);
/** 删除表 **/
// drop_table(stmt, tableName);
// show_tables(stmt);
// queryData(stmt, tableName);
//createTable(stmt, tableName);
conn.close();
} catch (Exception e) {
e.printStackTrace();
} finally {
System.out.println("!!!!!!END!!!!!!!!");
}
}
}