最近在测试hive0.11 hiveserver时遇到的一个关于认证的bug,具体表现:
在配置中指定了custom的认证方式时,通过beeline连接hiveserver2,发现连接hang住。
hive配置:
|
1
2
3
4
5
6
7
8
|
<property>
<name>hive.server2.authentication</name>a
<value>CUSTOM</value>
</property>
<property>
<name>hive.server2.custom.authentication.
class
</name>
<value>com.vipshop.hive.service.AuthWithPasswd</value>
</property>
|
查看hiveserver的日志,发现有如下报错:
|
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
|
15
/
01
/
08
17
:
54
:
59
ERROR server.TThreadPoolServer: Error occurred during processing of message.
java.lang.RuntimeException: java.lang.NoSuchMethodException: org.apache.hive.service.auth.PasswdAuthenticationProvider.<init>()
at org.apache.hadoop.util.ReflectionUtils.newInstance(ReflectionUtils.java:
131
)
at org.apache.hive.service.auth.CustomAuthenticationProviderImpl.<init>(CustomAuthenticationProviderImpl.java:
52
)
at org.apache.hive.service.auth.AuthenticationProviderFactory.getAuthenticationProvider(AuthenticationProviderFactory.java:
62
)
at org.apache.hive.service.auth.PlainSaslHelper$PlainServerCallbackHandler.handle(PlainSaslHelper.java:
73
)
at org.apache.hive.service.auth.PlainSaslServer.evaluateResponse(PlainSaslServer.java:
102
)
at org.apache.thrift.transport.TSaslTransport$SaslParticipant.evaluateChallengeOrResponse(TSaslTransport.java:
509
)
at org.apache.thrift.transport.TSaslTransport.open(TSaslTransport.java:
264
)
at org.apache.thrift.transport.TSaslServerTransport.open(TSaslServerTransport.java:
41
)
at org.apache.thrift.transport.TSaslServerTransport$Factory.getTransport(TSaslServerTransport.java:
216
)
at org.apache.thrift.server.TThreadPoolServer$WorkerProcess.run(TThreadPoolServer.java:
189
)
at java.util.concurrent.ThreadPoolExecutor$Worker.runTask(ThreadPoolExecutor.java:
886
)
at java.util.concurrent.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:
908
)
at java.lang.Thread.run(Thread.java:
662
)
Caused by: java.lang.NoSuchMethodException: org.apache.hive.service.auth.PasswdAuthenticationProvider.<init>()
at java.lang.Class.getConstructor0(Class.java:
2706
)
at java.lang.Class.getDeclaredConstructor(Class.java:
1985
)
at org.apache.hadoop.util.ReflectionUtils.newInstance(ReflectionUtils.java:
125
)
...
12
more
|
注释掉hive.server2.authentication的配置(即使用默认的none)后正常。这其实是hive0.11.0中的bug,在hive0.13.0中fix,bug id:
https://issues.apache.org/jira/browse/HIVE-4778
下面分析下具体涉及的类:
org.apache.hive.service.auth.HiveAuthFactory中的AuthTypes定义了多种认证方法(NONE,LDAP,KERBEROS,CUSTOM,PAM(hive0.11不支持)等)这里我们用到了CUSTOM,基于user和password的认证方式,涉及到PasswdAuthenticationProvider和CustomAuthenticationProviderImpl类
1)org.apache.hive.service.auth.PasswdAuthenticationProvider是一个基于用户名和密码验证的接口,主要定义了一个抽象方法Authenticate (参数就是用户名和密码)
2)org.apache.hive.service.auth.AnonymousAuthenticationProviderImpl类实现了PasswdAuthenticationProvider接口,提供了一个空的Authenticate方法(直接return)
3)org.apache.hive.service.auth.AuthenticationProviderFactory定义了一个AuthMethods的enum类,定义了有效的认证方法(hive0.13:LDAP/PAM/CUSTOM/NONE,hive0.11:LDAP/CUSTOM/NONE),并提供了对应的封装,同时定义了一个getAuthenticationProvider方法,用来返回对应的 AuthMethods的具体实现类:
|
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
|
public
static
PasswdAuthenticationProvider getAuthenticationProvider(AuthMethods authMethod)
throws
AuthenticationException {
if
(authMethod.equals(AuthMethods.LDAP)) {
return
new
LdapAuthenticationProviderImpl();
}
else
if
(authMethod.equals(AuthMethods.PAM)) {
return
new
PamAuthenticationProviderImpl();
}
else
if
(authMethod.equals(AuthMethods.CUSTOM)) {
return
new
CustomAuthenticationProviderImpl();
}
else
if
(authMethod.equals(AuthMethods.NONE)) {
return
new
AnonymousAuthenticationProviderImpl();
}
else
{
throw
new
AuthenticationException(
"Unsupported authentication method"
);
}
}
|
4)org.apache.hive.service.auth.CustomAuthenticationProviderImpl
PasswdAuthenticationProvider接口的具体实现类
在hive0.13中的实现:
|
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
|
public
class
CustomAuthenticationProviderImpl
implements
PasswdAuthenticationProvider {
Class<?
extends
PasswdAuthenticationProvider> customHandlerClass;
PasswdAuthenticationProvider customProvider;
@SuppressWarnings
(
"unchecked"
)
CustomAuthenticationProviderImpl () {
HiveConf conf =
new
HiveConf();
this
.customHandlerClass = (Class<?
extends
PasswdAuthenticationProvider>)
conf.getClass(
HiveConf.ConfVars.HIVE_SERVER2_CUSTOM_AUTHENTICATION_CLASS.varname,
PasswdAuthenticationProvider.
class
);
this
.customProvider =
ReflectionUtils.newInstance(
this
.customHandlerClass, conf);
}
@Override
public
void
Authenticate(String user, String password)
throws
AuthenticationException {
this
.customProvider.Authenticate(user, password);
}
}
|
hive0.11中的实现:
|
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
|
public
class
CustomAuthenticationProviderImpl
implements
PasswdAuthenticationProvider {
Class<?
extends
PasswdAuthenticationProvider> customHandlerClass;
PasswdAuthenticationProvider customProvider;
@SuppressWarnings
(
"unchecked"
)
CustomAuthenticationProviderImpl () {
HiveConf conf =
new
HiveConf();
this
.customHandlerClass = (Class<?
extends
PasswdAuthenticationProvider>)
conf.getClass(
HiveConf.ConfVars.HIVE_SERVER2_CUSTOM_AUTHENTICATION_CLASS.name(),
PasswdAuthenticationProvider.
class
);
this
.customProvider =
ReflectionUtils.newInstance(
this
.customHandlerClass, conf);
}
@Override
public
void
Authenticate(String user, String password)
throws
AuthenticationException {
this
.customProvider.Authenticate(user, password);
}
}
|
不同的就是customHandlerClass 的获取方法,这里我们手动测试下:
|
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
|
import
java.lang.*;
public
class
HiveConf{
public
static
enum
ConfVars {
PLAN_SERIALIZATION(
"hive.plan.serialization.format"
,
"kryo"
),
HIVE_SERVER2_CUSTOM_AUTHENTICATION_CLASS(
"hive.server2.custom.authentication.class"
,
null
),
;
public
final
String varname;
public
final
String defaultVal;
ConfVars(String varname, String defaultVal) {
this
.varname = varname;
this
.defaultVal = defaultVal;
}
public
String toString() {
return
varname;
}
}
public
static
void
main(String args[]) {
System.out.println(
"ConfVars List:"
);
for
(ConfVars c:ConfVars.values()){
System.out.println(c +
" is: "
+ c);
System.out.println(c +
" varname: "
+ c.varname);
System.out.println(c +
" name(): "
+ c.name());
}
System.out.println(HiveConf.ConfVars.HIVE_SERVER2_CUSTOM_AUTHENTICATION_CLASS.name());
System.out.println(HiveConf.ConfVars.HIVE_SERVER2_CUSTOM_AUTHENTICATION_CLASS.varname);
}
}
|
可以看到使用HiveConf.ConfVars.HIVE_SERVER2_CUSTOM_AUTHENTICATION_CLASS.varname时,this.customHandlerClass的结果为具体的实现类(即hive.server2.custom.authentication.class设置的类)比如class com.vipshop.hive.service.AuthWithPasswd,而使用HiveConf.ConfVars.HIVE_SERVER2_CUSTOM_AUTHENTICATION_CLASS.name()时,返回的是接口,即interface org.apache.hive.service.auth.PasswdAuthenticationProvider
再来看ReflectionUtils.newInstance方法:
|
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
|
private
static
final
Class<?>[] EMPTY_ARRAY =
new
Class[]{};
....
public
static
<T> T newInstance(Class<T> theClass, Configuration conf) {
T result;
try
{
Constructor<T> meth = (Constructor<T>) CONSTRUCTOR_CACHE.get(theClass);
if
(meth ==
null
) {
meth = theClass.getDeclaredConstructor(EMPTY_ARRAY);
meth.setAccessible(
true
);
CONSTRUCTOR_CACHE.put(theClass, meth);
}
result = meth.newInstance();
}
catch
(Exception e) {
throw
new
RuntimeException(e);
}
setConf(result, conf);
return
result;
}
|
在hive0.11时这里theClass为org.apache.hive.service.auth.PasswdAuthenticationProvider,org.apache.hive.service.auth.PasswdAuthenticationProvider是一个接口,没有定义构造函数,因此会抛出异常。
再来看认证的配置是在什么时候加载的?我们通过指定一个错误的配置来看其报错堆栈:
使用如下命令开启hiveserver的debug log:
|
1
|
bin/hiveserver2 -hiveconf hive.root.logger=DEBUG,console start
|
|
1
2
3
4
5
6
7
|
15
/
01
/
08
17
:
56
:
55
INFO service.AbstractService: Service:ThriftBinaryCLIService is started.
15
/
01
/
08
17
:
56
:
55
INFO service.AbstractService: Service:HiveServer2 is started.
15
/
01
/
08
17
:
56
:
55
ERROR thrift.ThriftCLIService: Error:
javax.security.auth.login.LoginException: Unsupported authentication type CUSTEM
at org.apache.hive.service.auth.HiveAuthFactory.getAuthTransFactory(HiveAuthFactory.java:
148
)
at org.apache.hive.service.cli.thrift.ThriftBinaryCLIService.run(ThriftBinaryCLIService.java:
43
)
at java.lang.Thread.run(Thread.java:
662
)
|
在hiveserver2正常启动时默认会启动ThriftBinaryCLIService服务:
在ThriftBinaryCLIService 类的run方法:
|
1
2
3
4
5
6
7
|
public
void
run() {
try
{
hiveAuthFactory =
new
HiveAuthFactory();
TTransportFactory transportFactory = hiveAuthFactory.getAuthTransFactory();
对应的TTransportFactory对象
TProcessorFactory processorFactory = hiveAuthFactory.getAuthProcFactory(
this
);
.....
|
而在HiveAuthFactory的构造函数中会解析hive的配置,获取对应的hiveserver的认证设置:
|
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
|
public
HiveAuthFactory()
throws
TTransportException {
conf =
new
HiveConf();
transportMode = conf.getVar(HiveConf.ConfVars.HIVE_SERVER2_TRANSPORT_MODE);
authTypeStr = conf.getVar(HiveConf.ConfVars.HIVE_SERVER2_AUTHENTICATION);
默认为
null
,有效值为
"NOSASL"
,
"NONE"
,
"LDAP"
,
"KERBEROS"
,
"PAM"
,
"CUSTOM"
if
(transportMode.equalsIgnoreCase(
"http"
)) {
if
(authTypeStr ==
null
) {
authTypeStr = AuthTypes.NOSASL.getAuthName();
}
}
else
{
if
(authTypeStr ==
null
) {
authTypeStr = AuthTypes.NONE.getAuthName();
}
if
(authTypeStr.equalsIgnoreCase(AuthTypes.KERBEROS.getAuthName())
&& ShimLoader.getHadoopShims().isSecureShimImpl()) {
saslServer = ShimLoader.getHadoopThriftAuthBridge().createServer(
conf.getVar(ConfVars.HIVE_SERVER2_KERBEROS_KEYTAB),
conf.getVar(ConfVars.HIVE_SERVER2_KERBEROS_PRINCIPAL)
);
try
{
saslServer.startDelegationTokenSecretManager(conf,
null
);
}
catch
(IOException e) {
throw
new
TTransportException(
"Failed to start token manager"
, e);
}
}
}
}
|
getAuthTransFactory方法会判断authTypeStr是否为有效值,否则抛出异常,退出启动
|
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
|
public
TTransportFactory getAuthTransFactory()
throws
LoginException {
TTransportFactory transportFactory;
if
(authTypeStr.equalsIgnoreCase(AuthTypes.KERBEROS.getAuthName())) {
try
{
transportFactory = saslServer.createTransportFactory(getSaslProperties());
}
catch
(TTransportException e) {
throw
new
LoginException(e.getMessage());
}
}
else
if
(authTypeStr.equalsIgnoreCase(AuthTypes.NONE.getAuthName())) {
transportFactory = PlainSaslHelper.getPlainTransportFactory(authTypeStr);
}
else
if
(authTypeStr.equalsIgnoreCase(AuthTypes.LDAP.getAuthName())) {
transportFactory = PlainSaslHelper.getPlainTransportFactory(authTypeStr);
}
else
if
(authTypeStr.equalsIgnoreCase(AuthTypes.PAM.getAuthName())) {
transportFactory = PlainSaslHelper.getPlainTransportFactory(authTypeStr);
}
else
if
(authTypeStr.equalsIgnoreCase(AuthTypes.NOSASL.getAuthName())) {
transportFactory =
new
TTransportFactory();
}
else
if
(authTypeStr.equalsIgnoreCase(AuthTypes.CUSTOM.getAuthName())) {
transportFactory = PlainSaslHelper.getPlainTransportFactory(authTypeStr);
}
else
{
throw
new
LoginException(
"Unsupported authentication type "
+ authTypeStr);
}
return
transportFactory;
}
|
本文转自菜菜光 51CTO博客,原文链接:http://blog.51cto.com/caiguangguang/1602206,如需转载请自行联系原作者