9

我在这里遵循了示例代码: https ://code.google.com/p/google-api-dotnet-client/wiki/OAuth2#Service_Accounts

授权失败:DotNetOpenAuth.Messaging.ProtocolException:发送直接消息或获取响应时发生错误。

内部异常是 System.Net.WebException: The remote server returned an error: (400) Bad Request。响应正文为空,响应 URI 为https://accounts.google.com/o/oauth2/token

在下面的响应中,您将看到特定错误是 invalid_grant。

这是我的代码:

var certificate = new X509Certificate2(CertificatePath, "notasecret", X509KeyStorageFlags.Exportable);
var provider = new AssertionFlowClient(GoogleAuthenticationServer.Description, certificate)
    {
        ServiceAccountId = "<...>@developer.gserviceaccount.com",
        Scope = CalendarService.Scopes.Calendar.GetStringValue()
    };

var authenticator = new OAuth2Authenticator<AssertionFlowClient>(provider, AssertionFlowClient.GetState);

var calendarService =
    new CalendarService(new BaseClientService.Initializer()
        {
            Authenticator = authenticator
        });

var eventList = calendarService.Events.List("<id of the calendar>").Execute();

证书和 ServiceAccountId 正确。我进行了三次检查,并且已经重新生成了证书。Google 日历 API 在 API 控制台中为用于创建服务帐户的 google 开发人员帐户打开。此帐户不属于 Google Apps 域。

我还使用指定的 AssertionFlowClient 的 ServiceAccountUser 属性对此进行了测试。我现在认为这是必需的 - 在我使用手动创建的 JWT 成功测试 CalendarService 时(请参阅下面的 OAuth 令牌的手动创建工作),当 prn 属性不包含在尝试创建令牌时,我收到 404 错误声明(即不包括 ServiceAccountUser)。

Google Apps 域配置

在 Google Apps 域中,我已授予此服务帐户访问日历的权限。

客户名称:[snip].apps.googleusercontent.com

API 范围:

已安装的 NuGet 包

  • Google.Apis(1.5.0-beta)
  • Google.Apis.Calendar.v3 (1.5.0.59-beta)
  • Google.Apis.Authentication(1.5.0-beta)

请求和响应

POST https://accounts.google.com/o/oauth2/token HTTP/1.1
Content-Type: application/x-www-form-urlencoded; charset=utf-8
User-Agent: DotNetOpenAuth/4.0.0.11165
Host: accounts.google.com
Cache-Control: no-store,no-cache
Pragma: no-cache
Content-Length: 606
Connection: Keep-Alive

grant_type=assertion&assertion_type=http%3A%2F%2Foauth.net%2Fgrant_type%2Fjwt%2F1.0%2Fbearer&assertion=eyJhbGciOiJSUzI1NiIsInR5cCI6IkpXVCJ9.eyJpc3MiOiI5NzUzOTk3NzMyNi01NHFvMXY4OW5iZTk4dGNlbGIycWY0cDdjNThzYjhmMkBkZXZlbG9wZXIuZ3NlcnZpY2VhY2NvdW50LmNvbSIsInNjb3BlIjoiaHR0cHM6Ly93d3cuZ29vZ2xlYXBpcy5jb20vYXV0aC9jYWxlbmRhciIsImF1ZCI6Imh0dHBzOi8vYWNjb3VudHMuZ29vZ2xlLmNvbS9vL29hdXRoMi90b2tlbiIsImV4cCI6MTM3OTU5MTA4MywiaWF0IjoxMzc5NTg3NDgzfQ.Ls_sv40MfB8MAD92JFcFiW5YYoRytQ3e2PA8RV_hn4FJfVHDo6uCSunN7950H2boO6LfX9EMrpjaf8ZyNyHyrQucQaWwfIFD6F2FpnqlcNkzXoqWMCwkt-k-8ypGMSZfFCEkhw8QOrlIPFZb6qx61689n08G9tZMTzHGYc2b8Gk

仔细检查,断言似乎是正确的,在这里解码:

{"alg":"RS256","typ":"JWT"}{"iss":"97539977326-54qo1v89nbe98tcelb2qf4p7c58sb8f2@developer.gserviceaccount.com","scope":"https://www.googleapis.com/auth/calendar","aud":"https://accounts.google.com/o/oauth2/token","exp":1379591083,"iat":1379587483}

