如何在 C# 中使用服务帐户登录 Google API - 凭据无效

Posted

技术标签:

【中文标题】如何在 C# 中使用服务帐户登录 Google API - 凭据无效【英文标题】:How to login to Google API with Service Account in C# - Invalid Credentials 【发布时间】:2016-11-18 01:25:30 【问题描述】:

我正在拼命尝试让一个简单的服务帐户登录以在 C#、Google API 和 Google Analytics 中工作。我的公司已经将数据导入 Analytics,我可以使用他们的 Query Explorer 来查询信息,但在 .Net 中入门却无处可去。我正在使用带有 PKI 的 Google 生成的 json 文件,因为文档说这样的服务帐户是与 Googla API 进行计算机到计算机通信的正确方法。代码片段:

public static GoogleCredential _cred;
public static string _exePath;

static void Main(string[] args) 
    _exePath = Path.GetDirectoryName(Assembly.GetExecutingAssembly().GetName().CodeBase).Replace(@"file:\", "");
    var t = Task.Run(() => Run());
    t.Wait();


private static async Task Run() 
    try 
        // Get active credential
        using (var stream = new FileStream(_exePath + "\\Default-GASvcAcct-508d097b0bff.json", FileMode.Open, FileAccess.Read)) 
            _cred = GoogleCredential.FromStream(stream);
        
        if (_cred.IsCreateScopedRequired) 
        _cred.CreateScoped(new string[]  AnalyticsService.Scope.Analytics );
        
        // Create the service
        AnalyticsService service = new AnalyticsService(
            new BaseClientService.Initializer() 
                HttpClientInitializer = _cred,
            );
        var act1 = service.Management.Accounts.List().Execute(); // blows-up here

一切编译正常,但是当它执行 Execute() 语句时,会抛出 GoogleApiException 错误:

[无效凭据] 位置[授权 - 标头] 原因[authError] 域[全局]

我错过了什么?

【问题讨论】:

【参考方案1】:

如果您在尝试确定如何创建ServiceAccountCredential 时到达这里,而不直接使用密钥文件,您可能有兴趣知道以下方法(有时)会起作用:

GoogleCredential credential = GoogleCredential.GetApplicationDefault();

ServiceAccountCredential serviceAccountCredential = 
    credential.UnderlyingCredential as ServiceAccountCredential;

【讨论】:

【参考方案2】:

对于 2020 年,调用如下:

using System;
using System.Collections.Generic;
using System.Web.Mvc;
using Google.Apis.Services;
using Google.Apis.Auth.OAuth2;
using System.IO;
using Google.Apis.Sheets.v4;
using Google.Apis.Sheets.v4.Data;

namespace SistemasInfinitos.Controllers.Google.Apis.Sample.MVC4

    public class SpreadsheetseController : Controller
     
        public ActionResult IndexAPI()
        
            //accede a las credenciales
            var stream = new FileStream(Server.MapPath("~/quickstart2-9aaf.json"),
                FileMode.Open
               // FileAccess.Read//SOLO LECTURA
                );
            //abre las credenciales
            var credentials = GoogleCredential.FromStream(stream);

            //virifica las credenciales
            if (credentials.IsCreateScopedRequired)
            
                credentials = credentials.CreateScoped(new string[]  SheetsService.Scope.Spreadsheets );
            
            ///inicializa la api
        var service = new SheetsService(new BaseClientService.Initializer()
            
                HttpClientInitializer = credentials,
                ApplicationName = "SistemasInfinitos",
            );

            // Define los parametros.  
            String spreadsheetId = "1MKxeqXV5UEMXU2yBe_xi0nwjooLhNN6Vk";
            String range = "Sheet1";
            SpreadsheetsResource.ValuesResource.GetRequest request =service.Spreadsheets.Values.Get(spreadsheetId, range);
            // imprime   
            ValueRange response = request.Execute();
            IList<IList<Object>> values = response.Values;
            ViewBag.List = values;
            return View();
        
    

和查看

@
    ViewBag.Title = "IndexAPI";


<div class="col-md-6">
    <h3>Read Data From Google Live sheet</h3>
    <table class="table" id="customers">
        <thead>
            <tr>
                <th>
                    id
                </th>
                <th>
                    Name
                </th>
            </tr>
        </thead>
        <tbody>
            @
                foreach (var item in ViewBag.List)
                
                    <tr>
                        <td>@item[0]</td>
                        <td>@item[1]</td>
                    </tr>

                
            
        </tbody>

    </table>
</div>

【讨论】:

【参考方案3】:

在 2020 年,您无需执行所有这些操作,而且 GoogleCredential 可以正常工作。问题中的代码看起来是正确的,除了一行:

credentials.CreateScoped(new string[]  DriveService.Scope.Drive );

CreateScoped 方法返回凭据的副本。如果你将它重新分配给它自己,它就可以工作。

为了完整起见,这是我完美运行的测试代码:

using (var stream =
            new FileStream("drive-credentials.json", FileMode.Open, FileAccess.Read))
                        
            var credentials = GoogleCredential.FromStream(stream);
            if (credentials.IsCreateScopedRequired)
            
                credentials = credentials.CreateScoped(new string[]  DriveService.Scope.Drive );
            


            var service = new DriveService(new BaseClientService.Initializer()
            
                HttpClientInitializer = credentials,
                ApplicationName = "application name",                    
            );

            FilesResource.ListRequest listRequest = service.Files.List();
            listRequest.PageSize = 10;
            listRequest.Fields = "nextPageToken, files(id, name)";

            // List files.
            IList<Google.Apis.Drive.v3.Data.File> files = listRequest.Execute()
                .Files;
        

【讨论】:

【参考方案4】:

发生了无效凭据错误,因为您指定的范围实际上并未随您的凭据一起发送。我犯了同样的错误,直到我调试后才意识到,在CreateScoped 调用之后仍然在凭据上看到 0 个作用域。

GoogleCredential 是不可变的,因此 CreateScoped 创建一个具有指定范围集的新实例。

像这样使用作用域结果重新分配您的凭据变量,它应该可以工作:

  if (_cred.IsCreateScopedRequired) 
    _cred = _cred.CreateScoped(AnalyticsService.Scope.Analytics);
  

接受的答案有效,因为它以更困难的方式实现相同的目标。

【讨论】:

这对我有帮助,因为我根本没有设置范围,因为我不需要在 python 等效项中设置【参考方案5】:

另一种选择是使用GoogleCredential.GetApplicationDefault()。我相信这是目前(2018 年 10 月)推荐的方法。这是一些 F#,但在 C# 模语法中或多或少是相同的:

let projectId = "<your Google Cloud project ID...>"
let creds =
  GoogleCredential.GetApplicationDefault()
    .CreateScoped(["https://www.googleapis.com/auth/cloud-platform"])
use service =
  new CloudBuildService(
    BaseClientService.Initializer(HttpClientInitializer=creds))
let foo = service.Projects.Builds.List(projectId).Execute()

现在,只需确保将 GOOGLE_APPLICATION_CREDENTIALS 设置为指向带有凭证 JSON 文件的文件,例如。 GOOGLE_APPLICATION_CREDENTIALS=creds.json dotnet run.

【讨论】:

【参考方案6】:

GoogleAnalytics 似乎无法使用泛型 GoogleCredential 并将其解释为 ServiceAccountCredential(即使承认它实际上是该类型)。因此,您必须以艰难的方式创建ServiceAccountCredential。很遗憾GoogleCredential 没有暴露凭证的各种属性,所以我不得不自己构建。 我使用http://jsonclassgenerator.codeplex.com/ 的 JSON C# 类生成器使用作为 Google API (Newtonsoft.Json) 的自动部分的 JSON 库构建“个人”ServiceAccountCredential 对象,检索服务帐户下载的 json 文件的基本部分,使用其电子邮件和私钥属性构建所需的凭据。将真正的 ServiceAccountCredential 传递给 GoogleAnalytics 服务构造函数,会导致成功登录并访问该帐户的允许资源。

以下工作代码示例:

using System;
using System.Diagnostics;
using System.IO;
using System.Reflection;
using System.Text;
using Google.Apis.Auth.OAuth2;
using Google.Apis.Services;
using Google.Apis.Analytics.v3;
using Newtonsoft.Json;
    .
    .
    .
try

    // Get active credential
    string credPath = _exePath + @"\Private-67917519b23f.json";

    var json = File.ReadAllText(credPath);
    var cr = JsonConvert.DeserializeObject<PersonalServiceAccountCred>(json); // "personal" service account credential

    // Create an explicit ServiceAccountCredential credential
    var xCred = new ServiceAccountCredential(new ServiceAccountCredential.Initializer(cr.ClientEmail)
    
        Scopes = new[] 
            AnalyticsService.Scope.AnalyticsManageUsersReadonly,
            AnalyticsService.Scope.AnalyticsReadonly
        
    .FromPrivateKey(cr.PrivateKey));

    // Create the service
    AnalyticsService service = new AnalyticsService(
        new BaseClientService.Initializer()
        
            HttpClientInitializer = xCred,
        
    );

    // some calls to Google API
    var act1 = service.Management.Accounts.List().Execute();

    var actSum = service.Management.AccountSummaries.List().Execute();

    var resp1 = service.Management.Profiles.List(actSum.Items[0].Id, actSum.Items[0].WebProperties[0].Id).Execute();

有些人可能想知道 Google 生成的带有 PKI(私钥)的服务帐户凭据是什么样的。从https://console.developers.google.com/iam-admin/projects 的 Google API 管理器(IAM 和管理员)中,选择适当的项目(您至少拥有其中一个)。现在选择服务帐户(从左侧导航链接),然后在屏幕顶部选择CREATE SERVICE ACCOUNT。填写名称,设置提供新的私钥复选框,然后点击创建。 Google 会自动下载一个 JSON 文件,如下所示:


  "type": "service_account",
  "project_id": "atomic-acrobat-135",
  "private_key_id": "508d097b0bff9e90b8d545f984888b0ef31",
  "private_key": "-----BEGIN PRIVATE KEY-----\nMIIE...o/0=\n-----END PRIVATE KEY-----\n",
  "client_email": "google-analytics@atomic-acrobat-135.iam.gserviceaccount.com",
  "client_id": "1123573016559832",
  "auth_uri": "https://accounts.google.com/o/oauth2/auth",
  "token_uri": "https://accounts.google.com/o/oauth2/token",
  "auth_provider_x509_cert_url": "https://www.googleapis.com/oauth2/v1/certs",
  "client_x509_cert_url": "https://www.googleapis.com/robot/v1/metadata/x509/google-analytics%40atomic-acrobat-135923.iam.gserviceaccount.com"

【讨论】:

看起来有人使用了您的代码并在他的博客上使用了它。因为你在 2016 年回答了这个问题,而他的博客是最近才发布的。我的问题是我可以使用这个获取访问令牌吗?所以我可以将访问令牌用于嵌入 api。因为我得到一个空访问令牌。 我懒得使用你的类生成器。所以我这样做了:Newtonsoft.Json.Linq.JObject cr = (Newtonsoft.Json.Linq.JObject) JsonConvert.DeserializeObject(json); string s = (string) cr.GetValue("private_key"); 谢谢你的回答——它帮了很多忙。 感谢 dlumpp 的回答,我已经能够确认只要您正确设置范围,使用 GoogleCredential.FromStream(或 FromFile)就可以正常工作。 ***.com/users/6753705/dlumpp 这样做会给我一个“MailKit.Security.AuthenticationException: '334: eyJzdGF0dXMiOiI0M...”,但是如果我使用证书它就可以了!太好了…… 我花了一些时间玩这些东西,突然我的钥匙就起作用了。出于兴趣,我更改了一些字符以查看会发生什么,根据我更改的内容,它仍然有效o_O

以上是关于如何在 C# 中使用服务帐户登录 Google API - 凭据无效的主要内容,如果未能解决你的问题,请参考以下文章

如何使用服务帐户通过 .NET C# 访问 Google Analytics API V3?

Google 服务帐户用户界面

如何在JMeter中使用Google帐户登录

如何将 Firebase 帐户关联到 Google Play 游戏帐户

如何在使用 Google 登录按钮时不将 Google 帐户添加到 Android 手机

使用 Google Apps 帐户登录时,在 Android 上对 Google Play 游戏服务进行身份验证会导致错误