当前位置: 首页 > 知识库问答 >
问题:

SPNEGO身份验证可从自定义Java客户端工作,但不能从Web浏览器工作

易阳朔
2023-03-14

我在通过SPNEGO从Web浏览器(Internet Explorer 11)到自定义Java应用程序服务器提供的Web服务进行身份验证时遇到问题。

我可以使用SPNEGO成功地通过自定义Java客户端应用程序对同一应用程序服务器进行身份验证。

下面可以找到定制Java客户端和应用程序服务器的实现详细信息。

我怀疑网络浏览器中的SPNEGO无法正常工作,因为:

a)Internet Explorer中的代币是有效的SPNEGO代币吗?

Web浏览器提供的GSSAPI令牌与我的Java客户端提供的令牌不同,可能不是有效的SPNEGO/Kerberos令牌。Java客户端提供以“协商YIMMQA...”开头的授权标头(确定),而Web浏览器提供以“协商oYIMRz...”开头的授权标头(可能不确定)。

和/或

b) 服务器主体名称的格式

由于历史原因,应用程序服务器运行时使用的服务主体名称实际上是Microsoft Active Directory用户主体(格式=“”user@DOMAIN),而我强烈怀疑Web浏览器SPNEGO实现使用请求的URL来构建服务主体名称。事实上,这正是我的自定义Java客户端在Linux上针对Linux后端运行时所做的。

实施细节:

Java应用程序服务器在Windows Server 2012上运行。Kerberos/SPNEGO实现是纯Java JAAS GSSAPI。

Java客户端在Windows(7/10)上运行,可以配置为使用Java SSPI(通过华夫格)或JAAS GSSAPI。这两种实现都创建服务器接受的GSS令牌。

生成的GSS/SPNEGO令牌在Web服务请求(客户端)和响应(服务器)的标头中传输。

服务器正在使用Oids1.3.6.15.5.2(SPNEGO)和1.2.840.113554.1.2.2(Kerberos)。

使用自定义Java客户端进行测试(OK):

服务器能够在一次握手中对Java客户端进行身份验证。Java客户端直接调用带有授权标头的Web服务,该授权标头以“协商YIMMQA...”开头。在服务器上进行Base64解码后,gssapiData 3140字节长,并且调用接受SecContext()成功。

如果我将此调用中的gssapiData转换为字符串,并在其中搜索任何人类可读的内容,那么一开始我会找到“EXAMPLE.COM”和“user DEV”。这看起来像服务器正在使用的SPN,一个Active Directory用户主体(“用户-Dev@EXAMPLE.COM”).

使用Internet Explorer 11进行测试(不可以):

来自浏览器的第一个调用有一个空的授权标头。我的服务器提示“协商”-

来自浏览器的第二次调用有一个以“协商YH4GBis...”开头的授权标头。一旦Base64解码,gssapiData就有128字节长。显然,这不包含服务票证。

如果我将gssapiData转换为字符串,那么在中间我会找到字符“NTLMSSP”。我猜浏览器建议使用NTLM。我的服务器拒绝此呼叫。

来自浏览器的第三次调用有一个以“协商oYIMRz...”开头的授权标头一旦Base64解码,gssapiData的长度为3147字节(非常接近Java客户端的长度)。

但是,当我的服务器对此执行acceptSecContext()时,它会抛出错误。“GSS异常:检测到有缺陷的令牌(机制级别:GSS标头未找到正确的标记)-

这表明令牌无效,或者我使用了错误的OID来读取它。

如果我将此调用中的gssapiData转换为字符串,那么在开始时,我会找到“HTTP”和“APPSERVER.example.com”。这看起来像是使用URL作为基础构建的Kerberos服务主体名称(SPN)-

附带说明:在Windows平台上,也就是本问题的重点,我无权创建/更改SPN或别名,也无权尝试不同的Web浏览器。在我的Linux开发环境中,我有,这可能会提供额外的输入...

共有1个答案

