您如何通过 System.Net.Http.HttpClient 使用基本身份验证?
How do you use Basic Authentication with System.Net.Http.HttpClient?
我正在尝试在 c# .net 核心中实现一个休息客户端,它需要首先进行基本身份验证,然后在后续请求中利用 Bearer 令牌。
当我尝试结合 client.PostAsync 和 FormUrlEncodedContent 对象执行基本身份验证时,出现异常:
System.InvalidOperationException occurred in System.Net.Http.dll: 'Misused header name. Make sure request headers are used with HttpRequestMessage, response headers with HttpResponseMessage, and content headers with HttpContent objects.'
//setup reusable http client
HttpClient client = new HttpClient();
Uri baseUri = new Uri(url);
client.BaseAddress = baseUri;
client.DefaultRequestHeaders.Clear();
client.DefaultRequestHeaders.ConnectionClose = true;
//Post body content
var values = new List<KeyValuePair<string,string>>();
values.Add(new KeyValuePair<string, string>("grant_type", "client_credentials"));
var content = new FormUrlEncodedContent(values);
//Basic Authentication
var authenticationString = $"{clientId}:{clientSecret}";
var base64EncodedAuthenticationString = Convert.ToBase64String(System.Text.ASCIIEncoding.ASCII.GetBytes(authenticationString));
content.Headers.Add("Authorization", $"Basic {base64EncodedAuthenticationString}");
//make the request
var task = client.PostAsync("/oauth2/token",content);
var response = task.Result;
response.EnsureSuccessStatusCode();
string responseBody = response.Content.ReadAsStringAsync().Result;
Console.WriteLine(responseBody);
Exception has occurred: CLR/System.InvalidOperationException
An unhandled exception of type 'System.InvalidOperationException' occurred in System.Net.Http.dll: 'Misused header name. Make sure request headers are used with HttpRequestMessage, response headers with HttpResponseMessage, and content headers with HttpContent objects.'
at System.Net.Http.Headers.HttpHeaders.GetHeaderDescriptor(String name)
at System.Net.Http.Headers.HttpHeaders.Add(String name, String value)
不要对整个身份验证字符串进行编码 - 对 "Username:Password" 表达式进行编码并将结果附加到 "Basic " 前缀。
var authenticationString = $"{clientId}:{clientSecret}";
var base64EncodedAuthenticationString = Convert.ToBase64String(System.Text.ASCIIEncoding.UTF8.GetBytes(authenticationString));
content.Headers.Add("Authorization", "Basic " + base64EncodedAuthenticationString);
此外,请考虑仅使用 ASCII 编码 - 服务器可能无法理解 UTF8,除非您向 header 添加 charset
声明。
Wikipedia 似乎很好地涵盖了这一点。
看起来您不能使用 PostAsync 并且可以访问混乱的 Headers 进行身份验证。我必须使用 HttpRequestMessage 和 SendAsync。
//setup reusable http client
HttpClient client = new HttpClient();
Uri baseUri = new Uri(url);
client.BaseAddress = baseUri;
client.DefaultRequestHeaders.Clear();
client.DefaultRequestHeaders.ConnectionClose = true;
//Post body content
var values = new List<KeyValuePair<string, string>>();
values.Add(new KeyValuePair<string, string>("grant_type", "client_credentials"));
var content = new FormUrlEncodedContent(values);
var authenticationString = $"{clientId}:{clientSecret}";
var base64EncodedAuthenticationString = Convert.ToBase64String(System.Text.ASCIIEncoding.ASCII.GetBytes(authenticationString));
var requestMessage = new HttpRequestMessage(HttpMethod.Post, "/oauth2/token");
requestMessage.Headers.Authorization = new AuthenticationHeaderValue("Basic", base64EncodedAuthenticationString);
requestMessage.Content = content;
//make the request
var task = client.SendAsync(requestMessage);
var response = task.Result;
response.EnsureSuccessStatusCode();
string responseBody = response.Content.ReadAsStringAsync().Result;
Console.WriteLine(responseBody);
具体问题是这一行(下)
content.Headers.Add("Authorization", $"Basic {base64EncodedAuthenticationString}");
这会失败,因为 HttpContent.Headers
(System.Net.Http.Headers.HttpContentHeaders
) 仅适用于 headers 即 content-specific,例如 Content-Type
、Content-Length
、等等。
您已经声明您不能使用 DefaultRequestHeaders
因为您只需要它用于单个请求 - 但您也不能将它与 PostAsync
一起使用 - 仅 SendAsync
如果您自己构建 HttpRequestMessage
,as per your own answer and @NeilMoss' answer - 但您将来可以使用 extension-method。
但是为了其他读者的利益,另一种选择是在现有PostAsync
的基础上增加一个新的扩展方法,其实真的很简单(only 3 lines!):
public Task<HttpResponseMessage> PostAsync( this HttpClient httpClient, Uri requestUri, HttpContent content, String basicUserName, String basicPassword, String? challengeCharSet = null, CancellationToken cancellationToken = default )
{
if( basicUserName.IndexOf(':') > -1 ) throw new ArgumentException( message: "RFC 7617 states that usernames cannot contain colons.", paramName: nameof(basicUserName) );
HttpRequestMessage httpRequestMessage = new HttpRequestMessage( HttpMethod.Post, requestUri );
httpRequestMessage.Content = content;
//
Encoding encoding = Encoding.ASCII;
if( challengeCharSet != null )
{
try
{
encoding = Encoding.GetEncoding( challengeCharSet );
}
catch
{
encoding = Encoding.ASCII;
}
}
httpRequestMessage.Headers.Authorization = new AuthenticationHeaderValue(
scheme : "Basic",
parameter: Convert.ToBase64String( encoding.GetBytes( userName + ":" + password ) )
);
return SendAsync( httpRequestMessage, cancellationToken );
}
用法:
HttpClient httpClient = ...
using( HttpResponseMessage response = await httpClient.PostAsync( uri, content, basicUserName: "AzureDiamond", basicPassword: "hunter2" ).ConfigureAwait(false) )
{
// ...
}
从您的调用代码显式创建 HttpClient 不是一个好习惯。
请使用 HttpClientFactory 来简化很多事情。
但是,如果您想使用基本身份验证,只需创建一个 HttpRequestMessage 并添加以下内容 header:
var request = new HttpRequestMessage(HttpMethod.Post, getPath)
{
Content = new FormUrlEncodedContent(values)
};
request.Headers.Authorization = new BasicAuthenticationHeaderValue("username", "password");
// other settings
如果您决定使用推荐的 IHttpClientFactory,那就更简单了:
serviceCollection.AddHttpClient(c =>
{
c.BaseAddress = new Uri("your base url");
c.SetBasicAuthentication("username", "password");
})
只是要补充一点我一直在努力解决的问题,我只在使用基本身份验证端点时遇到过。如果您将 Json 添加为 StringContent,那么它会添加一个 charset=utf-8,这通常是 return BadRequest 400。
这是我用来解决这个问题的代码:参考:
https://dzone.com/articles/httpclient-how-to-remove-charset-from-content-type
using (var client = new HttpClient())
using (var content = new StringContent(ParseJSON(data), Encoding.Default, "application/json"))
{
//Remove UTF-8 Charset causing BadRequest 400
content.Headers.ContentType.CharSet = "";
var clientId = "client";
var clientSecret = "secret";
var authenticationString = $"{clientId}:{clientSecret}";
var base64EncodedAuthenticationString = Convert.ToBase64String(System.Text.ASCIIEncoding.UTF8.GetBytes(authenticationString));
client.DefaultRequestHeaders.TryAddWithoutValidation(authHeader, authorization);
var response = await client.PostAsync(url, content);
return response;
}
我已经使用下面的代码解决了这个问题,这也符合我的目的。为 Get/Post 添加了代码,这会对您有所帮助。此外,我还添加了一个 Header 键。所以要将额外的数据传递给 header。希望能解决您的问题。
class Program {
private static readonly string Username = "test";
private static readonly string Password = "test@123";
static void Main(string[] args) {
var response = Login();
}
public static async Task Login()
{
var anotherKey ="test";
HttpClient httpClient = new HttpClient
{
BaseAddress = new Uri("https://google.com/")
};
httpClient.DefaultRequestHeaders.Add($"Authorization", $"Basic {Base64Encode($"{Username}:{Password}")}");
httpClient.DefaultRequestHeaders.Add($"anotherKey", $"{anotherKey}");
HttpResponseMessage httpResponseMessage = await httpClient.GetAsync("user/123").ConfigureAwait(false);
// For Get Method
var response= await httpResponseMessage.Content.ReadAsStringAsync().ConfigureAwait(false);
// For Post Method
User user = new User (1,"ABC");
HttpResponseMessage httpResponseMessage = await httpClient.PostAsJsonAsync("/post", user).ConfigureAwait(false);
UserDetail userDetail = await httpResponseMessage.Content.ReadAsAsync<UserDetail>().ConfigureAwait(false);
}
}
我正在尝试在 c# .net 核心中实现一个休息客户端,它需要首先进行基本身份验证,然后在后续请求中利用 Bearer 令牌。
当我尝试结合 client.PostAsync 和 FormUrlEncodedContent 对象执行基本身份验证时,出现异常:
System.InvalidOperationException occurred in System.Net.Http.dll: 'Misused header name. Make sure request headers are used with HttpRequestMessage, response headers with HttpResponseMessage, and content headers with HttpContent objects.'
//setup reusable http client
HttpClient client = new HttpClient();
Uri baseUri = new Uri(url);
client.BaseAddress = baseUri;
client.DefaultRequestHeaders.Clear();
client.DefaultRequestHeaders.ConnectionClose = true;
//Post body content
var values = new List<KeyValuePair<string,string>>();
values.Add(new KeyValuePair<string, string>("grant_type", "client_credentials"));
var content = new FormUrlEncodedContent(values);
//Basic Authentication
var authenticationString = $"{clientId}:{clientSecret}";
var base64EncodedAuthenticationString = Convert.ToBase64String(System.Text.ASCIIEncoding.ASCII.GetBytes(authenticationString));
content.Headers.Add("Authorization", $"Basic {base64EncodedAuthenticationString}");
//make the request
var task = client.PostAsync("/oauth2/token",content);
var response = task.Result;
response.EnsureSuccessStatusCode();
string responseBody = response.Content.ReadAsStringAsync().Result;
Console.WriteLine(responseBody);
Exception has occurred: CLR/System.InvalidOperationException
An unhandled exception of type 'System.InvalidOperationException' occurred in System.Net.Http.dll: 'Misused header name. Make sure request headers are used with HttpRequestMessage, response headers with HttpResponseMessage, and content headers with HttpContent objects.'
at System.Net.Http.Headers.HttpHeaders.GetHeaderDescriptor(String name)
at System.Net.Http.Headers.HttpHeaders.Add(String name, String value)
不要对整个身份验证字符串进行编码 - 对 "Username:Password" 表达式进行编码并将结果附加到 "Basic " 前缀。
var authenticationString = $"{clientId}:{clientSecret}";
var base64EncodedAuthenticationString = Convert.ToBase64String(System.Text.ASCIIEncoding.UTF8.GetBytes(authenticationString));
content.Headers.Add("Authorization", "Basic " + base64EncodedAuthenticationString);
此外,请考虑仅使用 ASCII 编码 - 服务器可能无法理解 UTF8,除非您向 header 添加 charset
声明。
Wikipedia 似乎很好地涵盖了这一点。
看起来您不能使用 PostAsync 并且可以访问混乱的 Headers 进行身份验证。我必须使用 HttpRequestMessage 和 SendAsync。
//setup reusable http client
HttpClient client = new HttpClient();
Uri baseUri = new Uri(url);
client.BaseAddress = baseUri;
client.DefaultRequestHeaders.Clear();
client.DefaultRequestHeaders.ConnectionClose = true;
//Post body content
var values = new List<KeyValuePair<string, string>>();
values.Add(new KeyValuePair<string, string>("grant_type", "client_credentials"));
var content = new FormUrlEncodedContent(values);
var authenticationString = $"{clientId}:{clientSecret}";
var base64EncodedAuthenticationString = Convert.ToBase64String(System.Text.ASCIIEncoding.ASCII.GetBytes(authenticationString));
var requestMessage = new HttpRequestMessage(HttpMethod.Post, "/oauth2/token");
requestMessage.Headers.Authorization = new AuthenticationHeaderValue("Basic", base64EncodedAuthenticationString);
requestMessage.Content = content;
//make the request
var task = client.SendAsync(requestMessage);
var response = task.Result;
response.EnsureSuccessStatusCode();
string responseBody = response.Content.ReadAsStringAsync().Result;
Console.WriteLine(responseBody);
具体问题是这一行(下)
content.Headers.Add("Authorization", $"Basic {base64EncodedAuthenticationString}");
这会失败,因为 HttpContent.Headers
(System.Net.Http.Headers.HttpContentHeaders
) 仅适用于 headers 即 content-specific,例如 Content-Type
、Content-Length
、等等。
您已经声明您不能使用 DefaultRequestHeaders
因为您只需要它用于单个请求 - 但您也不能将它与 PostAsync
一起使用 - 仅 SendAsync
如果您自己构建 HttpRequestMessage
,as per your own answer and @NeilMoss' answer - 但您将来可以使用 extension-method。
但是为了其他读者的利益,另一种选择是在现有PostAsync
的基础上增加一个新的扩展方法,其实真的很简单(only 3 lines!):
public Task<HttpResponseMessage> PostAsync( this HttpClient httpClient, Uri requestUri, HttpContent content, String basicUserName, String basicPassword, String? challengeCharSet = null, CancellationToken cancellationToken = default )
{
if( basicUserName.IndexOf(':') > -1 ) throw new ArgumentException( message: "RFC 7617 states that usernames cannot contain colons.", paramName: nameof(basicUserName) );
HttpRequestMessage httpRequestMessage = new HttpRequestMessage( HttpMethod.Post, requestUri );
httpRequestMessage.Content = content;
//
Encoding encoding = Encoding.ASCII;
if( challengeCharSet != null )
{
try
{
encoding = Encoding.GetEncoding( challengeCharSet );
}
catch
{
encoding = Encoding.ASCII;
}
}
httpRequestMessage.Headers.Authorization = new AuthenticationHeaderValue(
scheme : "Basic",
parameter: Convert.ToBase64String( encoding.GetBytes( userName + ":" + password ) )
);
return SendAsync( httpRequestMessage, cancellationToken );
}
用法:
HttpClient httpClient = ...
using( HttpResponseMessage response = await httpClient.PostAsync( uri, content, basicUserName: "AzureDiamond", basicPassword: "hunter2" ).ConfigureAwait(false) )
{
// ...
}
从您的调用代码显式创建 HttpClient 不是一个好习惯。 请使用 HttpClientFactory 来简化很多事情。
但是,如果您想使用基本身份验证,只需创建一个 HttpRequestMessage 并添加以下内容 header:
var request = new HttpRequestMessage(HttpMethod.Post, getPath)
{
Content = new FormUrlEncodedContent(values)
};
request.Headers.Authorization = new BasicAuthenticationHeaderValue("username", "password");
// other settings
如果您决定使用推荐的 IHttpClientFactory,那就更简单了:
serviceCollection.AddHttpClient(c =>
{
c.BaseAddress = new Uri("your base url");
c.SetBasicAuthentication("username", "password");
})
只是要补充一点我一直在努力解决的问题,我只在使用基本身份验证端点时遇到过。如果您将 Json 添加为 StringContent,那么它会添加一个 charset=utf-8,这通常是 return BadRequest 400。
这是我用来解决这个问题的代码:参考: https://dzone.com/articles/httpclient-how-to-remove-charset-from-content-type
using (var client = new HttpClient())
using (var content = new StringContent(ParseJSON(data), Encoding.Default, "application/json"))
{
//Remove UTF-8 Charset causing BadRequest 400
content.Headers.ContentType.CharSet = "";
var clientId = "client";
var clientSecret = "secret";
var authenticationString = $"{clientId}:{clientSecret}";
var base64EncodedAuthenticationString = Convert.ToBase64String(System.Text.ASCIIEncoding.UTF8.GetBytes(authenticationString));
client.DefaultRequestHeaders.TryAddWithoutValidation(authHeader, authorization);
var response = await client.PostAsync(url, content);
return response;
}
我已经使用下面的代码解决了这个问题,这也符合我的目的。为 Get/Post 添加了代码,这会对您有所帮助。此外,我还添加了一个 Header 键。所以要将额外的数据传递给 header。希望能解决您的问题。
class Program {
private static readonly string Username = "test";
private static readonly string Password = "test@123";
static void Main(string[] args) {
var response = Login();
}
public static async Task Login()
{
var anotherKey ="test";
HttpClient httpClient = new HttpClient
{
BaseAddress = new Uri("https://google.com/")
};
httpClient.DefaultRequestHeaders.Add($"Authorization", $"Basic {Base64Encode($"{Username}:{Password}")}");
httpClient.DefaultRequestHeaders.Add($"anotherKey", $"{anotherKey}");
HttpResponseMessage httpResponseMessage = await httpClient.GetAsync("user/123").ConfigureAwait(false);
// For Get Method
var response= await httpResponseMessage.Content.ReadAsStringAsync().ConfigureAwait(false);
// For Post Method
User user = new User (1,"ABC");
HttpResponseMessage httpResponseMessage = await httpClient.PostAsJsonAsync("/post", user).ConfigureAwait(false);
UserDetail userDetail = await httpResponseMessage.Content.ReadAsAsync<UserDetail>().ConfigureAwait(false);
}
}