当我在Google HomeGraph API上调用“请求同步”时,收到“ 403禁止访问”响应。
我正在编写智能家居操作,并已成功实现SYNC,QUERY和EXECUTE。在我的手机上测试,我可以看到设备并与之交互。我现在正在尝试实现请求同步,但似乎无法与API进行交互。我正在发出似乎成功的访问令牌请求。令牌始终以“ ya29.c”开头。在我的天真理解中,这表明标头和有效负载为空(在https://jwt.io上尝试)。但是,在https://accounts.google.com/o/oauth2/tokeninfo?access_token=对其进行测试时,它似乎有效,同时显示了我的服务帐户唯一ID和预期的范围。当我通过手动发布数据或通过Google自己的代码调用API时,出现403错误。除了异常对象外,我不知道在哪里可以获取有关此错误的更多信息。我是GCP的新手,找不到任何日志。鉴于我尝试了不同的方法,并且都返回了403,我倾向于怀疑问题与帐户或凭据相关,而不是代码,但无法确定。
尽管文档未显示,但我已经看到有些人使用API密钥。实际上,必须将其包含在SDK方法中。我在IAM中创建了一个不受限制的API密钥,并且正在使用它。我似乎没有明确地将其与HomeGraph API关联,但它表示可以调用任何API。
此示例获取访问令牌,然后尝试以三种不同方式调用API。每个失败都带有403。
using Google;
using Google.Apis.Auth.OAuth2;
using Google.Apis.HomeGraphService.v1;
using Google.Apis.HomeGraphService.v1.Data;
using Google.Apis.Services;
using Lambda.Core.Constants;
using System.IO;
using System.Net;
using System.Net.Http;
using System.Net.Http.Headers;
using System.Security.Cryptography.X509Certificates;
using System.Text;
using static Google.Apis.HomeGraphService.v1.DevicesResource;
namespace Lambda.Console
{
public class Example
{
public void RequestSync()
{
const string UrlWithoutKey = @"https://homegraph.googleapis.com/v1/devices:requestSync";
const string UrlWithKey = @"https://homegraph.googleapis.com/v1/devices:requestSync?key=" + OAuthConstants.GoogleApiKey;
string accessToken = this.GetAccessToken();
// Manual Attempt 1
try
{
string response = this.CallRequestSyncApiManually(accessToken, UrlWithoutKey);
}
catch (WebException ex)
{
// Receive 403, Forbidden
string msg = ex.Message;
}
// Manual Attempt 2
try
{
string response = this.CallRequestSyncApiManually(accessToken, UrlWithKey);
}
catch (WebException ex)
{
// Receive 403, Forbidden
string msg = ex.Message;
}
// SDK Attempt
try
{
this.CallRequestSyncApiWithSdk();
}
catch (GoogleApiException ex)
{
// Google.Apis.Requests.RequestError
// The caller does not have permission[403]
// Errors[Message[The caller does not have permission] Location[- ] Reason[forbidden] Domain[global]]
// at Google.Apis.Requests.ClientServiceRequest`1.ParseResponse(HttpResponseMessage response) in Src\Support\Google.Apis\Requests\ClientServiceRequest.cs:line 243
// at Google.Apis.Requests.ClientServiceRequest`1.Execute() in Src\Support\Google.Apis\Requests\ClientServiceRequest.cs:line 167
string msg = ex.Message;
}
}
private string GetAccessToken()
{
string defaultScope = "https://www.googleapis.com/auth/homegraph";
string serviceAccount = OAuthConstants.GoogleServiceAccountEmail; // "??????@??????.iam.gserviceaccount.com"
string certificateFile = OAuthConstants.CertificateFileName; // "??????.p12"
var oAuth2 = new GoogleOAuth2(defaultScope, serviceAccount, certificateFile); // As per https://stackoverflow.com/questions/26478694/how-to-produce-jwt-with-google-oauth2-compatible-algorithm-rsa-sha-256-using-sys
bool status = oAuth2.RequestAccessTokenAsync().Result;
// This access token at a glance appears invalid due to an empty header and payload,
// But verifies ok when tested here: https://accounts.google.com/o/oauth2/tokeninfo?access_token=
return oAuth2.AccessToken;
}
private string CallRequestSyncApiManually(string accessToken, string url)
{
string apiRequestBody = @"{""agentUserId"": """ + OAuthConstants.TestAgentUserId + @"""}";
var client = new HttpClient();
var request = (HttpWebRequest)WebRequest.Create(url);
var data = Encoding.ASCII.GetBytes(apiRequestBody);
request.Method = "POST";
request.Accept = "application/json";
request.ContentType = "application/json";
request.ContentLength = data.Length;
request.Headers.Add("Authorization", $"Bearer {accessToken}");
client.DefaultRequestHeaders.Authorization = new AuthenticationHeaderValue("Bearer", accessToken);
using (var stream = request.GetRequestStream())
{
stream.Write(data, 0, data.Length);
}
var response = (HttpWebResponse)request.GetResponse();
var responseString = new StreamReader(response.GetResponseStream()).ReadToEnd();
return responseString;
}
private void CallRequestSyncApiWithSdk()
{
var certificate = new X509Certificate2(OAuthConstants.CertificateFileName, OAuthConstants.CertSecret, X509KeyStorageFlags.Exportable);
var credential = new ServiceAccountCredential(
new ServiceAccountCredential.Initializer(OAuthConstants.GoogleServiceAccountEmail)
{
Scopes = new[] { "https://www.googleapis.com/auth/homegraph" },
}.FromCertificate(certificate));
var service = new HomeGraphServiceService(
new BaseClientService.Initializer()
{
// Complains if API key is not provided, even though we're using a certificate from a Service Account
ApiKey = OAuthConstants.GoogleApiKey,
HttpClientInitializer = credential,
ApplicationName = OAuthConstants.ApplicationName,
});
var request = new RequestSyncRequest(
service,
new RequestSyncDevicesRequest
{
AgentUserId = OAuthConstants.TestAgentUserId
});
request.Execute();
}
}
}
帐户截图。 (我尚未发布图片,因此它们是链接)
My Service Account has Owner & Service Account Token Creator enabled
我正在发出似乎成功的访问令牌请求。
使用Google客户端库时,您无需手动请求OAuth访问令牌。他们通常使用您从GCP控制台提供的凭据在内部处理此过程。
尽管文档未显示,但我已经看到有些人使用API密钥。实际上,必须将其包含在SDK方法中。
我们不建议使用API密钥方法来访问Home Graph API。您应该使用服务帐户凭据。 API密钥在技术上可以用于Request Sync方法,但是您将无法使用API密钥对Report State进行身份验证。
您在没有API密钥的情况下尝试构建HomeGraphServiceService
时遇到错误,这可能表明您使用的凭据未正确设置(没有私钥或可能缺少范围)。提供服务帐户凭据的推荐方法是以JSON格式而不是证书下载它们,并且从JSON生成凭据的代码应如下所示:
GoogleCredential credential;
using (var stream = new FileStream(serviceAccountCredentialFilePath, FileMode.Open, FileAccess.Read))
{
credential = GoogleCredential.FromStream(stream).CreateScoped(scopes);
}
您可以在authentication guide中找到其他用于验证API的C#示例。