简体   繁体   English

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

[英]Why would my REST service .NET clients send every request without authentication headers and then retry it with authentication header?

We happen to run a REST web service with API requiring that clients use Basic authentication. 我们碰巧运行带有API的REST Web服务,要求客户端使用基本身份验证。 We crafted a set of neat samples in various languages showing how to interface with our service. 我们用各种语言制作了一套简洁的样本,展示了如何与我们的服务进行交互。 Now I'm reviewing IIS logs of the service and see that the following pattern happens quite often: 现在我正在检查服务的IIS日志,并发现以下模式经常发生:

  • a request comes, gets rejected with HTTP code 401 请求到来,被HTTP代码401拒绝
  • the same request is resent and succeeds 相同的请求被重新发送并成功

which looks like the first request is sent without Authorization headers and then the second one is sent with the right headers and succeeds. 看起来像第一个请求没有授权标头发送,然后第二个请求与正确的标头一起发送并成功。 Most of the time the log record contains "user-agent" which is the same string we planted into our .NET sample. 大多数情况下,日志记录包含“user-agent”,这与我们在.NET示例中植入的字符串相同。

So I assume the problem is with .NET programs only. 所以我认为问题仅在于.NET程序。 The problem is not reproduced with our sample code so I assume the users somehow modified the code or wrote their own from scratch. 我们的示例代码没有重现该问题,因此我假设用户以某种方式修改了代码或从头开始编写自己的代码。

We tried contacting the users but apparently they don't want to invest time into research. 我们尝试联系用户,但显然他们不想花时间研究。 So it'd be nice to find what the most likely scenario is which leads to this behavior of .NET programs. 所以很高兴找到导致.NET程序这种行为的最可能的情况。

Why would they do this? 他们为什么要这样做? Why would they not attach the headers on the first attempt? 他们为什么不在第一次尝试时附上标题?

This is the default behavior of HttpClient and HttpWebRequest classes which is exposed the following way. 这是HttpClientHttpWebRequest类的默认行为,它们以下列方式公开。

Note: Below text explains suboptimal behavior causing the problem described in the question. 注意:下面的文字解释了导致问题中描述的问题的次优行为。 Most likely you should not write your code like this. 很可能你不应该像这样写你的代码。 Instead scroll below to the corrected code 而是向下滚动到更正的代码

In both cases, instantiate a NetworkCredenatial object and set the username and password in there 在这两种情况下,都要实例化NetworkCredenatial对象并在其中设置用户名和密码

var credentials = new NetworkCredential( username, password );

If you use HttpWebRequest - set .Credentials property: 如果您使用HttpWebRequest - 设置.Credentials属性:

webRequest.Credentials = credentials;

If you use HttpClient - pass the credentials object into HttpClientHandler (altered code from here ): 如果您使用HttpClient - 将凭证对象传递给HttpClientHandler此处更改的代码):

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

Then run Fiddler and start the request. 然后运行Fiddler并启动请求。 You will see the following: 您将看到以下内容:

  • the request is sent without Authorization header 请求在没有Authorization标头的情况下发送
  • the service replies with HTTP 401 and WWW-Authenticate: Basic realm="UrRealmHere" 服务回复HTTP 401和WWW-Authenticate:Basic realm =“UrRealmHere”
  • the request is resent with proper Authorization header (and succeeds) 使用适当的Authorization标头重新发送请求(并成功)

This behavior is explained here - the client doesn't know in advance that the service requires Basic and tries to negotiate the authentication protocol (and if the service requires Digest sending Basic headers in open is useless and can compromise the client). 此处解释此行为 - 客户端事先并不知道该服务需要Basic并尝试协商身份验证协议(如果服务需要Digest在open中发送Basic头,则无用且可能危及客户端)。

Note: Here suboptimal behavior explanation ends and better approach is explained. 注意:这里次优的行为解释结束,并解释了更好的方法。 Most likely you should use code from below instead of code from above. 您最有可能使用下面的代码而不是上面的代码。

For cases when it's known that the service requires Basic that extra request can be eliminated the following way: 对于已知服务需要Basic的情况 ,可以通过以下方式消除额外请求:

Don't set .Credentials , instead add the headers manually using code from here . 不要设置.Credentials ,而是使用此处的代码手动添加标题。 Encode the username and password: 编码用户名和密码:

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

When using HttpWebRequest add it to the headers: 使用HttpWebRequest将其添加到标题中:

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

and when using HttpClient add it to default headers: 并在使用HttpClient将其添加到默认标头:

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

When you do that the request is sent with the right authorization headers every time. 执行此操作时,每次都会使用正确的授权标头发送请求。 Note that you should not set .Credentials , otherwise if the username or password is wrong the same request will be sent twice both time with the wrong credentials and both times of course yielding HTTP 401. 请注意,您不应设置.Credentials ,否则如果用户名或密码错误,则同一请求将在错误凭据的两次发送两次,当然两次都会产生HTTP 401。

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM