Java GSS API指南

2023/07/02

1. 概述

在本教程中,我们将介绍通用安全服务API(GSS API)以及我们如何在Java中实现它。我们将了解如何使用Java中的GSS API保护网络通信。

在此过程中,我们将创建简单的客户端和服务器组件,并使用GSS API保护它们。

2. 什么是GSS API?

那么,什么是通用安全服务API?GSS API为应用程序提供了一个通用框架,以可插拔的方式使用不同的安全机制,如Kerberos、NTLM和SPNEGO。因此,它有助于应用程序将自身直接与安全机制分离。

需要澄清的是,此处的安全性涵盖身份验证、数据完整性和机密性

2.1 为什么我们需要GSS API?

Kerberos、NTLM和Digest-MD5等安全机制在功能和实现方面大不相同。通常,支持其中一种机制的应用程序会发现切换到另一种机制非常困难。

这就是像GSS API这样的通用框架为应用程序提供抽象的地方。因此,使用GSS API的应用程序可以协商合适的安全机制并将其用于通信。所有这一切都无需实际实现任何特定于机制的细节。

2.2 GSS API的工作原理?

GSS API是一种基于令牌的机制,它的工作原理是在对等方之间交换安全令牌。这种交换通常发生在网络上,但GSS API对这些细节是不可知的。

这些令牌由GSS API的特定实现生成和处理,这些令牌的语法和语义特定于对等点之间协商的安全机制

截图-2019-08-12-at-12.08.43

GSS API的中心主题围绕着安全上下文,我们可以通过令牌交换在对等点之间建立这种上下文。我们可能需要在对等点之间多次交换令牌来建立上下文

一旦在两端成功建立,我们就可以使用安全上下文来安全地交换数据。这可能包括数据完整性检查和数据加密,具体取决于底层安全机制。

3. Java中的GSS API支持

Java支持GSS API作为包“org.ietf.jgss”的一部分,包名称可能看起来很奇怪,这是因为GSS API的Java绑定是在IETF规范中定义的。规范本身独立于安全机制。

Java GSS的一种流行安全机制是Kerberos v5。

3.1 Java GSS API

让我们尝试了解一些构建Java GSS的核心API:

  • GSSContext封装了GSS API安全上下文,并在上下文中提供可用的服务
  • GSSCredential封装了建立安全上下文所必需的实体的GSS API凭据
  • GSSName封装GSS API主体实体,它为底层机制使用的不同命名空间提供抽象

除了上述接口外,还有几个重要的类需要注意:

  • GSSManager充当其他重要GSS API类(如GSSName、GSSCredential和GSSContext)的工厂类
  • Oid表示通用对象标识符(OID),它们是GSS API中用于识别机制和名称格式的分层标识符
  • MessageProp包装属性以在诸如保护质量(QoP)和数据交换机密性等方面指示GSSContext
  • ChannelBinding封装可选的通道绑定信息,用于加强提供对等实体身份验证的质量

3.2 Java GSS安全提供程序

虽然Java GSS定义了用于在Java中实现GSS API的核心框架,但它并未提供实现。Java采用基于提供程序的可插拔实现来实现包括Java GSS在内的安全服务

可以有一个或多个在Java加密体系结构(JCA)中注册的此类安全提供程序。每个安全提供程序都可以实现一个或多个安全服务,例如Java GSS API和底层的安全机制。

JDK附带一个默认的GSS提供程序。但是,我们可以使用具有不同安全机制的其他特定于供应商的GSS提供程序。IBM Java GSS就是这样的提供商之一,我们必须向JCA注册此类安全提供程序才能使用它们。

此外,如果需要,我们可以使用可能的自定义安全机制实现我们自己的安全提供程序。但是,这在实践中几乎不需要。

4. GSS API实例

现在,我们将通过示例了解Java GSS的实际应用。我们将创建一个简单的客户端和服务器应用程序。在GSS中,客户端通常被称为发起者,服务器被称为接受者。我们将在下面使用Java GSS和Kerberos v5进行身份验证。

4.1 客户端和服务器的GSS上下文

首先,我们必须在应用程序的服务器端和客户端建立一个GSSContext

