19

首先,让我解释一下我要做什么,因为这是一个两部分的问题。

我正在构建一个 JAX-RS 服务,该服务通过 OAuth2 在内部使用 Google 帐户进行身份验证,因此它可以访问和操作 Google 日历。该服务将由一个网站 (Joomla) 调用,该网站将允许登录该网站的用户在日历活动中注册他们的电子邮件地址,这样他们就可以在活动临近时收到电子邮件通知。这些用户不知道底层的 Google 帐户。

所以我的第一个问题是,使用服务帐户身份验证是否正确?查看 Google 文档,它是满足非人工身份验证的唯一用例(因此服务器到服务器身份验证)。

第二个问题是我已经编写了一些代码来执行此操作,但是当我调用日历提要的执行方法时,我得到了 401/未经授权的响应,返回了以下错误。此代码取自 Google 示例。

我正在使用 Google Calendar API 的 v3-rev3-1.5.0-beta。我已经在帐户中打开了日历 API,并创建并下载了一个私钥,该私钥在下面的代码中使用。

我在 Eclipse 中本地调用下面的代码,而不是从 Web 服务器调用。

所以我做的第一个调用是取回一个凭证对象(Id 被 X 混淆了):

private static final HttpTransport HTTP_TRANSPORT = new NetHttpTransport();
private static final JsonFactory JSON_FACTORY = new JacksonFactory();
.
.
.
GoogleCredential credential = new GoogleCredential.Builder().setTransport(HTTP_TRANSPORT)
            .setJsonFactory(JSON_FACTORY)
            .setServiceAccountId("284XXXXXXXX@developer.gserviceaccount.com")
            .setServiceAccountScopes(CalendarScopes.CALENDAR)
            .setServiceAccountPrivateKeyFromP12File(new File("D:/3cd8XXXXXXXXXXXXXXXXXXXXXXXXXXXXXd635e-privatekey.p12"))
            .build();

下一步是调用 Calendar API,如下所示(中间没有调用其他代码):

 Calendar calendar = Calendar.builder(HTTP_TRANSPORT, JSON_FACTORY)
        .setApplicationName("TestApp-Calendar/1.0").setHttpRequestInitializer(credential)
        .build();

 CalendarList feed = calendar.calendarList().list().execute();

调用 list().execute() 会导致以下错误:

com.google.api.client.googleapis.json.GoogleJsonResponseException: 401 Unauthorized { "code" : 401, "errors" : [ { "domain" : "global", "location" : "Authorization", "locationType" : " header", "message" : "需要登录", "reason" : "需要" } ], "message" : "需要登录" } 在 com.google.api.client.googleapis.json.GoogleJsonResponseException.from(GoogleJsonResponseException. java:159) 在 com.google.api.client.googleapis.json.GoogleJsonResponseException.execute(GoogleJsonResponseException.java:187) 在 com.google.api.client.googleapis.services.GoogleClient.executeUnparsed(GoogleClient.java:115)在 com.google.api.client.http.json。JsonHttpRequest.executeUnparsed(JsonHttpRequest.java:112) at com.google.api.services.calendar.Calendar$CalendarList$List.execute(Calendar.java:510) at uk.org.reigatepriorybowmen.Service.registerEmailForAllCalendarEvents(Service.java: 55) 在 uk.org.reigatepriorybowmen.Service.main(Service.java:74)

服务帐户设置如下:

在此处输入图像描述

那么,我做错了什么?!我花了几个小时在谷歌上搜索寻找解决方案,所以这是我最后的希望。

非常感谢您的帮助。

贾斯汀

4

6 回答 6

6

至于 401,谷歌的 API 似乎有些奇怪。我发现我在一段时间内第一次运行我的应用程序,然后每隔几个小时看似随机的时间间隔,我总是得到 401。在第二轮或第三轮它实际登录。

甚至在 Google 的 Task API 示例代码中也考虑了这种行为。

注意这段代码:

void onRequestCompleted() {
received401 = false;
}

void handleGoogleException(IOException e) {
if (e instanceof GoogleJsonResponseException) {
  GoogleJsonResponseException exception = (GoogleJsonResponseException) e;
  if (exception.getStatusCode() == 401 && !received401) {
    received401 = true;
    accountManager.invalidateAuthToken(credential.getAccessToken());
    credential.setAccessToken(null);
    SharedPreferences.Editor editor2 = settings.edit();
    editor2.remove(PREF_AUTH_TOKEN);
    editor2.commit();
    gotAccount();
    return;
  }
}
Log.e(TAG, e.getMessage(), e);

}

在 Google 自己的代码示例中,他们首次提供了 401。这可能是一些需要在代码中处理的特殊安全问题。

于 2012-08-07T05:41:36.523 回答
1

如果您没有在管理面板中添加 api 用户(我猜在您的情况下是文档面板),则可能会发生这种行为,例如,在谷歌分析中,您必须添加新用户,其中电子邮件地址与您从 api 获得的电子邮件地址相同(由Google API 服务帐户,创建私钥后)。在分析中,它看起来像这样:

谷歌分析中的例子

于 2012-06-27T22:16:57.613 回答
1

我是一名 Android 移动应用程序开发人员。我正在使用 Google Calendar API (OAuth 2.0) 制作应用程序。我也遇到了同样的错误。但是现在,我已经解决了这个错误。我正在编写场景和解决方案。
获取特定日历的日历事件的网址(移动应用 API):

https://www.googleapis.com/calendar/v3/calendars/en.indian%23holiday%40group.v.calendar.google.com/events?access_token=ya29.AHES6ZQyP3iDZM8PF-7ZqZE8oKmWAgUX55sPGPTjGbM5A

在这个 url 中,我们必须添加 calendarId。网址的格式是;

https://www.googleapis.com/calendar/v3/calendars/<calendarId>/events

在添加到 url 之前,我没有对 calendarId 进行编码。我对 calendarId 进行了编码,然后发出了 Http 请求。那时一切顺利。

final String accessToken=dataStore.getAccessToken();

    List<NameValuePair> params = new LinkedList<NameValuePair>();
    params.add(new BasicNameValuePair("access_token", accessToken));
    String paramString = URLEncodedUtils.format(params, "utf-8");

    final String url = String.format(AuthConstants.CALENDAR_EVENTS_URI,URLEncoder.encode(calendarId))+"?"+ paramString;

现在日历事件以 JSON 格式返回。

于 2013-02-15T11:14:17.417 回答
1

我认为您需要指定您要模拟的用户。

您现在使用 ServiceAccount 的方式仅适用于访问与您的应用程序或您的服务帐户本身相关的数据。由于您希望访问 Google 日历数据,因此您必须是有权访问您的任何域用户数据的 Google Apps 域管理员 - 如果我不正确,请告诉我。Google 日历数据属于用户,因此您需要指定您要模拟的用户。

要使用 Java 执行此操作,您似乎需要使用GoogleCredential.Builder#setServiceAccountUser(String)

请试试:

GoogleCredential credential = new GoogleCredential.Builder().setTransport(HTTP_TRANSPORT)
        .setJsonFactory(JSON_FACTORY)
        .setServiceAccountId("284XXXXXXXX@developer.gserviceaccount.com")
        .setServiceAccountScopes(CalendarScopes.CALENDAR)
        .setServiceAccountPrivateKeyFromP12File(new File("/path/to/privatekey.p12"))
        .setServiceAccountUser("user@domain.com")
        .build();

此外,一旦您在 API 项目中创建了服务帐户密钥(从 API 控制台),您必须将此项目添加到 cPanel 中的授权第三方应用程序列表中。可以在此处找到有关此的更多信息。您需要使用的“客户 ID”是绑定到服务帐户密钥的,看起来像<APP_ID>-<OTHER_KEY>.apps.googleusercontent.com

于 2012-05-05T00:32:32.310 回答
0

好的,所以我一直在玩这个并且已经克服了 401 错误,但是我现在遇到了 403。但是,我将详细说明我编写的至少可以解决 401 问题的代码。

我没有更改任何服务帐户设置或重新生成任何密钥等,都是一样的。

改变的是一些代码,我升级到日历 API 的v3r20lv1.12.0-beta

下面的代码,如上面在 OP 中详述的,保持不变,即:

private static final HttpTransport HTTP_TRANSPORT = new NetHttpTransport();
private static final JsonFactory JSON_FACTORY = new JacksonFactory();
.
.
.
GoogleCredential credential = new GoogleCredential.Builder().setTransport(HTTP_TRANSPORT)
        .setJsonFactory(JSON_FACTORY)
        .setServiceAccountId("284XXXXXXXX@developer.gserviceaccount.com")
        .setServiceAccountScopes(CalendarScopes.CALENDAR)
        .setServiceAccountPrivateKeyFromP12File(new File("D:/3cd8XXXXXXXXXXXXXXXXXXXXXXXXXXXXXd635e-privatekey.p12"))
        .build();

获取日历句柄的代码已更改,但这是由 API 更改强制执行的:

Calendar calendar = new Calendar(HTTP_TRANSPORT, JSON_FACTORY, credential);

此时,我可以通过运行以下代码遍历我的日历并查看事件(这会打印事件摘要):

Events events = calendar.events().list(CALENDAR_ID).execute();

for (Event event : events.getItems())
{
    System.out.println(event.getSummary());
}

但是,如果我尝试更新事件,则会收到 403 Forbidden 错误。我将为此提出一个单独的线程,并将两个线程连接在一起。

感谢大家的帮助!

编辑:是新线程。

于 2012-12-22T13:05:19.277 回答
0

您是否请求了适当范围的许可?您应该始终包括https://www.googleapis.com/auth/userinfo.profile

于 2013-05-02T09:06:08.107 回答