无法使用 HttpClient 进行有效的 GET 调用

问题描述 投票:0回答:1

我正在尝试编写一个简单的控制台应用程序来与我的霍尼韦尔恒温器进行通信。他们提供免费使用的 REST API,记录如下:https://developer.honeywellhome.com/。我在身份验证后无法立即进行简单的 GET 调用,而且我不知道我做错了什么。我希望有人能在这里帮助我。

总结来说,我的流程包括 3 个步骤:

  1. 注册一个应用程序以获取 AppID 和 Secret(这里都很好)。
  2. 使用 OAuth2 进行身份验证以获取访问令牌(这里一切都很好)。
  3. 使用提供的访问令牌调用任何 REST API(问题就在这里)。

详情

我的控制台csproj非常简单:

<Project Sdk="Microsoft.NET.Sdk">

  <PropertyGroup>
    <OutputType>Exe</OutputType>
    <TargetFramework>net8.0</TargetFramework>
    <ImplicitUsings>true</ImplicitUsings>
    <Nullable>enable</Nullable>
  </PropertyGroup>

</Project>

1.注册应用程序以获取 AppID 和 Secret。

应用程序在这里注册,这是非常标准的:https://developer.honeywellhome.com/user/me/apps

对于此示例,我们假设 Resideo 为我提供了这些值,我稍后将使用这些值:

  • 应用程序ID:
    ABCD1234
  • 秘密:
    WXYZ9876

2.进行身份验证以获取访问令牌。

访问令牌 json 响应的结构如下所述:https://developer.honeywellhome.com/authorization-oauth2/apis/post/accesstoken

这就是我定义json反序列化类的方式:

internal class ResideoToken
{
    [JsonPropertyName("refresh_token_expires_in")]
    public string RefreshTokenExpiration { get; set; } = string.Empty;
    [JsonPropertyName("api_product_list")]
    public string ApiProductList { get; set; } = string.Empty;
    [JsonPropertyName("organization_name")]
    public string OrganizationName { get; set; } = string.Empty;
    [JsonPropertyName("developer.email")]
    public string DeveloperEmail { get; set; } = string.Empty;
    [JsonPropertyName("token_type")]
    public string TokenType { get; set; } = string.Empty;
    [JsonPropertyName("issued_at")]
    public string IssuedAt { get; set; } = string.Empty;
    [JsonPropertyName("client_id")]
    public string ClientId { get; set; } = string.Empty;
    [JsonPropertyName("access_token")]
    public string AccessToken { get; set; } = string.Empty;
    [JsonPropertyName("application_name")]
    public string ApplicationName { get; set; } = string.Empty;
    [JsonPropertyName("scope")]
    public string Scope { get; set; } = string.Empty;
    [JsonPropertyName("expires_in")]
    public string ExpiresIn { get; set; } = string.Empty;
    [JsonPropertyName("refresh_count")]
    public string RefreshCount { get; set; } = string.Empty;
    [JsonPropertyName("status")]
    public string Status { get; set; } = string.Empty;
}

这就是我成功验证的方式:

string appId = "ABCD1234";
string secret = "WXYZ9876";
HttpClient client = new()
{
    BaseAddress = new Uri(uriString: "https://api.honeywell.com/", uriKind: UriKind.Absolute)
};

    KeyValuePair<string, string>[] encodedContentCollection =
[
    new("Content-Type", "application/x-www-form-urlencoded"),
    new("grant_type", "client_credentials")
];
HttpRequestMessage request = new(HttpMethod.Post, "oauth2/accesstoken")
{
    Content = new FormUrlEncodedContent(encodedContentCollection)
};

string base64AppIdAndSecret = Convert.ToBase64String(Encoding.UTF8.GetBytes($"{appId}:{secret}"));
client.DefaultRequestHeaders.Authorization = new AuthenticationHeaderValue("Basic", base64AppIdAndSecret);

