【问题标题】:401 Unauthorized error when using a server account to impersonate a user in order to access their Google Drive使用服务器帐户模拟用户以访问其 Google 云端硬盘时出现 401 未经授权的错误
【发布时间】:2017-08-22 17:38:40
【问题描述】:

我正在用 Java 编写一个后端进程,它将模拟用户并在他们的 Google Drive 上添加/删除文档。

服务器帐户似乎已正确验证,但当我尝试模拟用户时,我得到一个401 Unauthorized error。详情请看下文。

配置

我已将服务器帐号配置如下:

  • 在 Google API 下创建了一个项目并启用了 Google Drive API
  • 创建了一个名为 anothertest@yyyyyyyyy.iam.gserviceaccount.com 的服务帐户,将角色设置为服务帐户参与者并为其赋予域范围的委派。它的客户端 ID 为 110xxxxxxxxx342
  • 我已经下载了 P12 密钥文件

我已使用管理 API 客户端访问屏幕配置域以授权 110xxxxxxxxx342 拥有范围:https://www.googleapis.com/auth/drive

Google 支持人员查看了我的配置并给予了好评。

我的代码如下所示:

package com.dcm.sharingdocuments;

import java.io.File;
import java.io.IOException;
import java.util.Arrays;
import java.util.Iterator;
import java.util.List;
import java.util.Set;
import java.util.Map.Entry;

import com.google.api.client.auth.oauth2.TokenErrorResponse;
import com.google.api.client.auth.oauth2.TokenResponseException;
import com.google.api.client.googleapis.auth.oauth2.GoogleCredential;
import com.google.api.client.http.HttpTransport;
import com.google.api.client.http.javanet.NetHttpTransport;
import com.google.api.client.json.jackson2.JacksonFactory;
import com.google.api.services.drive.Drive;
import com.google.api.services.drive.DriveScopes;
import com.google.api.services.drive.model.FileList;

public class SharingDocumentsTest3 {

    private static final String SERVICE_ACCOUNT_EMAIL = " anothertest@yyyyyyyyy.iam.gserviceaccount.com";

    public static Drive getDriveService(String userEmail) throws Exception {

        File keyFile = new File("E:\\Projects\\Workspace\\Sharing Documents\\authentication\\AnotherTestKeyFile.p12");

        HttpTransport httpTransport = new NetHttpTransport();
        JacksonFactory jsonFactory = new JacksonFactory();

        List<String> SCOPES = Arrays.asList(DriveScopes.DRIVE_METADATA_READONLY);

        GoogleCredential credential = null;

        if (userEmail == null) {

            credential = new GoogleCredential.Builder().setTransport(httpTransport).setJsonFactory(jsonFactory)
                    .setServiceAccountId(SERVICE_ACCOUNT_EMAIL).setServiceAccountScopes(SCOPES)
                    .setServiceAccountPrivateKeyFromP12File(keyFile).build();

            credential.refreshToken();

        } else {

            credential = new GoogleCredential.Builder().setTransport(httpTransport).setJsonFactory(jsonFactory)
                    .setServiceAccountId(SERVICE_ACCOUNT_EMAIL).setServiceAccountScopes(SCOPES)
                    .setServiceAccountPrivateKeyFromP12File(keyFile).setServiceAccountUser(userEmail).build();

            credential.refreshToken();

        }

        Drive service = new Drive.Builder(httpTransport, jsonFactory, null).setHttpRequestInitializer(credential)
                .build();

        return service;

    }

    public static void main(String[] args) {

        SharingDocumentsTest3 sdt3 = new SharingDocumentsTest3();

        sdt3.execute();

    }

    private void execute() {

        try {

            Drive service = getDriveService(null);

            Drive services = getDriveService("anzzzze@zzzzz.me.uk");

            displayFiles(services);

        } catch (Exception e) {
            e.printStackTrace();
        }

    }

    private void displayFiles(Drive service) throws Exception {

        FileList result = service.files().list().setPageSize(10).execute();
        List<com.google.api.services.drive.model.File> files = result.getFiles();

        if (files == null || files.size() == 0) {

            System.out.println("No files found.");

        } else {

            System.out.println("Files:");
            for (com.google.api.services.drive.model.File file : files) {
                Set<Entry<String, Object>> entries = file.entrySet();

                Iterator<Entry<String, Object>> it = entries.iterator();

                while (it.hasNext()) {
                    Entry<String, Object> entry = it.next();
                    String key = entry.getKey();
                    Object value = entry.getValue();

                    if (value instanceof String) {
                        System.out.println("\tKey = " + key + ", Value = " + (String) value);
                    } else {
                        System.out.println("\tKey = " + key + ", Value = " + value.toString());
                    }

                }

                System.out.printf("%s (%s)\n", file.getName(), file.getId());
            }
        }
    }
}