让我们首先看看如何在客户端执行此操作:

GSSManager manager = GSSManager.getInstance();
String serverPrinciple = "HTTP/localhost@EXAMPLE.COM";
GSSName serverName = manager.createName(serverPrinciple, null);
Oid krb5Oid = new Oid("1.2.840.113554.1.2.2");
GSSContext clientContext = manager.createContext(serverName, krb5Oid, (GSSCredential)null, GSSContext.DEFAULT_LIFETIME);
clientContext.requestMutualAuth(true);
clientContext.requestConf(true);
clientContext.requestInteg(true);

这里发生了很多事情,让我们分解一下:

  • 我们首先创建GSSManager的实例
  • 然后我们使用这个实例来创建GSSContext,并传递:
    • 表示服务器主体的GSSName,请注意此处的Kerberos特定主体名称
    • 要使用的机制的Oid,这里是Kerberos v5
    • 发起者的凭据,此处为null表示将使用默认凭据
    • 已建立上下文的生命周期
  • 最后,我们为相互认证、机密性和数据完整性准备上下文

同样,我们必须定义服务器端上下文:

GSSManager manager = GSSManager.getInstance();
GSSContext serverContext = manager.createContext((GSSCredential) null);

如我们所见,这比客户端上下文简单得多。这里唯一的区别是我们需要我们用作null的接受者的凭证。和以前一样,null表示将使用默认凭据

4.2 GSS API认证

虽然我们已经创建了服务器端和客户端GSSContext,但请注意,它们在此阶段尚未建立。

要建立这些上下文,我们需要交换特定于指定安全机制(即Kerberos v5)的令牌:

// On the client-side
clientToken = clientContext.initSecContext(new byte[0], 0, 0);
sendToServer(clientToken); // This is supposed to be send over the network
		
// On the server-side
serverToken = serverContext.acceptSecContext(clientToken, 0, clientToken.length);
sendToClient(serverToken); // This is supposed to be send over the network
		
// Back on the client side
clientContext.initSecContext(serverToken, 0, serverToken.length);

这最终使上下文在两端建立:

assertTrue(serverContext.isEstablished());
assertTrue(clientContext.isEstablished());

4.3 GSS API安全通信

现在,我们已经在两端建立了上下文,我们可以开始发送具有完整性和机密性的数据

// On the client-side
byte[] messageBytes = "Tuyucheng".getBytes();
MessageProp clientProp = new MessageProp(0, true);
byte[] clientToken = clientContext.wrap(messageBytes, 0, messageBytes.length, clientProp);
sendToClient(serverToken); // This is supposed to be send over the network
       
// On the server-side 
MessageProp serverProp = new MessageProp(0, false);
byte[] bytes = serverContext.unwrap(clientToken, 0, clientToken.length, serverProp);
String string = new String(bytes);
assertEquals("Tuyucheng", string);

这里发生了一些事情,让我们分析一下:

  • MessageProp被客户端用来设置wrap方法和生成token
  • 方法wrap还添加了数据的加密MIC,MIC被捆绑为令牌的一部分
  • 该令牌被发送到服务器(可能通过网络调用)
  • 服务器再次利用MessageProp设置unwrap方法并取回数据
  • 此外,方法unwrap验证接收数据的MIC,确保数据完整性

因此,客户端和服务器能够以完整性和机密性交换数据。

4.4 示例的Kerberos设置

现在,通常期望像Kerberos这样的GSS机制从现有的Subject获取凭证。这里的类Subject是一个JAAS抽象,表示像人或服务这样的实体。这通常在基于JAAS的身份验证期间填充。

但是,对于我们的示例,我们不会直接使用基于JAAS的身份验证。我们将让Kerberos直接获取凭据,在我们的例子中使用keytab文件。有一个JVM系统参数可以实现这一点:

-Djavax.security.auth.useSubjectCredsOnly=false

但是,Sun Microsystem提供的默认Kerberos实现依赖于JAAS来提供身份验证。

这听起来可能与我们刚才讨论的相反。请注意,我们可以在应用程序中显式使用JAAS来填充Subject。或者将其留给底层机制直接进行身份验证,无论如何它都使用JAAS。因此,我们需要为底层机制提供一个JAAS配置文件:

