【问题标题】:Google API OAuth2, Service Account, "error" : "invalid_grant"Google API OAuth2,服务帐户,“错误”:“invalid_grant”
【发布时间】:2014-07-25 01:05:01
【问题描述】:

我正在尝试使用服务帐户将日历从 Dynamics CRM 软件同步到 Google。 在此期间,我面临缺乏关于 .net 的 google API 的文档,尤其是关于授权的文档。由于使用了过时的库和类,大多数 Google 示例甚至无法编译。

所以我在实习中找到了一些示例并收到错误。 有人可以看看我的样本并告诉我我做错了什么吗?

准备步骤:

  1. 我在我的私人 Google 帐户中创建了一个项目。
  2. 在项目开发者控制台中,在 APIS & AUTH -> Credentials 下,我生成了服务帐户。然后点击“生成 P12 密钥”并下载 .p12 文件。
  3. 在 APIS 和 AUTH -> API 下,打开“日历 API”

然后创建控制台应用程序并设法安装 OAuth 和日历 nuget 包。有:

  1. Google API 身份验证客户端库,Google.Apis.Auth 1.8.1
  2. Google API 客户端库,Google.Apis 1.8.1
  3. Google API 核心客户端库,ID:Google.Apis.Core 1.8.1
  4. Google.APIs.Calendar.v3 客户端库,Google.Apis.Calendar.V3 1.8.1.860

找到了符合我需要的代码:

using System;
using System.Security.Cryptography.X509Certificates;
using Google.Apis.Calendar.v3;
using Google.Apis.Auth.OAuth2;
using Google.Apis.Services;

namespace CrmToGoogleCalendar
{
    class Program
    {

        static void Connect()
        {
            var certificate = new X509Certificate2("My Project-ee7facaa2bb1.p12", "notasecret", X509KeyStorageFlags.Exportable);


            var serviceAccountEmail = "506310960175-q2k8hjl141bml57ikufinsh6n8qiu93b@developer.gserviceaccount.com";
            var userAccountEmail = "<my email>@gmail.com";
            var credential = new ServiceAccountCredential(new ServiceAccountCredential.Initializer(serviceAccountEmail) 
                {
                    User = userAccountEmail,
                    Scopes = new[] { CalendarService.Scope.Calendar }
                }
                .FromCertificate(certificate));

            var service = new CalendarService(new BaseClientService.Initializer()
            {
                ApplicationName = "Test calendar sync app",
                HttpClientInitializer = credential

            });

            var calList = service.CalendarList.List().Execute().Items;


            foreach (var cal in calList)
            {
                Console.WriteLine(cal.Id);
            }
        }


        static void Main(string[] args)
        {
            Connect();
        }
    }
}

我在应用程序和 Fiddler 中看到的与 Google API 的通信是:

请求:

主机:HTTPS accounts.google.com,网址:/o/oauth2/token
断言:长二进制字符串
grant_type: urn:ietf:params:oauth:grant-type:jwt-bearer

回复:

HTTP/1.1 400 错误请求内容类型:application/json 缓存控制: no-cache, no-store, max-age=0, must-revalidate Pragma: no-cache 到期时间:格林威治标准时间 1990 年 1 月 1 日星期五 00:00:00 日期:2014 年 7 月 24 日星期四 06:12:18 GMT X-Content-Type-Options:nosniff X-Frame-Options:SAMEORIGIN X-XSS-防护:1;模式=块服务器:GSE 备用协议: 443:quic 传输编码:分块

1f { "error" : "invalid_grant" } 0

请提前帮助和感谢!

【问题讨论】:

  • 你试过stackoverflow.com/questions/10576386/…的答案了吗?
  • 嗨@luc。我发现,在谷歌应用程序网页中打开日历后,它开始以Error:"unauthorized_client", Description:"Unauthorized client or scope in request.", Uri:"" 响应。所以同样的代码出现另一个错误。我仍然认为我在正确配置 google 时遗漏了一些东西
  • 我在生产服务器上收到此错误。在搜索了这样的答案后,我终于发现这是由于系统时钟关闭所致。 stackoverflow.com/questions/11973162/…

标签: oauth google-api google-calendar-api google-oauth google-api-dotnet-client


【解决方案1】:

