为什么HomeGraph API上的请求同步返回403禁止访问?

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

问题

当我在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密钥

尽管文档未显示,但我已经看到有些人使用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();
        }
    }
}

帐户配置

帐户截图。 (我尚未发布图片,因此它们是链接)

HomeGraph is enabled

My API Key is unrestricted

My Service Account has Owner & Service Account Token Creator enabled

c# actions-on-google google-api-dotnet-client google-smart-home
1个回答
0
投票

我正在发出似乎成功的访问令牌请求。

使用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#示例。

© www.soinside.com 2019 - 2024. All rights reserved.