当应用程序作为服务 运行 时,AcceptSecurityContext 失败

AcceptSecurityContext fails when application is running as a service

我有一个简单的 HTTP 服务器,它使用协商协议对客户端进行身份验证。它使用 SSPI 调用来获取服务器凭据并建立安全上下文。服务器在域中,代表域用户 运行ning。一切正常,如果我以控制台模式启动服务器,我将收到 HTTP 200 响应。但是,当我 运行 将其作为服务使用时,出现 SEC_E_INVALID_HANDLE 错误。以下是我在控制台模式下启动它时发生的情况:

1.Client发送HTTP Get请求http://localhost:8082

2.Server 回复 WWW-Authenticate:协商 header。

3.Client 发送授权 header 并包含以下数据:

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             

4.Server 响应 HTTP 401 错误并协商 header 提示继续:

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                                               .             

5.Client 发送授权 header:

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-"....     

6.Server 以 HTTP 200 响应并协商 header:

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ã©....   

现在,如果我 运行 将应用程序作为服务,我将得到几乎相同的响应,但 AcceptSecurityContext 将在第 6 步失败并出现 return SEC_E_INVALID_HANDLE 错误。我想知道如果我 运行 相同的应用程序并指定相同的用户作为服务登录身份,为什么会失败?它可能与 session 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);
    }
}

在这两种情况下,我都试图从服务器 运行ning 所在的同一台计算机和同一域用户访问网页。此外,我使用相同的域用户来访问 运行 控制台应用程序和 windows 服务。该问题在 Windows Server 2003 上无法重现,这让我认为它与新的安全功能有关。

已经有一段时间了,但我认为当 Load User Profile 在高级设置中设置为 false 时我看到了类似的问题应用程序池。该设置是 IIS 7.0 的新设置。文档确实说 false 值对应于 Windows Server 2003 行为,但我记得不加载配置文件会以某种方式干扰 SSPI 子系统。你是对的,那里几乎没有错误报告,我不得不跳过一些障碍才能从中找出答案。

更新

这些功能最终依赖于 Kerberos 客户端实现,其中很大一部分驻留在 lsass.exe 进程中。这是关于对整个子系统进行故障排除的一个很好的 link:http://blogs.msdn.com/b/canberrapfe/archive/2012/01/02/kerberos-troubleshooting.aspx

此外,我记得一旦客户端在身份验证方面出现问题,我们最终将其追溯到服务器 2008 上的客户端 运行 之间的某种协议不匹配(或类似的东西,重要的事实是版本更高比 2003) 连接到服务器 2003 上的辅助域控制器 运行。没有进一步跟踪,客户端刚刚升级了 DC。

最终更新

好的,我能够重现该问题,而且我确实能够让它发挥作用。由于第一次调用 AcceptSecurityContext 返回 SEC_I_CONTINUE_NEEDED,您的 Authenticate(string clientId, byte[] clientTokenBytes, string securityPackage, ILogger logger) 方法至少被调用两次。每次 Authenticate 通过调用 AcquireCredentialsHandle 函数获取新的凭据句柄。这在控制台和内部服务 运行 作为本地系统对我有用,但如果服务是 运行 在域帐户下,就像你说的那样。

因此,我将 AcquireCredentialsHandle 调用从 Authenticate 中拉出,以便我可以获得它一次,然后重新用于后续来电。这为我修复了服务。

在相关说明中,您应该使用 FreeCredentialsHandle 调用释放凭据句柄,否则您可能会在 lsass.exe 中发生内存泄漏,这将需要您重新启动服务器。请参阅 AcquireCredentialsHandle

的 MSDN 说明中的 备注 部分