Oauth 2.0 &引用;“需要登录”;401通过OAuth 2.0使用服务帐户调用v3 Google日历API时出现未经授权的消息

Oauth 2.0 &引用;“需要登录”;401通过OAuth 2.0使用服务帐户调用v3 Google日历API时出现未经授权的消息,oauth-2.0,google-calendar-api,google-oauth,google-api-java-client,Oauth 2.0,Google Calendar Api,Google Oauth,Google Api Java Client,首先,让我解释一下我想做什么,因为这是一个由两部分组成的问题 我正在构建一个JAX-RS服务,该服务通过OAuth2对Google帐户进行内部身份验证,因此它可以访问和操作Google日历。该服务将由一个网站(Joomla)调用,该网站将允许登录该网站的用户在日历事件中注册其电子邮件地址,以便在事件临近时获得电子邮件通知。这些用户不知道底层的Google帐户 所以我的第一个问题是,使用服务帐户身份验证是正确的吗?看看谷歌文档,它是唯一一个满足非人工身份验证(服务器到服务器身份验证)的用例 第二个

首先,让我解释一下我想做什么,因为这是一个由两部分组成的问题

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

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

第二个问题是,我已经编写了一些代码来实现这一点,但是当我调用execute方法Calendar feed时,返回了401/Unauthorized响应,返回了以下错误。这段代码是从Google示例中提取的

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

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

因此,我做的第一个调用是获取凭证对象(Id被X混淆):

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

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

com.google.api.client.googleapis.json.googlejson响应异常:401未经授权 { “代码”:401, “错误”:[{ “域”:“全局”, “位置”:“授权”, “位置类型”:“标题”, “消息”:“需要登录”, “原因”:“必需” } ], “消息”:“需要登录” } 位于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) 位于com.google.api.services.calendar.calendar$CalendarList$List.execute(calendar.java:510) 在uk.org.reigatepriorybowmen.Service.registerEmailForAllCalendarEvents上(Service.java:55) 位于uk.org.reigatepriorybowmen.Service.main(Service.java:74)

服务帐户的设置如下所示:

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

非常感谢你的帮助


Justin

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

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

要使用Java实现这一点,似乎需要使用

请尝试:

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”是绑定到服务帐户密钥的Id,看起来像
-.apps.googleusercontent.com

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


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

这种行为甚至在谷歌的TaskAPI示例代码中得到了解释

请注意以下代码:

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


在谷歌自己的代码示例中,他们首次做出了规定。这可能是一些需要在代码中处理的特殊安全问题。

好的,所以我一直在处理这个问题,并且已经通过了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);
此时,通过运行以下代码(这将打印事件摘要),我可以遍历日历并查看事件:

但是,如果我尝试更新一个事件,我会得到一个403禁止的错误。我将为此提出一个单独的线程,并将这两个线程链接在一起

谢谢大家的帮助


编辑:是新线程。

我是安卓移动应用程序开发人员。我正在使用GoogleCalendarAPI(OAuth2.0)制作一个应用程序。我也面临着同样的错误。但是现在,我已经解决了这个错误。我正在编写场景和解决方案。
获取特定日历的日历事件的url(移动应用程序API):

在这个城市
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 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());
}
https://www.googleapis.com/calendar/v3/calendars/en.indian%23holiday%40group.v.calendar.google.com/events?access_token=ya29.AHES6ZQyP3iDZM8PF-7ZqZE8oKmWAgUX55sPGPTjGbM5A
https://www.googleapis.com/calendar/v3/calendars/<calendarId>/events
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;