姬魁
2023-03-14

需要两个修复:

1)Internet Explorer(IE)基于URL构建服务主体名称(SPN)。例如,https://appserver.example.com/foo导致SPN“HTTP/APPSERVER.example.com”。

因此,必须以上述格式在Active Directory中设置正确的服务主体名称,作为应用程序服务器使用的用户主体名称(UPN)的别名。

2) 来自Internet Explorer的令牌是有效的SPNEGO令牌,但服务器上的GSS API不接受该令牌。

然而,通过对传入令牌进行一些简单的字符串操作,可以提取出一个Kerbeos令牌,GSS将接受并成功进行身份验证。

1)服务主体名称...

在此发布此问题后,我们设置了2个SPN HTTP/APPSERVER。实例com和HTTP/APPSERVER作为服务器UPN用户的别名-Dev@EXAMPLE.COM.

服务器继续使用UPN用户运行-Dev@EXAMPLE.COM.(我最初认为服务器必须运行其中一个新的SPN是错误的。)

我的Java客户端现在能够使用新的SPN来获取Kerberos服务票证并创建由我的服务器成功验证的令牌。

但是,来自Internet Explorer的代币继续被拒绝。

2)来自Internet Explorer vs我的Java客户端的代币...

我的Java客户端的令牌是这样开始的:

谈判Yimdwygkwybqucoi。。。。

Base64已解码,并表示为十六进制字节:

60 82 0C 77 06 06 2B 06 01 05 02 A0………

其中06 06 2B 06 01 05 05 02为SPNEGO OID 1.3.6.1.5.5.2。

Internet Explorer中的令牌如下启动:

协商Oyimpjccddqgawobaakcdeeggwtyiimkyjkozihvcsaqic

Base64已解码,并表示为十六进制字节:

A1 82 0C 3E 30 82 0C 3A A0 03 0A 01 01 A2 82 0C 31 04 82 0C 2D 60 82 0C 29 06 09 2A 86 48 86 F7 12 01 02 02......

这是一个以“A1”开头的Spnego NigTokenTarg。但是,GSSHeadersun.security.jgss.java类将拒绝任何不以“60”开头的GSS令牌。

逐字节检查IE NegTokenTarg显示,在前21个字节之后,我有一系列字节,非常接近我的应用程序中的令牌:

60 82 0C 29 06 09 2A 86 48 86 F7 12 01 02 02......

其中06 09 2A 86 48 86 F7 12 01 02 02KerberosOID1.2.840.113554.1.2.2

如果我通过丢弃原始令牌的前21个字节来提取此令牌(或提供偏移量为21的gssContext.acceptSecContext(gssapiData,offset,gssapiData.length)),那么GSS API能够读取新令牌,并提取用户主体,从而验证来自Internet Explorer的请求。

下面的代码示例使用base64 endcoded授权字符串的字符串操作来实现相同的功能:

String auth =req.headers("Authorization");
if ( auth != null && auth.startsWith("Negotiate ")) {
    //smells like an SPNEGO request, so get the token from the http headers
    String authBody = auth.substring("Negotiate ".length());
    if (authBody.startsWith("oY")) {
        // This is a NegTokenTarg from IE, which GSS API does not properly handle.
        // However if we chop of the first (28) chars, we find a Kerberos Token starting with "60 82 0C" that GSS can handle.            
        authBody=authBody.substring(authBody.indexOf("YI", 2));
     }

     try {                 
         byte gssapiData[] = Base64.getDecoder().decode(authBody);               
         gssContext = initGSSContext(MyUtils.SPNEGOOID, MyUtils.KRB5OID);
         byte token[] = gssContext.acceptSecContext(gssapiData, offset, gssapiData.length);


         ..etc.

总之,我认为我们

a)一个JavaGSS API弱点:GSS不直接接受一个SPNEGO令牌,它是一个NegTokenTarg。

b) Internet Explorer和我的服务器之间的相互作用,导致IE发送NegTokenTarg,这是GSS API所不期望的。