经过一些调查,我发现 Google API 无法正常使用您的个人帐户 @gmail.com。您应该在 Google 中有组织域帐户,格式为 you@your_organisation_domain

然后,同样令人困惑的是,Google Drive API page 上有文档,其中 “将域范围的权限委托给您的服务帐户” 部分在日历中未提及API 页面。 该部分有 7 个步骤,必须完成。

顺便说一句,个人帐户管理网站 admin.google.com 甚至不可用。因此,使用@gmail.com 帐户执行这 7 个步骤是不可能的。

然后,当客户端在 Google Apps 管理控制台 > 安全 > 高级设置 > 管理 OAuth 客户端访问权限 中获得授权时,代码开始工作。

有一个代码对我有用:

using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Security.Cryptography.X509Certificates;
using Google.Apis.Calendar.v3;
using Google.Apis.Calendar.v3.Data;
using Google.Apis.Auth.OAuth2;
using Google.Apis.Services;

namespace CrmToGoogleCalendar
{
    class Program
    {

        static void Connect()
        {
            Console.WriteLine("Calendar via OAuth2 Service Account Sample");

            var certificate = new X509Certificate2("My MC Project-3f38defdf4e4.p12", "notasecret", X509KeyStorageFlags.Exportable);
            var serviceAccountEmail = "795039984093-c6ab1mknpediih2eo9cb70mc9jpu9h03@developer.gserviceaccount.com";
            var userAccountEmail = "me@testdomain.com"; 
            var credential = new ServiceAccountCredential(new ServiceAccountCredential.Initializer(serviceAccountEmail) 
                {
                    User = userAccountEmail,
                    Scopes = new[] { CalendarService.Scope.Calendar }
                }
                .FromCertificate(certificate));

            var service = new CalendarService(new BaseClientService.Initializer()
            {
                ApplicationName = "Test calendar sync app",
                HttpClientInitializer = credential

            });

            /* Get list of calendars */
            var calList = service.CalendarList.List().Execute().Items;
            var myCalendar = calList.First(@c => @c.Id == userAccountEmail);

            /* CREATE EVENT */
            var event1 = new Event()
                {
                    Kind = "calendar#event",
                    Summary = "Calendar event via API",
                    Description = "Programmatically created",
                    Status = "confirmed",
                    Organizer = new Event.OrganizerData() {
                        Email = userAccountEmail
                    },
                    Start = new EventDateTime()
                        {
                            DateTime = DateTime.Now.AddDays(1)
                        },
                    End = new EventDateTime()
                    {
                        DateTime = DateTime.Now.AddDays(1).AddHours(1)
                    },
                    ColorId = "6",
                    Reminders = new Event.RemindersData()
                        {
                            UseDefault = false,
                            Overrides = new List<EventReminder>(
                                new [] {
                                    new EventReminder()
                                        {
                                            Method = "popup",
                                            Minutes = 60
                                        }
                                })
                        }
                };

            event1 = service.Events.Insert(event1, myCalendar.Id).Execute();
            Console.WriteLine("Created event Id: {0}", event1.Id);


            /* ENLIST EVENTS */
            Console.WriteLine("calendar id={0}", myCalendar.Id);
            var events = service.Events.List(myCalendar.Id).Execute();
            foreach (var @event in events.Items)
            {
                Console.WriteLine("Event ID: {0}, ICalUID: {1}", @event.Id, @event.ICalUID);
                Console.WriteLine("  Name: {0}", @event.Summary);
                Console.WriteLine("  Description: {0}", @event.Description);
                Console.WriteLine("  Status: {0}", @event.Status);
                Console.WriteLine("  Color: {0}", @event.ColorId);
                Console.WriteLine("  Attendees: {0}", @event.Attendees == null ? "" : @event.Attendees.Select(a => a.Email).ToString());
                Console.WriteLine("  Kind: {0}", @event.Kind);
                Console.WriteLine("  Location: {0}", @event.Location);
                Console.WriteLine("  Organizer: {0}", @event.Organizer.Email);
                Console.WriteLine("  Recurrence: {0}", @event.Recurrence == null ? "no recurrence" : String.Join(",", @event.Recurrence));
                Console.WriteLine("  Start: {0}", @event.Start.DateTime == null ? @event.Start.Date : @event.Start.DateTime.ToString());
                Console.WriteLine("  End: {0}", @event.End.DateTime == null ? @event.End.Date : @event.End.DateTime.ToString());
                Console.WriteLine("  Reminders: {0}", @event.Reminders.UseDefault.Value ? "Default" : "Not defailt, " + 
                    (@event.Reminders.Overrides == null ? "no overrides" : String.Join(",", @event.Reminders.Overrides.Select(reminder => reminder.Method + ":" + reminder.Minutes)))
                    );
                Console.WriteLine("=====================");
            }

            Console.ReadKey();
        }


