级别: 初级 Thomas Owusu (towusu@us.ibm.com), 软件工程师, IBM
2001 年 11 月 20 日 本文中,软件工程师 Thomas Owusu 基于他的
JavaGSSAPI 入门来描述了如何通过使用 Java 验证和授权的服务(JavaAuthentication and Authorization Service(JAAS))的登录接口来增强Java GSSAPI,JAAS 是一个可插的、模块化的认证和授权的 Java基础架构。Thomas 以 Java GSSAPI Kerberos 机制为例讨论了 KerberosJAAS 登录模块,该模块为 Java GSSAPI 主体提供了一个登录 Kerberos系统的接口。他还讨论了 Java GSSAPI 如何将 JAAS Subject用作一个凭证(包括服务器密钥)的中央资源库。
通用安全服务应用程序编程接口(Generic Security Service
Application Programming
Interface(GSSAPI))是一个标准化的抽象接口,它提供了一个通用的认证和安全消息传递接口,在该接口中可以插入实际的安全机制。一个通用的
GSSAPI 机制是“Kerberos 机制”,该机制是基于密钥密码术的。
GSSAPI
提供了一个标准的通用接口,通过该接口安全应用程序可以使用多种底层的安全机制。图
1 表示了一个安全应用程序如何调用标准的 GSSAPI
框架来获得安全性的好处,如认证、消息完整性和机密,而不管底层的实现安全性的机制是
Kerberos、分布式计算环境(DCE)还是简单的公用密钥 GSSAPI
机制(Simple Public-Key GSSAPI Mechanism(SPKM))。
图 1. GSSAPI
框架体系结构
为完全体会到 GSSAPI 的好处,请考虑用 Kerberos 的 Telnet,它是在
Telnet 会话期间,认证 Telnet
用户和加密在网络上交换的数据(包括密码)的一个安全的 Telnet
程序。由 Kerberos 提供该认证和消息保护功能。用 Kerberos 的 Telnet
仅可以使用 Kerberos,Kerberos 是基于密钥技术的。然而,使用 GSSAPI
接口编写的 Telnet 程序不但可以使用 Kerberos,还可以使用 GSSAPI
所支持的其它的安全机制。
GSSAPI 的主要的优点是:一个使用 GSSAPI
开发的安全应用程序无需修改就可以在不同的安全机制上工作。一个 GSSAPI
主体要求凭证作为身份的证据。GSSAPI
缺少一个获取凭证的登录接口;它仅在底层的安全机制中查询调用主体的凭证。因此,一个
GSSAPI 应用程序在调用 GSSAPI 之前必须提供凭证获取。
GSSAPI 可以通过提供利用 Java
认证和授权服务(JAAS)的登录体系结构来增强为有登录接口的
GSSAPI。JAAS
登录过程与事务处理系统中使用的两阶段提交过程类似,在事务处理系统中参与者在第一阶段准备事务,并当所有的参与者在第一阶段(即准备阶段)都成功时才在第二阶段提交事务。如果在准备阶段有一个或多个参与者失败,则终止或回滚该事务。
JAAS
登录的参与者是登录模块。在过程的第一阶段,每个登录模块执行一个登录。在第二个阶段,每个登录模块提交它的登录的结果,用在登录阶段获取的凭证更新当前访问控制上下文的
JAAS
Subject。如果所有的登录都是不成功的,就跳过提交阶段,并且放弃每个登录模块,将其状态复位。
一个 JAAS Subject
代表一个单独的安全主体。它代表了访问资源的发起者,并为 JAAS
框架中的访问控制决策建立了基础。一个 Subject
具体化了关于主体的相关信息。该信息包含主体的身份、公共的安全凭证和私有的安全凭证。由一个
Subject
代表的主体可以有多个身份和多个安全凭证,所有这些都封装在一个
Subject 中。一个 Subject 以集的形式(类
java.util.Set )维护了一个主体的身份和安全凭证。在适当的访问权限下,可用合适的
java.util.Set 修改方法来添加或从这些集中删除。如图 2
所示,一个 JAAS Subject 存在于
AccessControlContext ,这是针对安全控制决策的 Java
安全性所使用的安全性上下文的具体体现。一个 Java
虚拟机可以有不止一个
AccessControlContext 。
图 2. JAAS
Subject
在应用程序的 JAAS
配置中列有要用到的登录模块。根据登录模块对应用程序的登录的总体的成功或失败的影响,分配给每个登录模块一个权值。要想取得整个登录的成功,带有所需的或必要的权值的登录模块必须被认为是成功的。
JAAS 登录接口的一些好处
凭证获取特定于安全机制。GSSAPI
规范没有指定主体是如何获取凭证的,而且实现典型的也没有提供这样的功能。一个典型的
Kerberos GSSAPI 实现,比如,请求一个用户用 Kerberos kinit
工具来获取 Kerberos 凭证,Kerberos kinit
工具把凭证保存在一个凭证高速缓存文件中;一个服务主体被请求创建一个包含密钥的密钥表。在一个多机制环境中每个机制都要执行一个类似的过程。
在 Java GSSAPI 中使用 JAAS 登录,可以通过把该过程作为 GSSAPI
应用程序的一个组成部分来简化凭证的获取。而不需要外部的工具,如
kinit。
因为 JAAS
没有对它的登录模块的内部工作作任何限制,所以由登录模块提供的特征、功能和接口可以是简单的、复杂的、用户友好的、通用的、应用程序特定的或是模块设计者所决定的任何样子。重要的是在成功登录后,登录进来的主体的身份和凭证都置入了
Java GSSAPI,JAAS Subject 在 JAAS Subject
应用程序的当前访问控制上下文中。接着,Subject 成为应用程序所请求的
GSSAPI 凭证的中心。该方法有一个额外的好处就是:通过执行 JAAS
注销然后再登录,一个 Java GSSAPI
应用程序可以在运行时转换主体和凭证,而不必终止和重新启动应用程序。
一个 Kerberos 登录模块
设计目标和原则
JAAS
登录模块的基本目标是在成功登录尝试后把认证的登录主体身份植入给定的
JAAS Subject。对于一个 Kerberos 登录模块,这意味着首先通过从主体的
Kerberos 域的密钥分发中心(Key Distribution
Center(KDC))获取一个票证许可票证(ticket-granting
ticket(TGT))来为 Kerberos
系统认证一个主体。然后主体的身份被安装在 JAAS Subject 中。
对于作为 GSSAPI 凭证中央资源库的 Subject,在认证过程中获取的 TGT
被加到 Subject 的私有凭证集中。
通常,TGT 是为 GSSAPI 用户主体而不是为服务主体而获取的。TGT
被要求用一个或多个服务主体来为用户主体初始化 GSSAPI
上下文。另一方面,一个服务主体所要求的仅是接受 GSSAPI
上下文的密钥。一个服务密钥典型的被保存在一个密钥表文件中,该服务密钥在运行时就是从密钥表文件中检索的。
然而,一个 Java GSSAPI JAAS Kerberos
登录模块可被扩展还可以包含服务主体。可把服务主体的密钥加给 JAAS
Subject,强化将 JAAS Subject 用做一个 GSSAPI
凭证的中央资源库的目标。尽管密钥的创建没有涉及认证,但使用密钥的服务主体在接受包含相互认证的
GSSAPI
上下文时已经被认证了,在此接受过程中服务主体把自己向客户机主体认证。一个
GSSAPI 服务主体还可以初始化上下文,这时需要
TGT。登录模块也可以使其成为可能。
登录模块可以是交互式的并可以提示主体的 Kerberos
用户名和密码。在为用户主体获取 TGT
时或是为服务主体创建一个密钥时需要此信息。登录模块还可以使用一个来自凭证高速缓存的已存在的
TGT
或是来自密钥表的已存在的密钥。在该方式中,不需要用户的输入(用户名或密码)。
登录模块还支持缺省主体,即,在没有指明主体名时使用缺省的 TGT
或密钥。在这种情况下,加给 Subject 的主体是有缺省 TGT
或密钥的主体。
在多个凭证可用的场合,例如,在一个凭证高速缓存或一个密钥表,对缺省的凭证的选择可取决于很多因素。对于密钥,一些合理的因素是密钥版本号和密钥被添加到密钥表中的时间。对于
TGT,需要考虑的因素包括签发 TGT
的时间(认证时间)、它生效的时间(启动时间)、它过期的时间(结束时间)和把它加到凭证高速缓存的时间。这表明最经常添加到密钥表或凭证高速缓存的凭证是最合理的缺省选项;这当然是最简单的一种选择。
只有应用程序的全部登录都成功了,登录模块才更新当前登录上下文中的
JAAS
Subject。如果登录不成功,就要回滚在它的登录方法(第一阶段)执行的操作,并复位状态、废弃登录主体所获取的所有凭证。如果主体注销了,登录模块也要再次回滚和复位状态;这时,它还要清除那个
JAAS Subject,登录模块在这个代表主体的 Subject
里安装了部分或全部的主体身份和凭证。
让我们来概括一下前面所讲到的设计目标:
-
如果全部登录都成功了,那么就认证一个主体并把它的身份安装在给定的
JAAS Subject
中;如果登录被异常终止或主体注销了,那么就回滚并复位。这是最高目标。
- 把主体的凭证安装到 Subject 中。
- 既支持用户主体又支持服务主体。用户主体的凭证通常是
TGT。对服务主体而言,它通常是密钥,但也可以是 TGT。
- 支持从凭证高速缓存获得的 TGT
的使用,支持从密钥表获得密钥的使用。
- 支持缺省凭证;将缺省 TGT 或密钥的所有者作为主体安装在 Subject
里。
JAAS 配置选项
使用一些 JAAS 配置选项来实现简单的、基于文本的 Kerberos
登录模块中的那些设计目标。下面所列出的选项就足够了:
-
principal :Kerberos 主体的名称。
-
credsType :GSSAPI 凭证类型。它可是
initiator 、
acceptor 或
both 。
-
ccache :TGT 高速缓存的
URL。该选项还可以用于表明需要指定一个要使用的缺省 TGT
高速缓存,例如,
<<default>> 的值。
-
keytab :密钥表的 URL。指明
<<default>>
的值来表示缺省密钥表的使用。
这些选项中的一些是与其它选项不相容的。在表 1 中的单元格中的 X
表示两个选项是不相容的。例如,当请求 acceptor 凭证时指定 ccache
是无效的。标记 N
的单元格表明选项的组合是不适用的。例如,只能指定一种凭证类型。
表 1. JAAS Kerberos 登录模块配置选项
|
credsType= initiator
|
credsType= acceptor
|
credsType= both
|
ccache (URL)
|
ccache (default)
|
keytab (URL)
|
keytab (default)
| |
credsType=initiator
| | N | N | | | X | X | |
credsType=acceptor
| N | | N | X | X | | | |
credsType=both
| N | N | | | | | | |
ccache (URL)
| | X | | | N | | | |
ccache (default)
| | X | | N | | | | |
keytab (URL)
| X | | | | | | N | |
keytab (default)
| X | | | | | N | |
登录模块可根据表 1 的矩阵来执行选项有效性检查。它还应提供有意义的、有指导性的缺省值。例如,凭证类型应缺省为
initiator ,因为用户主体比服务主体更可能使用登录接口。
Subject 对象类型
登录模块必须在 JAAS Subject
安装主体和主体的凭证,将其作为特定的对象类型,从而安装信息可以随后被识别并可以从
Subject 中检索。JAAS Subject 持有作为
java.util.Set
类型集合的主体和主体凭证。这些集合中不应包含相同项。它们依靠它们的组成对象的
equals 方法来避免重复。
另外,对象类型必须是可以从相同类型的不同实例的集合中所能确定的一个合理的缺省实例。回想一下登录模块的设计目的之一就是支持缺省凭证的使用。Java
GSSAPI 必须能够定位由登录模块安装在 JAAS Subject
上的缺省凭证,其定位使用的是与登录模块从凭证高速缓存和密钥表中装入缺省凭证和密钥时所使用的一致的算法和基础。
清单 1 用两个类:
KerberosTGT 和
KerberosSecretKey
的定义说明了这些原则。这两个类分别代表了一个 Kerberos TGT
和一个密钥。它们都是从共同的祖先
KerberosCredentials
派生出来的。假定在一些可用的 Kerberos V5 包中已经定义了
Krb5Principal 类、
Krb5TGT 类和
Krb5SecretKey 类。
注意:本文中所包含的代码仅是为了说明用途,所以牺牲了良好的编程惯例,如错误检测、异常处理和旨在简单性和简洁性的模块化。
为达到简洁性,登录模块根据凭证和密钥加到 Subject 的时间来选择缺省的凭证和密钥;从此时起即为
KerberosCredentials 中的时间戳记。
清单 1. 安装在 Subject
的对象类型
public class KerberosCredentials {
private Krb5Principal principal;
private Krb5TGT tgt;
private Krb5SecretKey skey;
private java.util.Date timestamp;
KerberosCredentials(Krb5Principal principal, Krb5TGT tgt);
KerberosCredentials(Krb5Principal principal, Krb5SecretKey skey);
KerberosCredentials(Krb5Principal principal, Krb5TGT tgt, Krb5SecretKey skey);
public Krb5Principal getPrincipal();
public Krb5TGT getTGT();
public Krb5SecretKey getSecretKey();
public boolean isNewerThan(KerberosCredentials that);
public boolean equals(Object o);
public int hashCode();
}
public class KerberosTGT extends KerberosCredentials {
public KerberosTGT(KerberosPrincipal principal, Krb5TGT tgt) {
super(principal, tgt);
}
public String getIssuer();
}
public class KerberosSecretKey extends KerberosCredentials {
public KerberosSecretKey(Krb5Principal principal, Krb5SecretKey skey) {
super(principal, skey);
}
}
|
更新 Subject
清单 2 说明了是如何在登录过程的提交阶段用登录模块更新 Subject
的。要更新的 Subject 是由 JAAS
基础架构在初始化登录模块时提供的。要加到 Subject
的对象(
principal 、
tgt 和
skey )来自登录阶段。注意 TGT
和密钥保存在私有的凭证集中。
清单 2. 更新 Subject
public boolean commit() throws LoginException {
java.security.AccessController.doPrivileged(
(new java.security.PrivilegedAction() {
public Object run() {
subject.getPrincipals().add(principal);
subject.getPrivateCredentials().add(tgt);
subject.getPrivateCredentials().add(skey);
}
});
}
|
把 JAAS 登录集成到 Java GSSAPI 中
Java GSSAPI Kerberos 机制请求一个上下文发起者的 TGT 从 Kerberos
票证许可服务(Kerberos Ticket-Granting
Service(TGS))为上下文的接受者获得一份服务票证。另一方面,上下文接受者需要它的密钥来解密来自发起者的服务票证,通过这种方式认证发起者。
一个把 JAAS Subject 看成是凭证和密钥的专用库的 Java GSSAPI
安全机制依靠 JAAS 登录模块来将那些凭证和服务密钥置入 JAAS
Subject。它从 Subject 获得这些凭证和密钥,并且如果 Subject
没有包含必要的凭证和密钥时就会失败。
清单 3 说明了 Java GSSAPI Kerberos 机制是如何访问 JAAS Subject
并获得 TGT 和密钥的。
getInitiatorCredential() 方法从 Subject
获得特定主体的 TGT。如果特定主体是空(null),它就获得缺省的
TGT。同样的,
getAcceptorCredential() 方法从 Subject
获得特定主体的密钥,并且如果特定主体是空(null)的话,它就返回缺省的密钥。
就这两个方法自身来说,它们都是短小而且简单的。它们各自将实际工作委托给
PrivilegedExceptionAction 类的
run()
方法,这个类在特许块中被调用。
getInitiatorCredential()
和
getAcceptorCredential() 方法分别使用
SubjectTgtTrawler 类和
SubjectSecretKeyTrawler
PrivilegedExceptionAction 类。
getInitiatorAndAcceptorCredentials()
方法返回一个复合的
KerberosCredentials
对象,该对象既包含服务主体的一个 TGT 又包含该主体的一个密钥。
如它们名称所暗示的,
SubjectTgtTrawler 和
SubjectSecretKeyTrawler 检查 Subject 来分别寻找 TGT
和密钥。它们在私有的(而非公用的)Subject
的凭证集中检查,这也正是登录模块安装这些凭证的地方。
为达到简单性和简洁性,
SubjectTgtTrawler 将根据 TGT 被加到 Subject 的时间来返回一个缺省的 TGT。如前面所讨论的,其它因素可以用做选择缺省的 TGT 的依据。
清单 3. 从 Subject 中获得对象
KerborosCredentials getInitiatorCredential(Krb5Principal principal) throws Exception {
KerberosTGT tgt = null;
try {
tgt = (KerberosTGT) java.security.AccessController.doPrivileged
(new SubjectTgtTrawler(principal));
} catch (PrivilegedActionException exc) {
throw (Exception) exc.getException();
}
if (tgt == null) {
return null;
}
if (principal == null) {
principal = tgt.getPrincipal();
}
return new KerberosTGT(principal, tgt);
}
KerborosCredentials getAcceptorCredential(Krb5Principal principal) throws Exception {
KerberosSecretKey key = null;
try {
key = (KerberosSecretKey)
java.security.AccessController.doPrivileged
(new SubjectSecretkeyTrawler(principal));
} catch (PrivilegedActionException exc) {
throw (Exception) exc.getException();
}
if (key == null) {
return null;
}
if (principal == null) {
principal = key.getPrincipal();
}
return new KerberosSecretKey(principal, key);
}
KerberosCredentials getInitiatorAndAcceptorCredentials(Krb5Principal principal)
throws Exception {
Krb5Credentials initiatorCreds = null;
Krb5Credentials acceptorCreds = null;
if ((acceptorCreds = getAcceptorCredential(principal)) == null) {
return null;
}
if (principal == null) {
principal = acceptorCreds.getPrincipal();
}
if ((initiatorCreds = getInitiatorCredential(principal)) == null) {
return null;
}
return new KerberosCredentials(principal, initiatorCreds.getTGT(),
acceptorCreds.getSecretKey());
}
/**
* Trawls the JAAS Subject for a TGT belonging to the specified
* principal.
* Returns default TGT if no principal specified.
*/
class SubjectTgtTrawler implements PrivilgedExceptionAction {
private Krb5Princpal principal;
SubjectTgtTrawler(Krb5Principal principal) {
this.principal = principal;
}
public Object run() throws Exception {
AccessControlContext context = AccessController.getContext();
Subject subject = Subject.getSubject(context);
if (subject == null) {
throw new Exception
("No Subject in the current AccessControlContext");
}
Object credential = null;
KerberosTGT retTgt = null;
KerberosTGT tempTgt = null;
String tgtIssuer = null;
if (principal != null) {
String realm = principal.getRealm();
tgtIssuer = "krb5tgt/" + realm + "@" + realm;
}
Iterator iterator =
subject.getPrivateCredentials().iterator();
while (iterator.hasNext()) {
credential = iterator.next();
if (!(credential instanceof KerberosTGT)) {
continue;
}
tempTgt = (KerberosTGT)credential;
if (principal == null) {
if (retTgt == null) {
retTgt = tempTgt;
} else if (tempTgt.isNewerThan(regTgt)) {
retTgt = tempTgt;
}
} else if (principal.equals(tempTgt.getPrincipal()) &&
tgtIssuer.equals(tempTgt.getIssuer())) {
retTgt = tempTgt;
break;
}
}
return retTgt;
}
}
/**
* Trawls the JAAS Subject for a secret key belonging to the specified
* principal.
* Returns default secret key if no principal specified.
*/
class SubjectSecretkeyTrawler implements PrivilegedExceptionAction {
// Code similar to that for SubjectTgtTrawler
}
|

 |

|
结论
一个 GSSAPI
登录接口既不是由标准托管的,也不是由典型的实现提供的。JAAS
登录基础架构可被利用来为 Java GSSAPI
提供一个集成的登录接口。可设计出一个用于把 Java GSSAPI
凭证和密钥装入 JAAS Subject 的 JAAS
登录模块。这样的登录模块通过在运行时将 Subject
作为凭证和密钥的专用库,而简化了 Java GSSAPI。
然而,只有 Java GSSAPI 应用程序遵照由 JAAS
硬性规定的编程模式时,该应用程序才能从集成的 JAAS
登录功能中受益。
参考资料
关于作者  | 
|  | Thomas Owusu
已经从事顾问软件工程师很多年了。他的工作包括在 IBM Hursley、IBM
Austin 和 Transarc Corporation 针对不同的技术而工作。他目前正在 IBM
Software Group 从事关于 Tivoli Java Security
软件产品的工作。您可以通过
towusu@us.ibm.com 与 Thomas
联系。
|
对本文的评价
|