当我像上面那样运行代码时,我得到了错误:

    Mar 29, 2017 9:55:27 AM com.google.api.client.googleapis.services.AbstractGoogleClient <init>
    WARNING: Application name is not set. Call Builder#setApplicationName.
    com.google.api.client.auth.oauth2.TokenResponseException: 401 Unauthorized
        at com.google.api.client.auth.oauth2.TokenResponseException.from(TokenResponseException.java:105)
        at com.google.api.client.auth.oauth2.TokenRequest.executeUnparsed(TokenRequest.java:287)
        at com.google.api.client.auth.oauth2.TokenRequest.execute(TokenRequest.java:307)
        at com.google.api.client.googleapis.auth.oauth2.GoogleCredential.executeRefreshToken(GoogleCredential.java:384)
        at com.google.api.client.auth.oauth2.Credential.refreshToken(Credential.java:489)
        at com.dcm.sharingdocuments.SharingDocumentsTest3.getDriveService(SharingDocumentsTest3.java:50)
        at com.dcm.sharingdocuments.SharingDocumentsTest3.execute(SharingDocumentsTest3.java:75)
        at com.dcm.sharingdocuments.SharingDocumentsTest3.main(SharingDocumentsTest3.java:65)

因此,当我设置 setServiceAccountUser 时,代码在 credential.refreshToken() 处失败。当我没有时,它似乎已经成功刷新了令牌。我尝试了此代码的各种组合 - 例如注释掉了 refreshToken() 行,注释掉了 getDriveService(null) 行——但每当我尝试使用/刷新为模拟用户获得的凭据时,我都会收到 401 Unauthorized 错误。

如果我修改代码以便将 getDriveService(null) 获得的驱动器传递给 DisplayFiles(...),那么我会得到一个名为“Getting Started”的文件。因此,服务帐户授权似乎正在工作,并且 Google 已将其默认文件添加到服务器帐户的驱动器中。

我正在使用google-*1.22.0.jar 文件和Java 1.8 来运行上面的代码

我认为问题在于我配置域的方式或我试图模拟用户的方式,但我的代码看起来与网络上的许多示例一样,Google 支持似乎说我已正确配置域.

您可以提出任何建议作为解决方案或下一步行动,我们将不胜感激!

【问题讨论】:

  • 检查以下问题stackoverflow.com/questions/33602347/… 这似乎有效。
  • 您好康拉德,非常感谢您如此迅速地回复。我已经查看了这个问题,但是通过删除 setServiceAccountUser(...) 解决了用户的问题,从而不冒充用户。这也是我正在经历的。如果我不包含 setServiceAccountUser(...) ,则代码运行,但我需要包含此方法来模拟用户并访问他们的文件。
  • 1.您应该让客户端库处理刷新您的访问权限。该帐户和服务帐户没有刷新令牌。 2. DriveScopes.DRIVE_METADATA_READONLY developers.google.com/identity/protocols/OAuth2ServiceAccount
  • 您好 DalmTo,我同意令牌的刷新。这不是我期望做的事情,但主要是为了尽快发现错误。对于我正在尝试做的事情,我不希望在生产环境中执行此操作。我已经尝试了所有 DriveScopes - 包括 all() 并且每次都得到相同的错误。我已经多次阅读快速入门页面 - 但我的代码仍然失败!
  • 你解决了吗?在这里面临同样的问题...

标签: java google-api google-drive-api google-api-java-client service-accounts


【解决方案1】:

我在这个问题上纠结了很久,终于找到了我的问题。 “管理 API 客户端访问”管理控制台中肯定存在错误...

您必须为客户名称输入“客户 ID”(例如 110xxxxxxxxx342),而不是“服务帐户 ID”(看起来像电子邮件的那个)。现在,their documentation is correct,他们确实在文档中说要使用客户端 ID,我必须给他们那个。

所以这是错误。当我到达管理 API 屏幕时,我看到了“示例:www.example.com”。我在那里输入了服务帐户 ID,认为电子邮件地址格式比客户 ID 更匹配“www.example.com”。我按下“授权”,条目显然已被接受,一切都很好。结果如下所示:

它甚至从服务 ID 生成了客户端 ID!伟大的!除了每次我尝试连接setServiceUser() 时,我的代码都会出现 401 错误。

如果我返回“管理 API 客户端访问”控制台,并且如果我删除了先前的条目并执行相同的操作,但使用客户端 ID 而不是服务 ID。结果是这样的:

完全一样,但现在我没有收到 401 错误。 所以没有办法查看控制台,并知道您是否已成功配置它。我对此进行了 3 次测试,以确保我没有失去理智... p>

【讨论】:

  • 嗨,阿利斯泰尔,好消息!发现得好。我会自己试一试,但我认为你已经破解了。
  • 我遇到了同样的错误(委派时为 401),并且遵循了所有相同的步骤,包括为您解决问题的步骤。我可能错过了 GSuite 或 GCP 配置中的一步,但我终生无法弄清楚。我有没有机会打扰您发布包含这些 GSuite 和 GCP 步骤的更新?谢谢! :)
  • 快速更新,以防像我这样的其他人仍然遇到问题:我发现我需要摆弄授权的 API 分数。我的测试 API 调用是列出 CalendarList 条目,并且我已授予 googleapis.com/auth/calendar,这应该是绰绰有余的权限。但是我收到了 401,直到我将范围“扩展”为“googleapis.com/auth/calendar,https://www.googleapis.com/auth/…”,坦率地说,这对我来说毫无意义,但它确实有效!
猜你喜欢
  • 2018-10-20
  • 2012-05-20
  • 1970-01-01
  • 2023-02-25
  • 2020-08-03
  • 2022-11-09
  • 1970-01-01
  • 2017-07-31
  • 2020-07-10
相关资源
最近更新 更多