HTTP/1.1 400 Bad Request
Cache-Control: no-cache, no-store, max-age=0, must-revalidate
Pragma: no-cache
Expires: Fri, 01 Jan 1990 00:00:00 GMT
Date: Thu, 19 Sep 2013 10:44:42 GMT
Content-Type: application/json
X-Content-Type-Options: nosniff
X-Frame-Options: SAMEORIGIN
X-XSS-Protection: 1; mode=block
Server: GSE
Alternate-Protocol: 443:quic
Content-Length: 31

{
  "error" : "invalid_grant"
}

手动创建 OAuth 令牌作品

为了确认我的设置正确,我使用 google-oauth-jwt 手动创建了一个令牌(此处:https ://github.com/extrabacon/google-oauth-jwt )。我能够使用上面代码中使用的相同属性成功创建令牌。一旦我创建了令牌并在自定义 IAuthenticator 中使用它,我就可以成功地从目标 Google Apps 域中的用户日历中检索事件。因此,如果您想知道,可以使用服务帐户访问日历!

这是 IAuthenticator 实现,它只是添加了 Authorization 标头:

public class Authenticator : IAuthenticator
{
  public void ApplyAuthenticationToRequest(System.Net.HttpWebRequest request)
  {
    request.Headers.Add(HttpRequestHeader.Authorization, "Bearer <token here>");
  }
}
4

3 回答 3

2

我不确定自从提出这个问题后是否发生了一些变化,但 Google 日历确实支持服务帐户。

设置用于 Google Calendar API 的服务帐户时,您只需使用服务帐户电子邮件地址。访问 Google 日历网站。找到日历设置,然后转到日历选项卡,找到您要访问的日历并单击“共享:编辑设置”添加服务帐户电子邮件地址,就像您添加个人电子邮件地址一样。这将为服务帐户提供与您与任何其他用户共享该服务帐户相同的访问权限。

string[] scopes = new string[] {
    CalendarService.Scope.Calendar, // Manage your calendars
    CalendarService.Scope.CalendarReadonly // View your Calendars
 };

 var certificate = new X509Certificate2(keyFilePath, "notasecret", X509KeyStorageFlags.Exportable);

ServiceAccountCredential credential = new ServiceAccountCredential(
    new ServiceAccountCredential.Initializer(serviceAccountEmail) {
        Scopes = scopes
    }.FromCertificate(certificate));

// Create the service.
    CalendarService service = new CalendarService(new BaseClientService.Initializer() {
        HttpClientInitializer = credential,
        ApplicationName = "Calendar API Sample",
    });

Google Calendar API Authentication C#中提取的代码

于 2014-12-17T13:32:26.003 回答
1

据我所知,日历服务不支持服务帐户。

您可以使用与您相同的代码检查 G+ 示例是否有效(https://code.google.com/p/google-api-dotnet-client/source/browse/Plus.ServiceAccount/Program.cs?repo=samples )正在尝试在此处运行日历 API。谷歌加支持服务帐户,所以它应该工作。

为什么不能运行 OAuth2 用户流而不是服务帐户?

更新:毕竟,日历 API 支持服务帐户。对困惑感到抱歉。

于 2013-09-21T00:10:10.950 回答
0

我建议在DaImTo给出的答案中使用 ServiceAccountCredential ,但您可以以编程方式指定用户的电子邮件。您不必进入他们的谷歌日历设置:

string serviceAccountEmail = "mail@project.iam.gserviceaccount.com";

// Read certificate from PKCS#12 file downloaded from the Google Developer Console
var certificate = new X509Certificate2(@"files/projectKey.p12", "notasecret",
    X509KeyStorageFlags.Exportable);

// Create Service Account Credential to add to specific user's calendar
ServiceAccountCredential credential = new ServiceAccountCredential(
    new ServiceAccountCredential.Initializer(serviceAccountEmail)
    {
        User = userEmail,
        Scopes = new[] { CalendarService.Scope.Calendar } // Manage user's calendars
    }.FromCertificate(certificate));
}

// Initialize service using credential
CalendarService service = new CalendarService(new BaseClientService.Initializer()
{
      HttpClientInitializer = credential,
      ApplicationName = "project"
});

一旦我在 Google Developers Console 中为我的项目创建了服务凭据并启用了 Google Calendars API,这对我的应用程序有效——我能够在用户的日历中创建/读取/更新/删除事件。

于 2016-07-11T18:05:43.827 回答