为啥我的 REST 服务 .NET 客户端会在没有身份验证标头的情况下发送每个请求,然后使用身份验证标头重试?

Posted

技术标签:

【中文标题】为啥我的 REST 服务 .NET 客户端会在没有身份验证标头的情况下发送每个请求,然后使用身份验证标头重试?【英文标题】:Why would my REST service .NET clients send every request without authentication headers and then retry it with authentication header?为什么我的 REST 服务 .NET 客户端会在没有身份验证标头的情况下发送每个请求,然后使用身份验证标头重试? 【发布时间】:2014-11-03 20:29:59 【问题描述】:

我们碰巧使用 API 运行 REST Web 服务,要求客户端使用基本身份验证。我们用各种语言制作了一组简洁的示例,展示了如何与我们的服务交互。现在我正在查看服务的 IIS 日志,发现以下模式经常发生:

请求来了,被 HTTP 代码 401 拒绝 重新发送相同的请求并成功

看起来第一个请求是在没有 Authorization 标头的情况下发送的,然后第二个请求使用正确的标头发送并成功。大多数情况下,日志记录包含“用户代理”,这与我们植入 .NET 示例中的字符串相同。

所以我认为问题仅出在 .NET 程序上。我们的示例代码没有重现该问题,因此我假设用户以某种方式修改了代码或从头开始编写自己的代码。

我们尝试联系用户,但显然他们不想花时间进行研究。因此,最好找到导致 .NET 程序这种行为的最可能的情况。

他们为什么要这样做?为什么他们不会在第一次尝试时附加标题?

【问题讨论】:

【参考方案1】:

这是HttpClientHttpWebRequest 类的默认行为,通过以下方式公开。

注意:下面的文字解释了导致问题中描述的问题的次优行为。您很可能不应该这样编写代码。而是向下滚动到更正的代码

在这两种情况下,实例化一个NetworkCredenatial 对象并在其中设置用户名和密码

var credentials = new NetworkCredential( username, password );

如果你使用HttpWebRequest - 设置.Credentials 属性:

webRequest.Credentials = credentials;

如果您使用HttpClient - 将凭据对象传递给HttpClientHandler(更改代码来自here):

var client = new HttpClient(new HttpClientHandler()  Credentials = credentials )

然后运行 ​​Fiddler 并启动请求。您将看到以下内容:

发送的请求没有授权标头 服务回复 HTTP 401 和 WWW-Authenticate: Basic realm="UrRealmHere" 使用正确的 Authorization 标头重新发送请求(并成功)

here 解释了这种行为 - 客户端事先不知道服务需要 Basic 并尝试协商身份验证协议(如果服务需要 Digest em> 以开放的方式发送 Basic 标头是无用的,并且会危及客户端)。

注意:这里次优行为解释结束,解释更好的方法。您很可能应该使用下面的代码而不是上面的代码。

对于已知服务需要基本的情况,可以通过以下方式消除额外的请求:

不要设置.Credentials,而是使用here 中的代码手动添加标题。对用户名和密码进行编码:

var encoded = Convert.ToBase64String( Encoding.ASCII.GetBytes(
    String.Format( "0:1", username, password ) ) );

当使用HttpWebRequest 时,将其添加到标题中:

request.Headers.Add( "Authorization", "Basic " + encoded );

当使用HttpClient 时,将其添加到默认标题中:

client.DefaultRequestHeaders.Authorization =
    new AuthenticationHeaderValue( "Basic", encoded );

当您这样做时,每次都会使用正确的授权标头发送请求。请注意,您不应该设置.Credentials,否则如果用户名或密码错误,相同的请求将被发送两次,两次都使用错误的凭据,当然两次产生 HTTP 401。

【讨论】:

此解决方案运行良好,因为它允许在第一个请求上设置授权标头,从而绕过 401 挑战/响应。但是,根据我对 HttpClient 和 HttpClientHandler 的经验,设置预身份验证确实有效,因为在 401 挑战/响应完成后,每个请求都设置了 Authorize 标头。这篇文章在这里解释得很好:weblog.west-wind.com/posts/2010/Feb/18/… 提示:对于代理,您应该设置client.DefaultRequestHeaders.ProxyAuthorization 标头。 不应该设置HttpClientHandler.PreAuthenticate = true来解决让客户端在第一次请求时发送认证头的问题吗?

以上是关于为啥我的 REST 服务 .NET 客户端会在没有身份验证标头的情况下发送每个请求,然后使用身份验证标头重试?的主要内容,如果未能解决你的问题,请参考以下文章

为啥我的 REST API 客户端需要 JSONP 请求?

为啥 .NET 运行时会在我的字符串中添加下划线?

为啥 Spring Rest 服务在第一次请求时很慢?

为啥我的 Windows CE 设备无法成功调用服务器的 REST 方法?

为啥 XMPP 消息有时会在移动设备上丢失

为啥我的 ASP.NET 页面会在 html 元素 ID 中添加前缀“ctl00_ctl00”并破坏设计?