【问题标题】:How to prevent duplicate HTTP requests with Windows Authentication如何使用 Windows 身份验证防止重复的 HTTP 请求
【发布时间】:2026-01-11 07:45:02
【问题描述】:

我正在开发基于 WCF 的客户端/服务器应用程序(WCF 是自托管的,而不是在 IIS 中)。

WCF 服务有一个将数据块上传到服务器的操作。合约大致是这样的:

void UploadChunk(int clientId, byte[] chunk);

我们正在使用 Windows 身份验证 (Kerberos/NTLM),因此我们无法在此处使用流式传输。

绑定看起来像这样(客户端和服务器端):

new BasicHttpBinding
{   
   Security = new BasicHttpSecurity
   {
      Mode = BasicHttpSecurityMode.TransportCredentialOnly,
      Transport = { ClientCredentialType = HttpClientCredentialType.Windows },
   },
   MaxReceivedMessageSize = 0x7fffffff,
   ReaderQuotas = { MaxArrayLength = 0x800000 },
};

客户端通过派生自System.ServiceModel.ClientBase<TChannel> 的代理对象与服务对话。

所有这些都可以正常工作,但我们观察到 WCF 客户端发送每个 HTTP 请求两次,一次没有 auth 标头,另一次使用正确的 auth 标头。这是有问题的,因为请求会非常大,而且这种行为会导致请求大小是实际块大小的两倍。

我已经发现 (https://weblog.west-wind.com/posts/2010/Feb/18/NET-WebRequestPreAuthenticate-not-quite-what-it-sounds-like) 将 WebRequest.PreAuthenticate 设置为 true 会记住 auth 标头并将其重用于后续请求。

但是,到目前为止,WCF 并未公开修改 WebRequest 实例的机制。

这个问题有什么解决办法吗?

【问题讨论】:

  • "WCF 将每个 HTTP 请求发送两次" - 这是不正确的。 WCF 是一个服务器 端平台——它自己不发出任何请求。你用的是什么客户端?如果您使用的是 SOAP,您是使用服务参考(也称为服务代理)还是编写自己的客户端?
  • 我们从 ClientBase 派生。 TChannel 是我们在客户端和服务器程序集之间共享的合约接口(我编辑了问题以反映这一点)
  • 既然已经是 Kerberos,那就收集痕迹看看到底发生了什么。来自服务器的响应消息应该会告诉您原因。
  • WCF 跟踪没有显示重复的请求,可能是因为 Kerberos 握手是由 Windows (HTTP.sys) 完成的
  • 在旁注中,您为什么声明您没有使用流,因为您使用的是 Windows 身份验证? Windows 身份验证是一种传输级别的安全选项,因此它不应禁止使用流。

标签: c# wcf windows-authentication


【解决方案1】:

对于 Windows 身份验证,您的第一个请求总会有一个质询响应 (401)。

如果您可以控制所有客户端,我认为最实用的解决方案是使用最少的负载实现操作。

操作void IsAuthenticated() 应该可以。对于每个客户端代理实例,您将在 UploadChunk 之前调用 IsAuthenticated

IsAuthenticated 请求将使您通过401 质询响应,而无需发送大量有效负载,但将对连接进行身份验证。对该连接的后续请求将不会受到质疑。

编辑:

我描述的行为似乎只适用于 IIS 8。所以我仔细研究了两个 http.sys 跟踪,一个用于 IIS 托管服务,一个用于自托管服务。

IIS 托管服务似乎对身份验证进行了某种优化。使用身份验证器Sspi Authenticator 对连接的第一个请求进行身份验证。后续请求使用Fast Authenticator 进行身份验证。

自托管跟踪中不存在这些事件,这使我得出结论,自托管未针对 Windows 身份验证进行优化。

http.sys - trace IIS

http.sys - trace self host

然后我发现 this blog entry 提出了一个使用 NTLM、自定义绑定和 HTTP 传输的 unsafeConnectionNtlmAuthentication 设置的解决方案。如果您只愿意使用 NTLM 并且documentation 中突出显示的安全问题不是问题这似乎提供了您正在寻找的行为 http.sys 跟踪。

http.sys trace - self host with custom binding

对于服务器使用绑定

<customBinding>
    <binding name="myBinding">
      <textMessageEncoding messageVersion="Soap11" />
      <httpTransport authenticationScheme="Ntlm" unsafeConnectionNtlmAuthentication="true"/>
    </binding>
  </customBinding>

对于您的客户端,您可以使用具有 Ntlm 安全性的常规 basicHttpBinding:

<basicHttpBinding>
    <binding name="BasicHttpBinding_ITest">
      <security mode="TransportCredentialOnly">
        <transport clientCredentialType="Ntlm" />
      </security>
    </binding>
  </basicHttpBinding>

【讨论】:

  • 我们实际上在大请求之前发出了几个小的(经过身份验证的)请求。但是,尽管 TCP 连接保持活动状态,但每个请求都受到挑战。这显然是由于weblog.west-wind.com/posts/2010/Feb/18/… 中描述的行为。
  • 抱歉,我没有看到自我主机的提及。我做了更多的挖掘,并用我的发现和解决方案更新了答案。如果你愿意接受约束的话。
  • 感谢您的深入分析。出于安全原因,我们无法选择切换到 NTLM(仅)。我只是想知道 IIS 中发生了哪些魔法,使它成为可能,而在自托管设置中无法完成......
  • @FlorianGreinacher 如果您使用的是 Kerberos 或 NTLM,为什么不只使用 NTLM?您是否为服务的某些消费者使用了约束委派?
  • 出于安全和性能原因,我们希望默认使用 Kerberos。我们只是使用模拟,不需要委托。
【解决方案2】:

使用 HttWebRequest 调用 WCF 服务,手动创建完整的 SOAP 消息。这将允许将 PreAuthenticate 设置为 true。在没有负载的情况下进行第一次调用,Authenticate()。然后使用有效负载发出请求。

【讨论】:

  • 这不是一个选项,我们需要从 WCF 客户端堆栈进行序列化和错误处理/传播
最近更新 更多