        static void Main(string[] args)
        {
            Connect();
        }
    }
}

产生的输出看起来是这样的:

Calendar via OAuth2 Service Account Sample
Created event Id: jkits4dnpq6oflf99mfqf1kdo0
calendar id=me@testdomain.com
Event ID: 1logvocs77jierahutgv962sus, ICalUID: 1logvocs77jierahutgv962sus@google.com
  Name: test event
  Description: test description2
  Status: confirmed
  Color: 
  Attendees: 
  Kind: calendar#event
  Location: location2
  Organizer: me@testdomain.com
  Recurrence: RRULE:FREQ=WEEKLY;BYDAY=TH
  Start: 2014-07-31
  End: 2014-08-01
  Reminders: Not defailt, email:10,popup:10
=====================
Event ID: 1logvocs77jierahutgv962sus_20140814, ICalUID: 1logvocs77jierahutgv962sus@google.com
  Name: test event updated
  Description: test description2
  Status: confirmed
  Color: 
  Attendees: 
  Kind: calendar#event
  Location: location2
  Organizer: me@testdomain.com
  Recurrence: no recurrence
  Start: 2014-08-14
  End: 2014-08-15
  Reminders: Not defailt, email:10
=====================
Event ID: 974hqdhh8jhv5sdobkggmdvvd8, ICalUID: 974hqdhh8jhv5sdobkggmdvvd8@google.com
  Name: One hour event
  Description: test description
  Status: confirmed
  Color: 7
  Attendees: 
  Kind: calendar#event
  Location: Meeting Room Hire, Broadway, 255 The Bdwy, Broadway, NSW 2007, Australia
  Organizer: me@testdomain.com
  Recurrence: no recurrence
  Start: 1/08/2014 10:00:00 AM
  End: 1/08/2014 11:00:00 AM
  Reminders: Default
=====================
Event ID: jkits4dnpq6oflf99mfqf1kdo0, ICalUID: jkits4dnpq6oflf99mfqf1kdo0@google.com
  Name: Calendar event via API
  Description: Programmatically created
  Status: confirmed
  Color: 6
  Attendees: 
  Kind: calendar#event
  Location: 
  Organizer: me@testdomain.com
  Recurrence: no recurrence
  Start: 2/08/2014 12:30:50 PM
  End: 2/08/2014 1:30:50 PM
  Reminders: Not defailt, popup:60
=====================

第一个活动是每周一次的全天活动系列。 第二个是更新第一个事件的单个事件(具有相同的 UID)。 第三是一小时的单项活动。 最后一个是由上面的代码创建的。

希望这将有助于其他人节省时间。

【讨论】:

  • 你确定吗?我需要有一个谷歌邮箱才能访问这些服务?
  • 我遇到了同样的问题。现在我从您的回答中了解到,如果我想使用 Service account 来访问 Google API(在我的情况下是 Calendar API),这是强制性的(但不是由 Google 解释!)拥有一个 Google Admin Console 帐户,这是一项 30 天免费试用服务,然后变成一项付费服务​​......对吗?所以我只能使用 Web 应用程序帐户...
  • @CheshireCat 您可以使用您的个人 gmail 帐户访问 Google Calendar API。服务帐户用于域范围的身份验证。
  • This 是我的问题。我终于使用服务帐户使它工作。请参阅我的问题末尾的更新部分。
  • @CheshireCat,服务帐户可以与任何常规 gmail 帐户一起使用吗?我的意思是如果我们没有任何域帐户?如果没有域帐户,则无法登录域管理控制面板。但是任何 gmail 帐户都可以使用开发者控制台。
