我们碰巧运行带有API的REST Web服务,要求客户端使用 Basic 身份验证。我们用各种语言制作了一套简洁的样本,展示了如何与我们的服务进行交互。现在我正在检查服务的IIS日志,并看到以下模式经常发生:
看起来像第一个请求是在没有授权标头的情况下发送的,然后第二个请求是使用正确的标头发送并成功的。大多数情况下,日志记录包含“user-agent”,这与我们在.NET示例中植入的字符串相同。
所以我认为问题仅在于.NET程序。我们的示例代码没有重现该问题,因此我假设用户以某种方式修改了代码或从头开始编写自己的代码。
我们尝试联系用户,但显然他们不想花时间研究。所以很高兴找到导致.NET程序这种行为的最可能的情况。
他们为什么要这样做?他们为什么不在第一次尝试时附上标题?
答案 0 :(得分:36)
这是HttpClient
和HttpWebRequest
类的默认行为,以下列方式公开。
注意:下面的文字解释了导致问题中描述的问题的次优行为。很可能你不应该像这样写你的代码。而是向下滚动到更正后的代码
在这两种情况下,实例化NetworkCredenatial
对象并在其中设置用户名和密码
var credentials = new NetworkCredential( username, password );
如果您使用HttpWebRequest
- 设置.Credentials
属性:
webRequest.Credentials = credentials;
如果您使用HttpClient
- 将凭据对象传递到HttpClientHandler
(来自here的更改代码):
var client = new HttpClient(new HttpClientHandler() { Credentials = credentials })
然后运行Fiddler并启动请求。您将看到以下内容:
此行为已解释here - 客户端事先并不知道该服务需要 Basic 并尝试协商身份验证协议(如果服务需要 Digest 在open中发送 Basic 标头是没用的,可能会损害客户端。)
注意:这里次优的行为解释结束,并解释了更好的方法。您最有可能使用下面的代码而不是上面的代码。
对于已知服务需要 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。