在访问带有kerberos认证的hadoop生态圈服务时,必须带上keytab文件认证。
常用的代码:
String userCode="user1";
String keytabPath = "./user1.keytab";
System.setProperty("java.security.krb5.kdc", kdc);
System.setProperty("java.security.krb5.realm", realm);
final Configuration conf = new Configuration();
UserGroupInformation ugi = UserGroupInformation.loginUserFromKeytabAndReturnUGI(userCode, keytabPath);
final HConnection connection;
ugi.doAs(new PrivilegedAction<Object>() {
@Override
public Object run() {
try {
connection = HConnectionManager.createConnection(conf);
} catch (IOException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
return null;
}
});
userCode是用户的名称,keytabPath是keytab文件的路径,一般系统是采用kdc生成的验证文件,来进行系统登录。系统不需要知道密码。
这里的ugi,就是kerberos的ticket, 是通过 UserGroupInformation.loginUserFromKeytabAndReturnUGI(userCode,keytabPath); 这个方法
成功后,返回的票据信息,通过这个票据,可以在没过期的情况下,用来访问hadoop系统。
重点是要说明,为什么要用这个票据的doAs方法来进行访问。首先我们看下UserGroupInformation的代码:
@InterfaceAudience.Public
@InterfaceStability.Evolving
public <T> T doAs(PrivilegedAction<T> action) {
logPrivilegedAction(subject, action);
return Subject.doAs(subject, action);
}
logPrivilegedAction是记录日志信息,先不用管,关键在Subject.doAs,事实上,这个是jdk的代码
public static <T> T doAs(final Subject subject,
final java.security.PrivilegedAction<T> action) {
java.lang.SecurityManager sm = System.getSecurityManager();
if (sm != null) {
sm.checkPermission(AuthPermissionHolder.DO_AS_PERMISSION);
}
if (action == null)
throw new NullPointerException
(ResourcesMgr.getString("invalid.null.action.provided"));
// set up the new Subject-based AccessControlContext
// for doPrivileged
final AccessControlContext currentAcc = AccessController.getContext();
// call doPrivileged and push this new context on the stack
return java.security.AccessController.doPrivileged
(action,
createContext(subject, currentAcc));
}
最后的代码是原生的看不到了,根据注释,可以看出,这里是新开了一个上下文,这个上下文是subject来初始化的,然后调用我们的连接方法。
subject就是存放票据的地方,Userinformation初始化后,就不可变了
定义是 privatefinal Subjectsubject;
为什么有的时候,不用doAs方法,调用认证的方法后,也可以正常访问kerberos的hadoop集群呢? 这里看下 loginUserFromKeytabAndReturnUGI的方法
static UserGroupInformation loginUserFromKeytabAndReturnUGI(String user,
String path
) throws IOException {
if (!isSecurityEnabled())
return UserGroupInformation.getCurrentUser();
String oldKeytabFile = null;
String oldKeytabPrincipal = null;
long start = 0;
try {
oldKeytabFile = keytabFile;
oldKeytabPrincipal = keytabPrincipal;
keytabFile = path;
keytabPrincipal = user;
Subject subject = new Subject();
LoginContext login = newLoginContext(
HadoopConfiguration.KEYTAB_KERBEROS_CONFIG_NAME, subject,
new HadoopConfiguration());
start = Time.now();
login.login();
metrics.loginSuccess.add(Time.now() - start);
UserGroupInformation newLoginUser = new UserGroupInformation(subject);
newLoginUser.setLogin(login);
newLoginUser.setAuthenticationMethod(AuthenticationMethod.KERBEROS);
return newLoginUser;
}
private static LoginContext newLoginContext(String appName, Subject subject, javax.security.auth.login.Configuration loginConf) throws LoginException {
// Temporarily switch the thread's ContextClassLoader to match this
// class's classloader, so that we can properly load HadoopLoginModule
// from the JAAS libraries.
Thread t = Thread.currentThread();
ClassLoader oldCCL = t.getContextClassLoader(); t.setContextClassLoader(HadoopLoginModule.class.getClassLoader());
try {
return new LoginContext(appName, subject, null, loginConf);
} finally {
t.setContextClassLoader(oldCCL);
}
}
LoginContext login 这个方法,是把当前线程的,切换到登录后subject上面去了,也就是当前线程的上下文,具备了登录信息,也就不需要用doAs的方法,也可以正常访问kerberos.
不过这种方式有局限性:
1.切换用户,必须进行一次登录操作,频繁登录,给kdc服务器造成压力,kerberos的票据,是有保存期限的,在期限里面,我们不需要登录。
2.这种方式对jdk有依赖,根据实践,IBM的jdk,重复登录,无法切换到新的用户,必须用doAs的方式才正常。