在 Java 中验证、使用和重复使用密码和 Kerberos 凭据

Authenticating, using and reusing password and Kerberos credentials in Java

警告:这是一个非常长的post,只是因为我不知道如何用其他方式解释我们的具体问题(抱歉)。

简短版本:

我们正在构建一个非常模块化的 Java 应用程序套件(客户端和服务器端)。一些客户端必须针对服务器进行身份验证,而服务器可以针对不同类型的用户存储进行身份验证。服务器还可以代表其经过身份验证的用户调用其他服务器,即使用他们的凭据。到目前为止,使用简单的 username/password 凭据效果很好,但现在我们必须有选择地通过 Kerberos(以及后来的其他身份验证系统)支持 SSO。到目前为止,我们失败了(部分惨败)。

长版:

我们用于处理主体的中央 Java 库称为用户访问层 (UAL)。该库由客户端和服务器应用程序使用,并提供三种类型的功能:

  1. 根据凭据对用户进行身份验证(导致身份验证失败或有关用户的基本信息,即至少一个登录名或 ID)
  2. 执行主体查询
  3. 如果后端支持,则在用户存储中执行主体修改

(2) 和 (3) 可以使用调用者指定的凭据或(如果后端支持)作为技术用户执行。

对用户存储的实际访问由配置的后端处理。我们提供了许多后端(LDAP、WebDAV、自定义 JDBC 数据库、自定义 XML 文件),它们都可以通过一个统一的配置文件进行配置,通常命名为 useraccess.xml。该文件定义应使用哪个后端(或多个后端)以及如何配置它,例如LDAP 后端或数据库 URL 的 LDAP 服务器和结构数据以及数据库后端的数据库用户凭据。所有后端都实现相同的接口,因此应用程序代码独立于为特定安装配置的后端。

在我们的产品中,UAL 被两种不同类型的应用程序使用:

  1. 客户端(命令 line/desktop 客户端和用户在浏览器中打开的 Web 前端应用程序)。这些应用程序使用 UAL 来执行主体查询(例如,我们的文件浏览器应用程序在修改 WebDAV 资源的 ACL 时)或主体修改(我们有一个基于 Web 的用户管理应用程序)。根据应用程序,他们可以通过以下方式之一获取用于 UAL 操作的凭证: 一种。用户在调用应用程序时在命令行上提供凭据 b.应用程序打开一个对话框并提示用户在服务器访问需要时输入凭据 C。用户首次访问 Web 前端应用程序时显示的登录屏幕

  2. 服务器。它使用 UAL 来: 一种。根据通过使用的协议提供的凭据对用户进行身份验证(例如 HTTP/WebDAV 或 SOAP/WSSE) b.根据用户(或用户组)的属性对用户进行授权 C。代表客户执行 UAL 操作 (queries/modifications) d.代表客户端调用其他服务器,即将用户的凭据传递给另一台服务器(例如通过 HTTP/WebDAV 或 SOAP/WSSE)

直到现在,我们所有的凭据都是用户 name/password 对,只要我们确保将这些凭据保存在用户的 session 中,以便以后在必要时使用它们访问另一个,就可以正常工作服务器。我们可以做到,每次检索凭据或将其传递给服务器的调用都会通过我们的一些代码,这些代码可以 store/provide 必要的凭据。

由于需要通过 Kerberos 支持 SSO,一切都变得更加复杂。我们已经尝试了几种方法并多次修改了我们的代码库,但每次我们认为走在正确的轨道上时,我们都会意识到我们忽略了一个地方,它无法按我们预期的方式工作。

让事情如此混乱的是我们必须以几种不同的方式处理凭​​据。这些是我们必须向服务器提供凭据的一些方式:

  1. 访问 HTTP/WebDAV 服务器时通过 Apache HttpClient
  2. 访问 Web 服务时通过 SOAP/WSSE
  3. 访问 LDAP 服务器时通过 JNDI(在 UAL 中)

以及我们必须从客户端接收和验证凭据的一些方法:

  1. 作为 Apache Jackrabbit 中的登录模块
  2. 在我们的 JAX-WS 网络服务之一中收到 SOAP/WSSE 消息时

我们的一个非常常见的用例如下:

  1. 客户端通过 SOAP 调用服务器 A,提供凭据
  2. 服务器 A 从 SOAP 消息中检索凭据并验证它们(如果它们无效(身份验证错误)或用户无权执行所需操作(授权错误),则响应错误)
  3. 服务器A然后调用WebDAV serer B 代表用户,传递用户的凭据,以便可以在该用户的权限下执行 WebDAV 操作(并使用该用户的名称和其他属性)
  4. 服务器 B 从 HTTP 消息中检索凭据并验证它们
  5. 服务器 B 然后对用户存储 C 执行主体查询,将用户的凭据传递到用户存储(取决于配置的 UAL 后端,这可能只是将用户名和密码与用户存储中的用户名和密码进行比较 XML 文件,或使用它们建立 LDAP 连接并以该用户身份查询 LDAP 存储)

