HTTPClient在成功之前获得两个401s(发送错误的令牌)

时间:2021-11-24 03:17:31

I'm trying to communicate with a self-hosted WebAPI client using HttpClient. The client is created with the following code:

我正在尝试使用HttpClient与自托管的WebAPI客户端进行通信。使用以下代码创建客户端:

HttpClientHandler clientHandler = new HttpClientHandler()
{
    UseDefaultCredentials = true,
    PreAuthenticate = true
};
var client = new HttpClient(clientHandler);

on the server side we set:

在服务器端我们设置:

HttpListener listener = (HttpListener)app.Properties[typeof(HttpListener).FullName];
listener.AuthenticationSchemes = AuthenticationSchemes.IntegratedWindowsAuthentication;

in the Startup file.

在启动文件中。

The problem is that I get two (or one after preauthentication) 401 errors before the request is processed.

问题是我在处理请求之前得到两个(或一个预先认证后)401错误。

In fiddler the sequence looks like this:

在提琴手中,序列看起来像这样:

First request:
Authorization: Negotiate TlRMTVNTUAABAAAAl7II4gcABwAxAAAACQAJACgAAAAGAbEdAAAAD1dTMTEzLTEyMFNXVC0xMTM=
Answer:
WWW-Authenticate: Negotiate TlRMTVNTUAACAAAADgAOADgAAAAVwonisrQOBMTKHhKwCkgCAAAAAJoAmgBGAAAABgGxHQAAAA9TAFcAVAAtADEAMQAzAAIADgBTAFcAVAAtADEAMQAzAAEAEgBXAFMAMQAxADMALQAxADIAMAAEABYAcwB3AHQALQAxADEAMwAuAGwAbwBjAAMAKgBXAFMAMQAxADMALQAxADIAMAAuAHMAdwB0AC0AMQAxADMALgBsAG8AYwAFABYAQQBMAEQASQA5ADkAOQAuAGwAbwBjAAcACACkGh0XVY3QAQAAAAA=

Second request (succeeds):
Authorization: Negotiate TlRMTVNTUAADAAAAAAAAAFgAAAAAAAAAWAAAAAAAAABYAAAAAAAAAFgAAAAAAAAAWAAAAAAAAABYAAAAFcKI4gYBsR0AAAAPfJafWSuLL0sAXYtWCynOqg==

So why isn't my client sending the correct authorization token the first time around but always needs this two-time approach?

那么为什么我的客户端不是第一次发送正确的授权令牌,但总是需要这种两次方法?

3 个解决方案

#1


11  

What you are experiencing is normal, this is how the NTLM authentication scheme works.

您正在经历的是正常的,这是NTLM身份验证方案的工作方式。

1: C  --> S   GET ...

2: C <--  S   401 Unauthorized
              WWW-Authenticate: NTLM

3: C  --> S   GET ...
              Authorization: NTLM <base64-encoded type-1-message>

4: C <--  S   401 Unauthorized
              WWW-Authenticate: NTLM <base64-encoded type-2-message>

5: C  --> S   GET ...
              Authorization: NTLM <base64-encoded type-3-message>

6: C <--  S   200 Ok
  1. The client sends a GET request to the server.
  2. 客户端向服务器发送GET请求。

  3. Since you need to be authenticated to access the requested resource, the server sends back a 401 Unathorized response and notifies the client in the WWW-Authenticate header that it supports NTLM authentication. So this is where you get your first 401 response code.
  4. 由于您需要进行身份验证以访问所请求的资源,因此服务器会发回401 Unathorized响应,并在WWW-Authenticate标头中通知客户端它支持NTLM身份验证。因此,这是您获得第一个401响应代码的地方。

  5. The client sends the domain name and the username to the server in the Authorization header. Note that based solely on these information the client cannot be authenticated yet.
  6. 客户端在Authorization标头中将域名和用户名发送到服务器。请注意,仅基于这些信息,客户端尚未进行身份验证。

  7. The server sends a challenge to the client. It's a randomly generated number called a nonce. This is where you get your second 401 response code.
  8. 服务器向客户端发送质询。它是一个随机生成的数字,称为nonce。这是您获得第二个401响应代码的地方。

  9. The client sends back a response to the server's challenge, using its password's hash to encrypt the random number.
  10. 客户端使用其密码的哈希值发送回服务器质询的响应,以加密随机数。

  11. The server sends the client's username, the challenge sent to the client and the response received from the client to the domain controller. Using the username the domain controller retrieves the hash of the user's password and encrypts the challenge with it. If the result matches the response sent by the client, the client is authenticated and the server sends back a 200 response code and the requested resource to the client.
  12. 服务器将客户端的用户名,发送给客户端的质询和从客户端接收的响应发送到域控制器。使用用户名,域控制器检索用户密码的哈希值并使用它加密质询。如果结果与客户端发送的响应匹配,则对客户端进行身份验证,然后服务器将200响应代码和请求的资源发送回客户端。