HttpResponseMessage response = await client.SendAsync(request, HttpCompletionOption.ResponseHeadersRead);

response.EnsureSuccessStatusCode(); // Should throw if not 200-299

Stream responseContentStream = await response.Content.ReadAsStreamAsync();

ResideoToken token = await JsonSerializer.DeserializeAsync<ResideoToken>(responseContentStream, JsonSerializerOptions.Default) ?? 
    throw new Exception("Could not deserialize response stream to a ResideoToken");

3.使用提供的访问令牌调用任何 REST API。

我发现的最简单的情况是使用 GET 方法并传递一个参数来获取位置和设备列表:https://developer.honeywellhome.com/lyric/apis/get/locations

// I had this originally, but it was incorrect -> client.DefaultRequestHeaders.Add("Bearer", token.AccessToken);
client.DefaultRequestHeaders.Authentication = new AuthenticationHeaderValue("Bearer", token.AccessToken);

client.DefaultRequestHeaders.Accept.Add(new MediaTypeWithQualityHeaderValue("application/json"));

// Base URL has already been established in the client
// According to the instructions, the apikey is the AppID
HttpResponseMessage locationResponse = await client.GetAsync($"v2/locations?apikey={appId}");

locationResponse.EnsureSuccessStatusCode(); // This is failing with 401 unauthorized

// I am never able to reach this
string result = await locationResponse.Content.ReadAsStringAsync();
Console.WriteLine($"Locations: {result}");

如您所见,GetAsync 调用失败并返回 401。这是例外情况:

Unhandled exception. System.Net.Http.HttpRequestException: Response status code does not indicate success: 401 (Unauthorized).
   at System.Net.Http.HttpResponseMessage.EnsureSuccessStatusCode()

这很奇怪,因为如果我将代码生成的 Base64 字符串打印到控制台,复制它,并在curl调用中使用它,它就会成功:

$ curl -X GET --header "Authorization: Bearer AbCdEfGhIjKlMnOpQrStUvWxYz==" "https://api.honeywell.com/v2/locations?apikey=ABCD1234"

# This prints a huge valid json file describing all the details of the locations and my devices as described in https://developer.honeywellhome.com/lyric/apis/get/locations

问题

第三步是所有问题发生的地方,所以这些是我不确定的事情:

  • 我是否正确地重用了从成功身份验证中获得的访问令牌字符串?
  • 我是否正确地重用了 HttpClient?据我了解,建议对使用相同身份验证标识的同一组请求继续使用相同的实例。
  • 我是否将 Bearer 标头设置在正确的位置作为默认标头,或者我应该手动创建请求并在那里设置标头?如果是后者,我需要怎么做?
  • 我是否将默认请求标头的接受媒体类型设置为有效值“application/json”?如果没有,我需要把它放在哪里?
  • 身份验证时 HttpClient 中最初设置的默认选项是否会导致后续请求调用出现任何问题?换句话说,我是否需要清除默认标头?
  • 我是否将正确的 URL 传递给 GetAsync 调用?它包含基本网址。
  • 直接在 GetAsync url 字符串中设置 GET 参数(apikey=1234ABCD)是否正确?如果不是,正确的方法是什么?
  • 考虑到使用
    curl
    时确实有效,关于如何调试 401 响应有什么建议吗?

提前致谢。

编辑:我修复了设置不记名令牌的行,但我仍然收到完全相同的 401 异常。

c# .net rest .net-core httpclient
1个回答
0
投票

在第 3 步(使用提供的访问令牌调用任何 REST API)我看到您这样做是为了添加不记名令牌:

client.DefaultRequestHeaders.Add("Bearer", token.AccessToken);

我认为您应该这样做来表明您想要添加授权(就像您在步骤 2 中所做的那样):

client.DefaultRequestHeaders.Authorization = new AuthenticationHeaderValue("Bearer", token.AccessToken);
© www.soinside.com 2019 - 2024. All rights reserved.