IE-服务器交互是:

1) IE请求(无协商)

2) 拒绝我的服务器,使用协商

3) 来自IE的第二个请求,协商头令牌看起来像NTLM,而不是Kerberos--

4) 使用协商SPNEGO令牌从我的服务器拒绝

5)来自IE的第三个请求,使用协商标头SPNEGO NigTokenTarg

背景信息:

我的应用服务器使用JavaJAAS GSS来Kerberos/Spnego功能。我的自定义客户端可以使用JavaJAAS GSS或Microsoft SSPI Waffle。

我发现这篇Microsoft文档对理解SPNEGO令牌的格式非常有帮助。

https://msdn.microsoft.com/en-us/library/ms995330.aspx

这篇博客旨在了解如何“处理”负十进制字节。(数字-128到-1转换为128到255)。

http://sketchytech.blogspot.com/2015/11/bytes-for-beginners-representation-of.html

由于我的目标最终用户的公司标准浏览器是Internet Explorer,没有现实的选择来使用“更好”的东西,在谷歌上搜索时,我发现了Chromium和Firefox的SPN处理代码。链接如下。Chromium代码有大量评论和知识库文章和中小企业博客的链接。

铬柱代码

https://cs.chromium.org/chromium/src/net/http/http_auth_handler_negotiate.cc?type=cs

火狐代码

https://dxr.mozilla.org/mozilla-central/source/extensions/auth/nsAuthSSPI.cpp#98

 类似资料:
  • 我用keytool创建了一个密钥对,用于客户端身份验证。从这个文件中,我将公钥导出为服务器的证书,以对客户端进行身份验证。 客户端设置: 将服务器证书加载到信任库文件中,并将密钥库文件用作密钥库。当我通过SSLContext代码和使用Apache HttpClient加载信任库和密钥库时,客户端工作: ssl输出显示客户端呈现证书链。使用SoapUI设置密钥库也很好。 我的问题是:通过vm参数(不

  • 我正在开发一个Spotify应用程序,我想获得令牌。 我做错了什么? 真的有办法使用JavaScript从静态HTML文件中使用Spotify API吗?

  • 我正在将应用程序的安全性迁移到Spring Security4.0。我的要求是身份验证应该是JAAS身份验证,自动化数据将从数据库中提取。所以我已经编写和自定义了身份验证提供程序。但我的问题是Spring没有将身份验证请求委托给我的自定义身份验证提供程序。 代码如下 web.xml条目 调用堆栈

  • 问题内容: 我在Glassfish之上创建了一个JAX-WS Web服务,它需要基本的HTTP身份验证。 现在,我想为该Web服务创建一个独立的Java应用程序客户端,但是我不知道如何传递用户名和密码。 它可以与Eclipse的Web Service资源管理器一起使用,并检查连接线,我发现了这一点: 如何使用Java代码在此“授权”标头中传递用户名和密码?是散列还是类似的东西?什么是算法? 没有涉

  • 我遵循了为keycloak(版本4.8.3)设置自定义身份验证器spi的演练。我几乎只使用从这里得到的示例代码。我只更改了以便能够编译项目并使用部署它。而且它起作用了...我可以在keycloak中配置新的身份验证流,更新浏览器流并设置所需的动作。但是,如果我想在应用程序中使用新的身份验证,我会得到以下消息:。并且在控制台中得到以下输出: 我在github上查找了文件DefaulTauthenti

  • 我不熟悉SSL和证书。我一直在做关于客户端证书认证的研究。我看过这个和wiki。 因此,如果我必须为我的B2B REST服务实现客户端证书身份验证解决方案,我应该执行以下操作 要求客户端生成自己的私钥,并为其公钥生成证书(CA 颁发?)。通过电子邮件或 USB 闪存盘发送该证书。 在服务器端将客户端的公共证书导入信任存储区并启用客户端身份验证 在握手期间,客户端会出示其证书并进行身份验证,因为服务