Hadoop认证Kerberos--UserGroupInformation.doAs

在访问带有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的方式才正常。