com.sun.security.jgss.initiate  {
  com.sun.security.auth.module.Krb5LoginModule required
  useKeyTab=true
  keyTab=example.keytab
  principal="client/localhost"
  storeKey=true;
};
com.sun.security.jgss.accept  {
  com.sun.security.auth.module.Krb5LoginModule required
  useKeyTab=true
  keyTab=example.keytab
  storeKey=true
  principal="HTTP/localhost";
};

此配置非常简单,我们已将Kerberos定义为发起者和接受者所需的登录模块。此外,我们还配置为使用keytab文件中的相应主体。我们可以将此JAAS配置作为系统参数传递给JVM:

-Djava.security.auth.login.config=login.conf

在这里,假设我们可以访问Kerberos KDC。在KDC中,我们已经设置了所需的主体并获得了要使用的keytab文件,假设为“example.keytab”。

此外,我们需要指向正确KDC的Kerberos配置文件:

[libdefaults]
default_realm = EXAMPLE.COM
udp_preference_limit = 1
[realms]
EXAMPLE.COM = {
    kdc = localhost:52135
}

这个简单的配置定义了一个在端口52135上运行的KDC,默认realm为EXAMPLE.COM。我们可以将其作为系统参数传递给JVM:

-Djava.security.krb5.conf=krb5.conf

4.5 运行示例

要运行该示例,我们必须使用上一节中讨论的Kerberos工件

此外,我们需要传递所需的JVM参数:

java -Djava.security.krb5.conf=krb5.conf \
  -Djavax.security.auth.useSubjectCredsOnly=false \
  -Djava.security.auth.login.config=login.conf \
  cn.tuyucheng.taketoday.jgss.JgssUnitTest

这足以让Kerberos使用来自keytab和GSS的凭据执行身份验证以建立上下文。

5. 现实世界中的GSS API

虽然GSS API承诺通过可插拔机制解决大量安全问题,但很少有用例得到更广泛的采用:

  • 它在SASL中被广泛用作安全机制,尤其是在Kerberos是选择的底层机制的情况下。Kerberos是一种广泛使用的身份验证机制,尤其是在企业网络中。利用基于Kerberos的基础架构对新应用程序进行身份验证非常有用。因此,GSS API很好地弥补了这一差距。
  • 它还与SPNEGO结合使用,以在事先不知道安全机制的情况下协商安全机制。就此而言,SPNEGO在某种意义上是GSS API的一种伪机制。这在所有现代浏览器中得到广泛支持,使它们能够利用基于Kerberos的身份验证。

6. GSS API比较

GSS API在以可插拔方式为应用程序提供安全服务方面非常有效。但是,这并不是在Java中实现这一目标的唯一选择。

让我们了解Java还提供了什么,以及它们与GSS API的比较:

  • Java安全套接字扩展(JSSE):JSSE是Java中的一组包,用于实现Java的安全套接字层(SSL)。它提供数据加密、客户端和服务器身份验证以及消息完整性。与GSS API不同,JSSE依赖于公钥基础设施(PKI)来工作。因此,GSS API比JSSE更灵活、更轻量。
  • Java简单身份验证和安全层(SASL):SASL是用于互联网协议的身份验证和数据安全框架,可将它们与特定的身份验证机制分离。这在范围上类似于GSS API。但是,Java GSS通过可用的安全提供程序对底层安全机制的支持有限。

总的来说,GSS API在以机制不可知的方式提供安全服务方面非常强大。但是,Java中对更多安全机制的支持将进一步采用它。

7. 总结

综上所述,在本教程中,我们了解了GSS API作为安全框架的基础知识。我们介绍了GSS的Java API并了解了如何利用它们,在此过程中,我们创建了简单的客户端和服务器组件,它们执行相互身份验证并安全地交换数据。

此外,我们还了解了GSS API的实际应用以及Java中可用的替代方案。

与往常一样,本教程的完整源代码可在GitHub上获得。

Show Disqus Comments

Post Directory

扫码关注公众号:Taketoday
发送 290992
即可立即永久解锁本站全部文章