我在通过SPNEGO从Web浏览器(Internet Explorer 11)到自定义Java应用程序服务器提供的Web服务的身份验证时遇到问题。
我可以使用自定义Java客户端应用程序将SPNEGO成功验证到同一Application Server。
定制Java客户端和应用程序服务器的实现细节可以在下面找到。
我怀疑来自Web浏览器的SPNEGO无法正常工作,因为:
a)Internet Explorer中的 token 是否为有效的SPNEGO token ?
Web浏览器提供的GSSAPI token 与Java客户端提供的GSSAPI token 不同,并且可能不是有效的SPNEGO / Kerberos token 。 Java客户端提供以“Negotiate YIMMQA ...”(确定)开头的授权标头,而Web浏览器提供以“Negotiate oYIMRz ...”开头的授权标头(可能不正确)。
和/或
b)服务器主体名称的格式
由于历史原因,Application Server正在使用实际上是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(通过Waffle)或JAAS + GSSAPI。两种实现都创建服务器接受的GSS token 。
生成的GSS / SPNEGO token 在Web服务请求(客户端)和响应(服务器)的标头中传输。
服务器正在使用Oids“1.3.6.1.5.5.2”(SPNEGO)和“1.2.840.113554.1.2.2”(Kerberos)。
使用自定义Java客户端进行测试(OK):
服务器能够通过一次握手来验证Java客户端。 Java客户端直接使用带有“Negotiate YIMMQA ...”的授权标头直接调用Web服务。在服务器上对Base64进行解码之后,gssapiData的长度为3140字节,并且对acceptSecContext()的调用成功。
如果我将gssapiData从此调用转换为字符串,并在其中搜索任何人类可读的内容,那么一开始我会找到“EXAMPLE.COM”和“user-DEV”。看起来像服务器正在使用的SPN,即Active Directory用户主体(“user-Dev@EXAMPLE.COM”)。
使用Internet Explorer 11进行测试(不确定):
来自浏览器的第一个调用的授权标题为空。我的服务器提示“协商”->确定。
来自浏览器的第二个调用具有一个授权标头,其开头为“协商YH4GBis ...”。 Base64解码后,gssapiData的长度为128个字节。显然,其中不包含服务凭单。
如果将gssapiData转换为字符串,则在中间找到字符“NTLMSSP”。我猜浏览器建议使用NTLM。我的服务器拒绝此呼叫。
来自浏览器的第三个调用具有一个以“Negotiate oYIMRz ...”开头的授权标头。对Base64进行解码后,gssapiData的长度为3147字节(非常接近Java客户端的长度)。
但是,当我的服务器对此执行一个acceptSecContext()时,它将引发错误。 “GSS异常:检测到 token 有缺陷(机制级别:GSS标头未找到正确的标签)。 ->不好。
这向我表明该 token 无效,或者我使用了错误的Oids来读取它。
如果我将gssapiData从此调用转换为字符串,则一开始我会找到“HTTP”和“APPSERVER.example.com”。这看起来像是以URL为基础构建的Kerberos服务主体名称(SPN)。 —>这向我建议我的Application Server应该以SPN的格式运行,例如“HTTP / APPSERVER.example.com”或“HTTP/appserver.example.com@EXAMPLE.COM”(第二种是该格式)我的Linux / FreeIPA配置使用)。
附带说明:在这个问题的焦点所在的Windows平台上,我没有创建/更改SPN或别名的权限,也没有尝试使用其他Web浏览器的权限。在我的Linux开发环境中,它可能会提供其他输入。 。 。
最佳答案
快速回答
需要两个修复程序:
1)Internet Explorer(IE)基于URL构建服务主体名称(SPN)。例如https://appserver.example.com/foo生成SPN“HTTP / APPSERVER.example.com”。
因此,必须在Active Directory中以上述格式设置适当的服务主体名称,作为应用程序服务器使用的用户主体名称(UPN)的别名。
和
2)来自Internet Explorer的 token 是有效的SPNEGO token ,但服务器上的GSS API不接受。
但是,通过对传入 token 进行一些简单的字符串操作,可以提取Kerbeos token ,GSS将接受该 token 并成功进行身份验证。
更长的答案
1)服务主体名称...
在此发布此问题之后,我们将2个SPN设置为服务器的UPN用户-Dev@EXAMPLE.COM的别名HTTP / APPSERVER.example.com和HTTP / APPSERVER。
服务器将继续使用UPN用户-Dev@EXAMPLE.COM运行。 (我最初认为服务器必须使用新的SPN之一运行是错误的。)
我的Java客户端现在可以使用新的SPN来获取Kerberos服务票证并创建已由我的服务器成功认证的 token 。
但是,来自Internet Explorer的 token 将继续被拒绝。
2)来自Internet Explorer的 token 与我的Java客户端...
我的Java客户端中的 token 开始如下:
协商YIIMdwYGKwYBBQUCoI...。
Base64解码,并表示为十六进制字节:
60 82 0C 77 06 06 2B 06 01 05 05 02 A0………
其中06 06 2B 06 01 05 05 02是SPNEGO OID 1.3.6.1.5.5.2。
Internet Explorer的 token 开始如下:
协商oYIMPjCCDDqgAwoBAaKCDDEEggwtYIIMKQYJKoZIhvcSAQIC
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…。
这是Spnego NegTokenTarg,因为它以“A1”开头。但是,java类sun.security.jgss.GSSHeader将拒绝所有不以“60”开头的GSS token 。
逐字节检查IE NegTokenTarg显示,在前21个字节之后,我有一系列字节与应用程序中的 token 非常接近:
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 02是Kerberos OID 1.2.840.113554.1.2.2
如果我通过丢弃原始 token 的前21个字节来提取此 token (或提供偏移量为21的gssContext.acceptSecContext(gssapiData,offset,gssapiData.length)),则GSS API能够读取新 token 并提取用户主体,从而验证来自Internet Explorer的请求。
下面的代码示例使用base64最终编码的授权字符串的字符串操作来实现相同功能:
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)Java GSS API的一个弱点:GSS不直接接受作为NegTokenTarg的SPNEGO token 。
要么
b)Internet Explorer和我的服务器之间的相互作用,导致IE发送NegTokenTarg,这不是GSS API期望的。
IE-服务器互动是:
1)来自IE的请求(无需协商)
2)拒绝我的服务器,进行协商
3)来自IE的第二个请求,带有协商标头+ token ,看起来像NTLM,而不是Kerberos。 ->这可能是导致问题的路由原因。
4)使用协商+ SPNEGO token 从我的服务器拒绝
5)来自IE的第三个请求,带有协商标头+ SPNEGO NegTokenTarg
背景信息:
我的应用程序服务器使用Java JAAS + GSS来实现Kerberos / Spnego功能。我的自定义客户端可以使用Java JAAS + GSS或Microsoft SSPI + Waffle。
我发现此Microsoft文档对理解SPNEGO token 的格式非常有帮助。
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代码包含大量注释,并链接到知识库文章和SME博客。
Chrome 码
https://cs.chromium.org/chromium/src/net/http/http_auth_handler_negotiate.cc?type=cs&l=142
FireFox代码
https://dxr.mozilla.org/mozilla-central/source/extensions/auth/nsAuthSSPI.cpp#98
关于java - SPNEGO身份验证可通过自定义Java客户端运行,但不能通过Web浏览器运行,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/51409652/