最近为了解决本地发布测试代码到服务器的问题,试用了下JAAS(Java Authencation and Authorization Service,Java授权与验证服务)和GSSAPI来完成Kerberos登录验证。完成了身份验证以后,就可以使用ant标准任务SCP来完成发布操作了。
关于Kerberos验证机制请参考:一个不错的Kerberos机制讲解blog(虽然头像有点,但不要以貌取文)
基于Kerberos的验证机制,需要两步来完成登录服务器身份验证:
1、通过JAAS完成客户端与KDC服务器的验证工作,并获得登录服务器的ticket
2、使用GSSAPI登录服务器
注:以下代码能够正常执行的前提是,执行的机器上已经正常配置了Kerberos登录的文件,可以参考Kerberos登录软件的配置方式,确保使用Kerberos的登录软件可以正常登录服务器之后再尝试以下代码。下面的代码只是用于辅助自动登录,并不包含具体Kerberos登录的配置
1、使用JAAS登录Kerberos账号
关于JAAS的详细信息请参考:Sun的Jaas和Gssapi指南 。
我使用的代码与指南中的基本相同,所以具体内容的详细解释请参考:Jaas验证样例
使用JAAS登录Kerberos登录的步骤如下:
- JAAS服务中已经提供了Kerberos登录的实现,需要在启动程序时添加配置文件信息来指定登录的模式为Kerberos,例如:-Djava.security.auth.login.config=/data/jaas.conf,或者配置System属性也可以。
- jaas.conf的内容很简单,只是指定了登录模式为Kerberos:
KerberosLogin { com.sun.security.auth.module.Krb5LoginModule required debug=true; };
- 使用JAAS服务需要调用LoginContext(javax.security.auth.login包)来尝试登录,代码如下:
LoginContext lc = null; try { lc = new LoginContext ("KerberosLogin ", new TextCallbackHandler()); //“KerberosLogin”这个名称必须与配置文件中的名称相同 } catch (LoginException le) { System.err.println("Cannot create LoginContext. " + le.getMessage()); System.exit(-1); } catch (SecurityException se) { System.err.println("Cannot create LoginContext. " + se.getMessage()); System.exit(-1); } try { // attempt authentication lc.login(); } catch (LoginException le) { System.err.println("Authentication failed:"); System.err.println(" " + le.getMessage()); System.exit(-1); }
- 如果正确的配置了Kerberos的登录信息,则以上代码如果正常运行完就表示客户端与KDC的通讯已经成功完成。
2、使用GSSAPI完成SSH的身份验证
完成了客户端与KDC的通讯后,剩下的任务就是登录服务器了。以下的代码必须在成功完成步骤1之后进行,具体步骤为:
- 在subject的权限下进行登录。完成了步骤一之后,我们可以从login context中获得身份验证的一个subject对象,这个对象中包含了当前用户的权限,接下来的登录服务器操作必须在这个subject的权限范围内完成,如
Subject.doAsPrivileged(lc.getSubject() , new java.security.PrivilegedExceptionAction<Object>() { @Override public Object run() throws Exception { //登录操作 } } }
- 构建GSS context对象。这个context对象包含了用于登录服务器的ticket以及客户端的相关信息。
try { GSSManager manager = GSSManager.getInstance(); //Kerberos的oid Oid krb5Mechanism = new Oid("1.2.840.113554.1.2.2"); GSSName serverName = manager.createName("host/" + hostName, null); GssContext context = manager.createContext(serverName, krb5Mechanism, null, GSSContext.DEFAULT_LIFETIME); context.requestMutualAuth(true); // Mutual authentication context.requestConf(true); // Will use confidentiality later context.requestInteg(true); // Will use integrity later } catch (GSSException e) { // catch Exception }
- 拥有了GSS context对象之后,就可以进行SSH的身份验证,完成了身份验证之后就可以像普通的SCP一样进行操作了。下面的代码只包含了SSH中使用GSSAPI进行验证的过程,不包含SSH的建立,交换key以及后续的sftp操作。
/* * 使用GSSAPI进行SSH验证的基本顺序是 * 1、询问user-auth服务 * 2、获得确认后传输验证的方式 * 3、进行Kerberos的验证操作 */ 1、询问user-auth服务 请求数据: 5 byte “ssh-userauth” String 响应标志 6 2、请求身份验证方法为Kerberos 50 byte username string 登录服务器的名称,不是登录Kerberos的名称 "ssh-connection" string "gssapi-with-mic" string 1 int 表示只有一种类型的oid在后面 new Oid("1.2.840.113554.1.2.2").getDER() Kerberos的oid的DER编码 响应标志 60 表示支持Kerberos登录 3、进行Kerberos验证,这里需要使用刚才构建的GSS context对象 byte[] token = new byte[0]; while (!context.isEstablished()) { try { token = context.init(token, 0, token.length); } catch (JSchException e) { e.printStackTrace(); return; } if (token != null) { // 会写数据 // 61 byte // token string } if (!context.isEstablished()) { // 读取返回信息,忽略错误和失败的情况 // 以下代码代表读取返回信息,并设置给token,用与继续context的初始化参数 buf.getInt(); buf.getByte(); buf.getByte(); token = buf.getString(); } }
注:这段代码省略了关于SSH通讯数据的解析和输出部分,具体关于SSH的通讯格式请参数wikipedia的SSH页面 。