use*_*ame 6 c# security ntlm sspi windows-authentication
我有一个简单的HTTP服务器,使用Negotiate协议对客户端进行身份验证.它使用SSPI调用来获取服务器凭据并建立安全上下文.服务器位于域中并代表域用户运行.一切正常,如果我在控制台模式下启动服务器,我将收到HTTP 200响应.但是,当我将其作为服务运行时,我收到SEC_E_INVALID_HANDLE错误.以下是在控制台模式下启动时发生的情况:
1.Client发送HTTP Get请求http:// localhost:8082
2.Server响应WWW-Authenticate:Negotiate header.
3.Client发送Authorization标头并包含以下数据:
60 73 06 06 2B 06 01 05 05 02 A0 69 30 67 A0 30 `s..+..... i0g 0
30 2E 06 0A 2B 06 01 04 01 82 37 02 02 0A 06 09 0...+....7.....
2A 86 48 82 F7 12 01 02 02 06 09 2A 86 48 86 F7 *H÷......*H÷
12 01 02 02 06 0A 2B 06 01 04 01 82 37 02 02 1E ......+....7...
A2 33 04 31 4E 54 4C 4D 53 53 50 00 01 00 00 00 ¢3.1NTLMSSP.....
97 B2 08 E2 04 00 04 00 2D 00 00 00 05 00 05 00 ².â....-.......
28 00 00 00 06 01 B1 1D 00 00 00 0F 50 41 43 45 (.....±.....PACE
4D 42 4C 41 48 MBLAH
Run Code Online (Sandbox Code Playgroud)
4.Server响应HTTP 401错误并协商标头提示继续:
A1 81 CE 30 81 CB A0 03 0A 01 01 A1 0C 06 0A 2B ¡Î0Ë ....¡...+
06 01 04 01 82 37 02 02 0A A2 81 B5 04 81 B2 4E ....7...¢µ.²N
54 4C 4D 53 53 50 00 02 00 00 00 08 00 08 00 38 TLMSSP.........8
00 00 00 15 C2 89 E2 B0 3B BE 20 45 33 FD 92 80 ....Ââ°;¾ E3ý
04 E7 01 00 00 00 00 72 00 72 00 40 00 00 00 06 .ç.....r.r.@....
01 B1 1D 00 00 00 0F 42 00 4C 00 41 00 48 00 02 .±.....B.L.A.H..
00 08 00 42 00 4C 00 41 00 48 00 01 00 0A 00 50 ...B.L.A.H.....P
00 41 00 43 00 45 00 4D 00 04 00 10 00 62 00 6C .A.C.E.M.....b.l
00 61 00 68 00 2E 00 63 00 6F 00 6D 00 03 00 1C .a.h...c.o.m....
00 50 00 61 00 63 00 65 00 6D 00 2E 00 62 00 6C .P.a.c.e.m...b.l
00 61 00 68 00 2E 00 63 00 6F 00 6D 00 05 00 10 .a.h...c.o.m....
00 62 00 6C 00 61 00 68 00 2E 00 63 00 6F 00 6D .b.l.a.h...c.o.m
00 07 00 08 00 5D B3 C5 9A 0F 17 D1 01 00 00 00 .....]³Å..Ñ....
00 .
Run Code Online (Sandbox Code Playgroud)
5.Client发送授权头:
A1 77 30 75 A0 03 0A 01 01 A2 5A 04 58 4E 54 4C ¡w0u ....¢Z.XNTL
4D 53 53 50 00 03 00 00 00 00 00 00 00 58 00 00 MSSP.........X..
00 00 00 00 00 58 00 00 00 00 00 00 00 58 00 00 .....X.......X..
00 00 00 00 00 58 00 00 00 00 00 00 00 58 00 00 .....X.......X..
00 00 00 00 00 58 00 00 00 15 C2 88 E2 06 01 B1 .....X....Ââ..±
1D 00 00 00 0F C0 BD 0C 5B F5 F9 35 FE 78 6D 08 .....À½.[õù5þxm.
BF 7B D9 CC E3 A3 12 04 10 01 00 00 00 F5 17 A7 ¿{ÙÌã£.......õ.§
50 2D 22 9A 84 00 00 00 00 P-"....
Run Code Online (Sandbox Code Playgroud)
6.Server响应HTTP 200并协商头:
A1 1B 30 19 A0 03 0A 01 00 A3 12 04 10 01 00 00 ¡.0. ....£......
00 43 87 E0 88 C1 36 E3 A9 00 00 00 00 .CàÁ6ã©....
Run Code Online (Sandbox Code Playgroud)
现在,如果我将应用程序作为服务运行,我将获得几乎相同的响应,但是在步骤#6中AcceptSecurityContext将失败并返回SEC_E_INVALID_HANDLE错误.我想知道如果我运行相同的应用程序并指定相同的用户作为服务登录标识,为什么会失败?它可能以某种方式与会话0隔离有关吗?还有一种方法可以更好地排除故障,我没有在事件查看器中看到任何错误消息,并且无效的句柄错误没有说明丢失的内容.
以下是要进行身份验证的服务器代码:
public static WinAuthResult Authenticate(string clientId, byte[] clientTokenBytes, string securityPackage, ILogger logger)
{
if (clientTokenBytes == null || clientTokenBytes.Length == 0)
{
ClearContext(clientId);
throw new Win32Exception(Secur32.SEC_E_INVALID_TOKEN);
}
var serverCredExpiry = new Secur32.SECURITY_INTEGER();
var serverCredHandle = new Secur32.SecHandle();
var acquireResult = Secur32.AcquireCredentialsHandle(null, securityPackage, Secur32.SECPKG_CRED_INBOUND, IntPtr.Zero, IntPtr.Zero, 0, IntPtr.Zero, out serverCredHandle, out serverCredExpiry);
if (acquireResult != Secur32.SEC_E_OK)
throw new Win32Exception(acquireResult);
var oldContextExists = contexts.ContainsKey(clientId);
var oldContextHandle = GetContextHandle(clientId);
var newContextHandle = new Secur32.SecHandle();
var clientToken = new Secur32.SecBufferDesc(clientTokenBytes);
var outputToken = new Secur32.SecBufferDesc(61440);
var contextAttributes = (uint)0;
var outputCresExpiry = new Secur32.SECURITY_INTEGER();
int acceptResult;
if (!oldContextExists)
{
acceptResult = Secur32.AcceptSecurityContext(
ref serverCredHandle,
IntPtr.Zero,
ref clientToken,
0,
Secur32.SECURITY_NATIVE_DREP,
ref newContextHandle,
ref outputToken,
out contextAttributes,
out outputCresExpiry);
}
else
{
acceptResult = Secur32.AcceptSecurityContext(
ref serverCredHandle,
ref oldContextHandle,
ref clientToken,
0,
Secur32.SECURITY_NATIVE_DREP,
ref newContextHandle,
ref outputToken,
out contextAttributes,
out outputCresExpiry);
}
if (acceptResult == Secur32.SEC_E_OK)
{
ClearContext(clientId);
return new WinAuthResult(false, outputToken.GetSecBufferByteArray());
}
else if (acceptResult == Secur32.SEC_I_CONTINUE_NEEDED)
{
contexts[clientId] = newContextHandle;
return new WinAuthResult(true, outputToken.GetSecBufferByteArray());
}
else
{
ClearContext(clientId);
throw new Win32Exception(acceptResult);
}
}
Run Code Online (Sandbox Code Playgroud)
在这两种情况下,我都试图从运行服务器的同一台计算机访问网页,并使用相同的域用户.此外,我使用相同的域用户来运行控制台应用程序和Windows服务.该问题在Windows Server 2003上无法重现,这使我认为它与新的安全功能相关.
已经有一段时间了,但我想当应用程序池的高级设置中的加载用户配置文件设置为false时,我看到了类似的问题。该设置是 IIS 7.0 中的新增设置。文档确实说错误值对应于 Windows Server 2003 行为,但我记得不加载配置文件会以某种方式干扰 SSPI 子系统。你是对的,那里几乎没有错误报告,我不得不跳过一些麻烦才能从中找出答案。
更新
这些功能最终依赖于 Kerberos 客户端实现,其中很大一部分驻留在 lsass.exe 进程中。这是有关对整个子系统进行故障排除的一个很好的链接:http://blogs.msdn.com/b/canberrapfe/archive/2012/01/02/kerberos-troubleshooting.aspx
另外,我记得一旦客户端遇到身份验证问题,我们最终可以追溯到在 Server 2008(或类似的东西,重要的事实是版本高于 2003)上运行的客户端连接到辅助域控制器之间的某些协议不匹配运行在Server 2003上。没有进一步跟踪,客户端只是升级了DC。
最终更新
好的,我能够重现该问题,并且实际上能够使其工作。Authenticate(string clientId, byte[] clientTokenBytes, string securityPackage, ILogger logger)由于第一次调用AcceptSecurityContextreturns ,您的方法至少被调用两次SEC_I_CONTINUE_NEEDED。每次Authenticate通过调用函数获取新的凭据句柄AcquireCredentialsHandle。这在控制台和作为本地系统运行的内部服务中对我有用,但如果服务在域帐户下运行则不起作用,就像你说的那样。
因此,我退出AcquireCredentialsHandle了该呼叫,Authenticate以便我可以获取它一次,然后再用于后续的来电。这为我解决了服务问题。
与此相关的是,您应该使用 call 释放凭据句柄FreeCredentialsHandle,否则您可能会在 lsass.exe 中出现内存泄漏,这将需要您重新启动服务器。请参阅MSDN 描述中的备注部分AcquireCredentialsHandle