如何将基本身份验证与System.Net.Http.HttpClient一起使用?

时间:2019-09-19 15:25:01

标签: c# basic-authentication dotnet-httpclient

我正在尝试在c#.net核心中实现一个REST客户端,该客户端首先需要执行基本身份验证,然后在后续请求中利用Bearer令牌。

当我尝试结合带有FormUrlEncodedContent对象的client.PostAsync进行基本身份验证时,出现了异常:

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)

5 个答案:

答案 0 :(得分:3)

从调用代码中显式创建 HttpClient 不是一个好习惯。 请使用简化很多事情的 HttpClientFactory

但是,如果您想使用基本身份验证,只需创建一个 HttpRequestMessage 并添加以下标头:

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");
})

答案 1 :(得分:1)

具体问题是这一行(下)

content.Headers.Add("Authorization", $"Basic {base64EncodedAuthenticationString}");

这会失败,因为 HttpContent.Headers (System.Net.Http.Headers.HttpContentHeaders) 仅适用于特定于内容的标头,例如 Content-TypeContent-Length 等。

您已经声明您不能使用 DefaultRequestHeaders,因为您只需要在单个请求中使用它 - 但您也不能将它与 PostAsync 一起使用 - 仅提供 SendAsync您自己构建 HttpRequestMessageas per your own answer and @NeilMoss' answer - 但您将来可以使用扩展方法。

但是为了其他读者的利益,另一种选择是在现有的 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) )
{
    // ...
}

答案 2 :(得分:0)

不对整个身份验证字符串进行编码-对“ 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,除非您在标头中添加charset声明。

Wikipedia似乎涵盖得很好。

答案 3 :(得分:0)

您似乎无法使用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);

答案 4 :(得分:0)

只是要补充一些我一直在努力解决的问题,我只在使用基本身份验证端点时遇到过这种情况。如果将 Json 添加为 StringContent,则会添加 charset=utf-8,这通常会返回 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;
}