Google API OAuth2, Сервисный аккаунт, "ошибка": "invalid_grant"
Я пытаюсь использовать служебную учетную запись для синхронизации календарей из программного обеспечения Dynamics CRM с Google. Во время этого я столкнулся с отсутствием документации по API Google для.net, особенно в отношении авторизации. Большинство примеров Google не могут быть скомпилированы даже из-за устаревших библиотек и используемых классов.
Таким образом, я нашел некоторый пример через интернированный и получаю ошибку. Может ли кто-нибудь посмотреть на мой образец и сказать, что я делаю не так?
Подготовительные шаги:
- Я создал проект в моей личной учетной записи Google.
- В консоли разработчика проекта в разделе APIS & AUTH -> Credentials я создал служебную учетную запись. Затем нажмите "Создать ключ P12" и загрузите файл.p12.
- Под APIS & AUTH -> API, включен "Календарь API"
Затем создал консольное приложение и смог установить пакеты OAuth и Calendar nuget. Есть:
- Клиентская библиотека аутентификации API Google, Google.Apis.Auth 1.8.1
- Клиентская библиотека API Google, Google.Apis 1.8.1
- Базовая клиентская библиотека API Google, Id: Google.Apis.Core 1.8.1
- 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();
}
}
}
Связь с Google API, которую я вижу в приложении и Fiddler:
Запрос:
Хост: HTTPS account.google.com, URL: /o/oauth2/token
Утверждение: длинная двоичная строка
тип_транспорта: urn:ietf: параметры: oauth: тип-гранта:jwt-носитель
Отклик:
HTTP / 1.1 400 Bad Request Content-Type: application / json Cache-Control: без кеша, без хранилища, max-age=0, обязательно повторная проверка Pragma: no-cache Истекает: пятница, 01 января 1990 00:00:00 GMT Дата: четверг, 24 июля 2014 г. 06:12:18 GMT Параметры X-Content-Type: nosniff Параметры X-Frame: SAMEORIGIN X-XSS-Protection: 1; mode=block Сервер: GSE Alternate-Protocol: 443: quic Transfer-Encoding: chunked
1f {"error": "invalid_grant"} 0
Пожалуйста, помогите и спасибо заранее!
3 ответа
После некоторых исследований я обнаружил, что Google API не работает должным образом с вашей личной учетной записью @gmail.com. У вас должна быть учетная запись домена организации в Google в формате you@your_organisation_domain
Затем, что также довольно запутанно, на странице API Google Диска есть документация с разделом "Делегировать полномочия для всего домена вашей учетной записи службы", не упомянутым на странице "Календарь API". В разделе 7 шагов, которые необходимо выполнить.
Кстати, с администрацией личного кабинета сайт admin.google.com даже недоступен. Таким образом, невозможно выполнить эти 7 шагов с учетной записью @gmail.com.
Затем, когда клиент авторизован в консоли администратора 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). Третье - одно мероприятие на один час. Последний создан кодом выше.
Надеюсь, что это поможет другим сэкономить свое время.
У меня возникла та же проблема, она работала нормально на удаленном сервере, но на локальном сервере я дошел до этой ошибки, долго рассказывая после нескольких часов головной боли, я обнаружил, что проблема была в моих системных часах, просто выполните этот шаг
1. Нажмите на часы в вашей системе
- Это поднимет календарь и время
2. Нажмите Изменить настройки даты и времени...
- Откроется диалоговое окно "Дата и время"
3. Нажмите на вкладку Интернет время
4. Нажмите Изменить настройки
- Откроется диалоговое окно "Настройки времени Интернета".
затем, наконец, обновите ваши часы с одним из серверов времени
Я не совсем уверен, что не так с вашим кодом, я думаю, это часть того, как вы загружаете файл ключа. Это также может быть тот факт, что вам не нужно отправлять пользователя с ServiceAccountCredential, поскольку учетная запись службы - это пользователь, в который вы входите. Я не уверен, почему вы отправляете кому-то письмо 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 календарей, вам нужно помнить, чтобы создать его в первую очередь.