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

sha*_*oth 17 .net c# authentication http basic-authentication

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

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

看起来像第一个请求没有授权标头发送,然后第二个请求与正确的标头一起发送并成功.大多数情况下,日志记录包含"user-agent",这与我们在.NET示例中植入的字符串相同.

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

我们尝试联系用户,但显然他们不想花时间研究.所以很高兴找到导致.NET程序这种行为的最可能的情况.

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

sha*_*oth 38

这是HttpClient以及HttpWebRequest以下方式公开的类的默认行为.

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

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

var credentials = new NetworkCredential( username, password );
Run Code Online (Sandbox Code Playgroud)

如果你使用HttpWebRequest--set .Credentialsproperty:

webRequest.Credentials = credentials;
Run Code Online (Sandbox Code Playgroud)

如果您使用HttpClient- 将凭证对象传递给HttpClientHandler(此处更改的代码):

var client = new HttpClient(new HttpClientHandler() { Credentials = credentials })
Run Code Online (Sandbox Code Playgroud)

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

  • 请求在没有Authorization标头的情况下发送
  • 服务回复HTTP 401和WWW-Authenticate:Basic realm ="UrRealmHere"
  • 使用适当的Authorization标头重新发送请求(并成功)

此处解释此行为- 客户端事先并不知道该服务需要Basic并尝试协商身份验证协议(如果服务需要Digest在open中发送Basic头,则无用且可能危及客户端).

注意:这里次优的行为解释结束,并解释了更好的方法.您最有可能使用下面的代码而不是上面的代码.

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

不要设置.Credentials,而是使用此处的代码手动添加标头.编码用户名和密码:

var encoded = Convert.ToBase64String( Encoding.ASCII.GetBytes(
    String.Format( "{0}:{1}", username, password ) ) );
Run Code Online (Sandbox Code Playgroud)

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

request.Headers.Add( "Authorization", "Basic " + encoded );
Run Code Online (Sandbox Code Playgroud)

并在使用时HttpClient将其添加到默认标题:

client.DefaultRequestHeaders.Authorization =
    new AuthenticationHeaderValue( "Basic", encoded );
Run Code Online (Sandbox Code Playgroud)

执行此操作时,每次都会使用正确的授权标头发送请求.请注意,您不应该设置.Credentials,否则如果用户名或密码错误,则同一请求将在错误凭据的两次发送两次,当然两次都会产生HTTP 401.

  • 此解决方案运行良好,因为它允许在第一个请求上设置 Authorization 标头,从而绕过 401 Challenge / Response。然而,根据我对 HttpClient 和 HttpClientHandler 的经验,设置预验证确实有效,因为在 401 质询/响应完成后,每个请求都设置了授权标头。这篇文章在这里解释得很好:http://weblog.west-wind.com/posts/2010/Feb/18/NET-WebRequestPreAuthenticate-not-quite-what-it-sounds-like (2认同)
  • 不应该设置`HttpClientHandler.PreAuthenticate = true`来解决使客户端在第一个请求中发送身份验证标头的问题吗? (2认同)