【解决方案2】:

我遇到了同样的问题,它在远程服务器上工作正常,但在本地服务器上我遇到了那个错误,长话短说,经过几个小时的头痛,我发现问题出在我的系统时钟上,只需完成这些步骤

1.点击系统中的时钟

  • 这将调出日历和时间

2。点击更改日期和时间设置...

  • 将出现日期和时间对话框

3.点击互联网时间标签

4.点击更改设置

  • 将出现 Internet 时间设置对话框。

然后最后用其中一个时间服务器更新你的时钟

【讨论】:

    【解决方案3】:

    我不太确定您的代码有什么问题,我认为它是您加载密钥文件的一部分。也可能是您不需要发送 UserServiceAccountCredential,因为服务帐户是您正在登录的用户。我不确定您为什么要向某人发送 Gmail 电子邮件。

    using Google.Apis.Auth.OAuth2;
    using System.Security.Cryptography.X509Certificates;
    using Google.Apis.Calendar.v3;
    namespace GoogleAnalytics.Service.Account
    {
        class Program
        {
            static void Main(string[] args)
            {
                //Install-Package Google.Apis.Calendar.v3
                string serviceAccountEmail = "1046123799103-6v9cj8jbub068jgmss54m9gkuk4q2qu8@developer.gserviceaccount.com";
                var certificate = new X509Certificate2(@"C:\Users\HP_User\Documents\GitHub\Google-Analytics-dotnet-ServiceAccount\GoogleAnalytics.Service.Account\Diamto Test Everything Project-bc63fd995bd7.p12", "notasecret", X509KeyStorageFlags.Exportable);
    
                ServiceAccountCredential credential = new ServiceAccountCredential(
                       new ServiceAccountCredential.Initializer(serviceAccountEmail)
                       {
                           Scopes = new[] { CalendarService.Scope.Calendar }
                       }.FromCertificate(certificate));
    
                // Create the service.
                var service = new CalendarService(new CalendarService.Initializer()
                {
                    HttpClientInitializer = credential,
                    ApplicationName = "CalendarService API Sample",
                });
    
                // define the new Calendar
                Google.Apis.Calendar.v3.Data.Calendar calendar = new Google.Apis.Calendar.v3.Data.Calendar();
                calendar.Description = "New Calendar";
                calendar.Summary = "New Calendar Summary";
                // Insert the Calendar
                service.Calendars.Insert(calendar).Execute();
                // List The Calendar
                var calList = service.CalendarList.List().Execute().Items;
    
            }
        }
    }
    

    此代码经过测试,它向您展示了如何为服务帐户创建初始日历。如果不创建日历,它将返回 0 个日历,您需要记住先创建一个。

    【讨论】:

    • 感谢您的评论。我正在使用我的 gmail 电子邮件,因为我正在使用我的私人帐户。这不是域帐户,这是我的 google gmail 帐户。
    • 当我在 ServiceAccountCredential 中省略用户时,它会以 0 个日历回答。我打开我的谷歌日历,看到了一个默认的日历和任务。当用户指定时,仍然代码返回错误,如果未指定,则返回无数据。我打开日历后错误文本更改为Error:"unauthorized_client", Description:"Unauthorized client or scope in request.", Uri:""
    • 服务帐户不是您,它是由服务帐户电子邮件地址定义的自己的实体。即使您在开发控制台中亲自创建了服务帐户,但这并不意味着它可以访问您的 Google 日历,因为它不是您。您可以尝试将服务帐户电子邮件地址添加到您的日历中,这可能会使其访问您的日历,但我没有尝试过。
    • 谢谢@DalmTo,我找到了答案,你是对的 - 使用了错误的帐户。尽管日历 API 页面上没有提到该 API,但它并不适用于个人帐户。不过,Drive 页面上有一个很好的文档。
    • 如果您发现文档中存在差异,请向下滚动到页面底部,应该有一个反馈链接。如果我没记错的话,发送一些反馈 Google 每 3 个月检查一次文档。这将确保它为下一个人固定。
    猜你喜欢
    • 2014-09-22
    • 1970-01-01
    • 2014-07-19
    • 1970-01-01
    • 1970-01-01
    • 2013-08-21
    • 2023-03-15
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多