#2


1  

Faced a similar issue, and after going through a lot of answers, where none of those worked. The following worked, and it doesn't come up with two 401's:

面对类似的问题,经过大量的答案,没有一个工作。以下工作,并没有提出两个401:

var credential = new NetworkCredential("username", "password", "domainname");
var myCache = new CredentialCache();

// Add the target Uri to the CredentialCache with credential object
myCache.Add(new Uri("http://targeturi/"), "NTLM", credential);

// Create an HttpClientHandler to add some settings
var handler = new HttpClientHandler();
handler.AllowAutoRedirect = true;
handler.Credentials = myCache;

// Create an HttpClient with the handler object
httpClient = new HttpClient(handler);

// Wait to get the reponse, and you can use the reponse in your code
HttpResponseMessage response = await httpClient.GetAsync(resourceUri);

Source

#3


0  

the default behavior where it only sends credentials after receiving an HTTP 401 Not Authorized response.

默认行为,它仅在收到HTTP 401 Not Authorized响应后发送凭据。

Manually adding the credentials header seems to be the best solution available.

手动添加凭据标头似乎是可用的最佳解决方案。

More details in this post

这篇文章中的更多细节

#1


11  

What you are experiencing is normal, this is how the NTLM authentication scheme works.

您正在经历的是正常的,这是NTLM身份验证方案的工作方式。

1: C  --> S   GET ...

2: C <--  S   401 Unauthorized
              WWW-Authenticate: NTLM

3: C  --> S   GET ...
              Authorization: NTLM <base64-encoded type-1-message>

4: C <--  S   401 Unauthorized
              WWW-Authenticate: NTLM <base64-encoded type-2-message>

5: C  --> S   GET ...
              Authorization: NTLM <base64-encoded type-3-message>

6: C <--  S   200 Ok
  1. The client sends a GET request to the server.
  2. 客户端向服务器发送GET请求。

  3. Since you need to be authenticated to access the requested resource, the server sends back a 401 Unathorized response and notifies the client in the WWW-Authenticate header that it supports NTLM authentication. So this is where you get your first 401 response code.
  4. 由于您需要进行身份验证以访问所请求的资源,因此服务器会发回401 Unathorized响应,并在WWW-Authenticate标头中通知客户端它支持NTLM身份验证。因此,这是您获得第一个401响应代码的地方。

  5. The client sends the domain name and the username to the server in the Authorization header. Note that based solely on these information the client cannot be authenticated yet.
  6. 客户端在Authorization标头中将域名和用户名发送到服务器。请注意,仅基于这些信息,客户端尚未进行身份验证。

  7. The server sends a challenge to the client. It's a randomly generated number called a nonce. This is where you get your second 401 response code.
  8. 服务器向客户端发送质询。它是一个随机生成的数字,称为nonce。这是您获得第二个401响应代码的地方。

  9. The client sends back a response to the server's challenge, using its password's hash to encrypt the random number.
  10. 客户端使用其密码的哈希值发送回服务器质询的响应,以加密随机数。

  11. The server sends the client's username, the challenge sent to the client and the response received from the client to the domain controller. Using the username the domain controller retrieves the hash of the user's password and encrypts the challenge with it. If the result matches the response sent by the client, the client is authenticated and the server sends back a 200 response code and the requested resource to the client.
  12. 服务器将客户端的用户名,发送给客户端的质询和从客户端接收的响应发送到域控制器。使用用户名,域控制器检索用户密码的哈希值并使用它加密质询。如果结果与客户端发送的响应匹配,则对客户端进行身份验证,然后服务器将200响应代码和请求的资源发送回客户端。

#2


1  

Faced a similar issue, and after going through a lot of answers, where none of those worked. The following worked, and it doesn't come up with two 401's:

面对类似的问题,经过大量的答案,没有一个工作。以下工作,并没有提出两个401:

var credential = new NetworkCredential("username", "password", "domainname");
var myCache = new CredentialCache();

// Add the target Uri to the CredentialCache with credential object
myCache.Add(new Uri("http://targeturi/"), "NTLM", credential);

// Create an HttpClientHandler to add some settings
var handler = new HttpClientHandler();
handler.AllowAutoRedirect = true;
handler.Credentials = myCache;

// Create an HttpClient with the handler object
httpClient = new HttpClient(handler);

// Wait to get the reponse, and you can use the reponse in your code
HttpResponseMessage response = await httpClient.GetAsync(resourceUri);

Source

#3


0  

the default behavior where it only sends credentials after receiving an HTTP 401 Not Authorized response.

默认行为,它仅在收到HTTP 401 Not Authorized响应后发送凭据。

Manually adding the credentials header seems to be the best solution available.

手动添加凭据标头似乎是可用的最佳解决方案。

More details in this post

这篇文章中的更多细节