问题是:互联网上似乎很少有信息可以帮助解决我们的具体问题。对于初学者来说,大多数资源只是简单地描述了如何为容器设置 JAAS 配置文件,以让其 Web 应用程序执行用户身份验证。但是我们的代码必须在客户端和服务器上 运行 并使用一个配置文件来指定用于身份验证和主体的用户存储配置 queries/modifications。此外,这必须使用相同的代码,使用针对各种用户存储(其中一些是自定义编写的)的用户 name/password 凭据,以及针对 LDAP 服务器的 Kerberos(以及后来的其他)票证。最后,拥有一个可靠地告诉我们用户提供了正确凭据的身份验证库是不够的(许多 JAAS 登录模块似乎都这样做),因为我们实际上必须保留用户的凭据以供进一步调用。

由于作为我们核心组件之一的基础的 Apache Jackrabbit 需要我们配置 JAAS 登录模块,并且已经有用于 LDAP 和 Kerberos 身份验证的 JAAS 登录模块,我们已成功修改 UAL 以执行所有通过 JAAS 执行其身份验证任务。为此,我们为自定义后端编写了两个登录模块,并且我必须实现自己的 LDAP 登录模块,因为默认的 JAAS 登录模块会根据 LDAP 服务器成功验证用户身份,但随后会丢弃用户的凭据和 LDAP 上下文,因此我们无法使用相同的凭据执行进一步的 LDAP 查询。我们自己的所有登录模块都将凭证存储在经过身份验证的主体的私有凭证集中,这也是 JAAS 的默认 Kerberos 登录模块所做的。使用生成的主题,我们可以执行用户查询。这适用于我们所有的后端以及密码和 Kerberos 票证。

我们还能够修改我们的 SOAP 服务以从 SOAP 消息中提取凭据。对于密码凭据,我们可以在身份验证回调请求凭据时简单地将它们传递给 JAAS。但是,似乎没有办法对 Kerberos 票证执行相同的操作。相反,我们的 SOAP 服务目前自行处理这些问题,通过必要的 GSS API 调用传递它们以验证票证,为 SOAP 服务的配置服务用户检索匹配的票证,并创建包含凭据和用户的主题信息。使用这个主题,我们可以通过 UAL 执行 queries/modifications。然而,这不仅意味着我们的 SOAP 服务在验证 Kerberos 票证时完全绕过 UAL,它们还需要在自己的配置中使用一些 Kerberos 配置数据(服务用户名、领域和密钥表文件),除了 useraccess.xml 已经包含相同的数据(但通用 UAL 客户端不能直接访问,因为这些设置特定于 UAL LDAP/Kerberos 后端)。显然,当我们添加对其他基于票证的身份验证方法的支持时,情况只会变得更糟,并且除了实际处理用户存储访问的 UAL 后端之外,还必须在每个 SOAP 服务中手动实现它们。

最糟糕的是,我们仍然不确定如何将所有这些放入我们基于 Jackrabbit 的 WebDAV 服务器中。 Jackrabbit 需要一个登录模块,它应该可以很好地处理用户 name/password 凭据,但(据我们所知)无法处理 Kerberos 票证。我们可能可以从 HTTP headers 手动获取这些信息,但这不会阻止 Jackrabbit 调用登录模块和登录模块失败,因为它仍然会要求输入密码,然后在没有密码的情况下无法针对 Kerberos 进行身份验证.

我无法摆脱这样一种感觉,即我们的方法或(很可能)我们对所有这些部分应如何组合在一起的理解存在根本性缺陷,但我们在网上找不到的任何内容都足以说明我们的要求我们做错了什么(或者,更重要的是,应该如何做才能使它正确)。由于即使描述我们的问题也很复杂,所以我到目前为止都避免 post 将其作为一个问题,但如果你已经读到这里并且可以给我们任何关于如何解决这个问题的指示,你可以拯救我们从几周的挫折中。

您可以在

之后删除所有内容

Until now, all our credentials were user name/password pairs, which worked fine as long as we made sure to keep these credentials in the user's session where necessary to later use them for accessing another server. We could do that every call that retrieved credentials or passed them to a server went through some of our code which could store/provide the necessary credentials.

您的问题很简单:您需要使用 Kerberos 的凭据委派。基本技术非常简单。由于您在这里有多个问题区域,我建议将它们分解以解决您的问题:

  1. OS/environment Kerberos 凭据委派配置
  2. 如何申请可委托服务令牌
  3. 如何从客户端检索委托的 TGT
  4. 如何重用来自客户端的 TGT 并请求另一个服务令牌

由于您的入站通道是 HTTP,以下是答案:

  1. 如果您在 Active Directory 环境中,请请求您的管理员在将接受 GSS 安全上下文的计算机帐户上设置 "trusted for delegation"。
  2. 这有点棘手,因为它取决于客户端语言和库。在Java中设置GSSContext.requestCredDeleg(true)就这么简单。其余部分详述。
  3. 检查我的 Tomcat SPNEGO/AD Authenticator library 代码,我正在提取客户端的 TGT 并将其存储在由 HttpServletRequest#getPrincipal 方法提供的 Principal 实现中。
  4. 假设您的后端客户端库正确支持 GSS-API,基本上有两种方法:(1) 显式凭证使用:将委托的 GSSCredential 实例传递给客户端库它应该休息。 (2) 隐式:将您的客户端操作包装在 PrivilegedAction 中,用私有 GSSCredential 构造一个 Subject 并用两者调用 Subject.doAs。 JAAS 将使用来自线程主题的隐式凭证并代表您的用户执行操作。

看来您还没有达到第 2 点或第 3 点。