简体   繁体   English

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

[英]How to login to Google API with Service Account in C# - Invalid Credentials

I'm beating myself bloody trying to get a simple service acccount login to work in C#, to Google API and Google Analytics.我试图获得一个简单的服务帐户登录以在 C#、Google API 和 Google Analytics 中工作,我正在打自己的血。 My company is already getting data into Analytics, and I can query information with their Query Explorer, but getting started in .Net is not going anywhere.我的公司已经在将数据导入 Analytics,我可以使用他们的 Query Explorer 查询信息,但是在 .Net 中入门还行。 I am using a Google-generated json file with PKI, as the documentation says that such a service account is the proper way for computer-to-computer communication with Googla API.我正在使用带有 PKI 的 Google 生成的 json 文件,因为文档说这样的服务帐户是与 Googla API 进行计算机到计算机通信的正确方式。 Code snipet:代码片段:

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

It all compiles fine, but when it hit the Execute() statement, a GoogleApiException error is thrown:一切都编译得很好,但是当它遇到 Execute() 语句时,会抛出一个GoogleApiException错误:

[Invalid Credentials] Location[Authorization - header] Reason[authError] Domain[global] [Invalid Credentials] Location[Authorization - header] Reason[authError] Domain[global]

What am I missing?我错过了什么?

It appears that the GoogleAnalytics cannot consume a generic GoogleCredential and interpret it as a ServiceAccountCredential (even though it is acknowledged, interally, that it is actually of that type ).似乎 GoogleAnalytics 无法使用通用GoogleCredential并将其解释为ServiceAccountCredential (即使它实际上被承认为该类型)。 Thus you have to create a ServiceAccountCredential the hard way.因此,您必须以艰难的方式创建ServiceAccountCredential It's also unfortunate that GoogleCredential does not expose the various properties of the credential, so I had to build my own.同样不幸的是, GoogleCredential没有公开凭证的各种属性,所以我不得不构建自己的。

I used the JSON C# Class Generator at http://jsonclassgenerator.codeplex.com/ to build a "personal" ServiceAccountCredential object using the JSON library that is an automatic part of Google API (Newtonsoft.Json), retrieved essential parts of the downloaded json file of the service account, to construct the required credential, using its email and private key properties.我使用http://jsonclassgenerator.codeplex.com/上的 JSON C# 类生成器使用 JSON 库构建“个人”ServiceAccountCredential 对象,该对象是 Google API (Newtonsoft.Json) 的自动部分,检索下载的基本部分服务帐户的 json 文件,以使用其电子邮件和私钥属性构建所需的凭据。 Passing a genuine ServiceAccountCredential to the GoogleAnalytics service constructor, results in a successful login, and access to that account's allowed resources.将真正的ServiceAccountCredential传递给 GoogleAnalytics 服务构造函数,会导致成功登录并访问该帐户的允许资源。

Sample of working code below:下面的工作代码示例:

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();

Some may wonder what a Google-generated service account credential with PKI (Private Key) looks like.有些人可能想知道 Google 生成的带有 PKI(私钥)的服务帐户凭据是什么样的。 From the Google APIs Manager (IAM & Admin) at https://console.developers.google.com/iam-admin/projects , select the appropriate project (you have at least one of these).从位于https://console.developers.google.com/iam-admin/projects的 Google API 管理器(IAM 和管理)中,选择适当的项目(您至少拥有其中之一)。 Now select Service accounts (from the left nav links), and CREATE SERVICE ACCOUNT at top of screen.现在选择服务帐户(从左侧导航链接),然后在屏幕顶部创建服务帐户 Fill in a name, set the Furnish a new private key checkbox, then click Create .填写名称,设置Furnish a new private key复选框,然后单击Create Google will cause an automatic download of a JSON file, that looks something like this: 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"
}

The invalid credentials error is happening because the scopes you specified aren't actually getting sent with your credentials.发生无效凭据错误是因为您指定的范围实际上并未随您的凭据一起发送。 I made the same mistake and only realized after I debugged and still saw 0 scopes on the credential after the CreateScoped call.我犯了同样的错误,只有在调试后才意识到,并且在CreateScoped调用后仍然看到凭证上的 0 范围。

A GoogleCredential is immutable so CreateScoped creates a new instance with the specified scopes set. GoogleCredential是不可变的,因此CreateScoped创建一个具有指定范围集的新实例。

Reassign your credentials variable with the scoped result like so and it should work:像这样使用范围结果重新分配您的凭据变量,它应该可以工作:

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

The accepted answer works because it's achieving the same thing in a more difficult way.接受的答案有效,因为它以更困难的方式实现了同样的目标。

In 2020 you don't need to do all this and the GoogleCredential works fine.在 2020 年,您不需要执行所有这些操作,并且 GoogleCredential 可以正常工作。 The code in the question looks correct except for one line:除了一行之外,问题中的代码看起来是正确的:

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

The CreateScoped method returns a copy of the credentials. CreateScoped方法返回凭据的副本 If you reassign it back to itself it just works.如果您将它重新分配回自身,它就可以正常工作。

For the sake of completeness, this is my test code that works perfectly:为了完整起见,这是我完美运行的测试代码:

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

FOR 2020, the call is made as follows:对于 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();
        }
    }
}

and 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>

Another option is to use GoogleCredential.GetApplicationDefault() .另一种选择是使用GoogleCredential.GetApplicationDefault() I believe this is the currently (Oct. 2018) recommended approach.我相信这是目前(2018 年 10 月)推荐的方法。 Here's some F#, but it's more or less the same in C# modulo syntax:这是一些 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()

Now, just make sure that you set the GOOGLE_APPLICATION_CREDENTIALS to point to the file with the credentials JSON file, eg.现在,只需确保将GOOGLE_APPLICATION_CREDENTIALS设置为指向具有凭据 JSON 文件的文件,例如。 GOOGLE_APPLICATION_CREDENTIALS=creds.json dotnet run . GOOGLE_APPLICATION_CREDENTIALS=creds.json dotnet run

If you arrive here while trying to determine how to create a ServiceAccountCredential , without using a key file directly you might be interested to know that the method below will work (sometimes):如果您在尝试确定如何创建ServiceAccountCredential到达这里,而不直接使用密钥文件,您可能有兴趣知道以下方法是否有效(有时):

GoogleCredential credential = GoogleCredential.GetApplicationDefault();

ServiceAccountCredential serviceAccountCredential = 
    credential.UnderlyingCredential as ServiceAccountCredential;

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM