【问题标题】:Failure of delegation of Google Drive access to a service accountGoogle Drive 访问服务帐户的授权失败
【发布时间】:2016-11-28 16:29:53
【问题描述】:

我参与了构建一个内部使用的应用程序,用户可以通过该应用程序上传文件,以存储在 Google 云端硬盘中。由于建议不要将服务帐户用作文件所有者,因此我希望代表公司系统管理员有权访问的指定用户帐户上传应用程序。

我已经创建了应用程序以及服务帐户。为服务帐户创建了两个密钥,因为我尝试了 JSON 和 PKCS12 格式来尝试实现这一点:

我已经下载了 OAuth 2.0 客户端 ID 详细信息,并且还有服务帐户密钥的 .json 和 .p12 文件(按上面显示的顺序):

我让我的系统管理员按照此处详述的步骤将 Drive API 访问权限委派给服务帐户:https://developers.google.com/drive/v2/web/delegation#delegate_domain-wide_authority_to_your_service_account

我们发现,在第 4 步中,唯一适用于“客户端名称”的是为 Web 应用程序列出的“客户端 ID”(以 .apps.googleusercontent.com 结尾)。为服务帐户密钥列出的长十六进制 ID 不是它所需要的(见下文):

在上述之前,我有代码可以创建一个 DriveService 实例,该实例可以直接上传到服务帐户,引用服务帐户密钥的 .json 文件:

private DriveService GetServiceA()
{
    var settings = SettingsProvider.GetInstance();

    string keyFilePath = HostingEnvironment.MapPath("~/App_Data/keyfile.json");
    var scopes = new string[] { DriveService.Scope.Drive };

    var stream = new IO.FileStream(keyFilePath, IO.FileMode.Open, IO.FileAccess.Read);
    var credential = GoogleCredential.FromStream(stream);
    credential = credential.CreateScoped(scopes);

    var service = new DriveService(new BaseClientService.Initializer()
    {
        HttpClientInitializer = credential,
        ApplicationName = "MyAppName"
    });

    return service;
}

这适用于列出和上传,虽然当然没有用于访问文件的 Web UI,而且它似乎不处理权限元数据或生成缩略图之类的事情,例如PDF。这就是我尝试使用标准帐户进行上传的原因。

一旦对委托进行了明显排序,我就尝试调整上面链接的委托参考中显示的代码,并结合其他地方的代码从 .json 密钥文件中提取必要的细节。使用这段代码,只要我尝试执行任何 API 命令,甚至可以简单到:

FileList fileList = service.FileList().Execute();

我收到一个错误:

异常详细信息:Google.Apis.Auth.OAuth2.Responses.TokenResponseException:错误:“unauthorized_client”,描述:“请求中未经授权的客户端或范围。”,Uri:“”

这项工作的代码是:

private DriveService GetServiceB()
{
  var settings = SettingsProvider.GetInstance();

  string keyFilePath = HostingEnvironment.MapPath("~/App_Data/keyfile.json");
  string serviceAccountEmail = "<account-email>@<project-id>.iam.gserviceaccount.com";
  var scopes = new string[] { DriveService.Scope.Drive };

  var stream = new IO.FileStream(keyFilePath, IO.FileMode.Open, IO.FileAccess.Read);
  var reader = new IO.StreamReader(stream);
  string jsonCreds = reader.ReadToEnd();
  var o = JObject.Parse(jsonCreds);
  string privateKey = o["private_key"].ToString();

  var credential = new ServiceAccountCredential(
    new ServiceAccountCredential.Initializer(serviceAccountEmail)
    {
      Scopes = scopes,
      User = "designated.user@sameappsdomain.com"
    }
    .FromPrivateKey(privateKey)
  );

  var service = new DriveService(new BaseClientService.Initializer()
  {
    HttpClientInitializer = credential,
    ApplicationName = "MyAppName"
  });

  return service;
}

最后,我创建了第二个服务帐户密钥来保存一个 .p12 文件,以便更紧密地匹配授权委托文档中的代码,但这会导致相同的异常:

private DriveService GetServiceC()
{
  var settings = SettingsProvider.GetInstance();

  string p12KeyFilePath = HostingEnvironment.MapPath("~/App_Data/keyfile.p12");
  string serviceAccountEmail = "<account-email>@<project-id>.iam.gserviceaccount.com";
  var scopes = new string[] { DriveService.Scope.Drive }; // Full access

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

  var credential = new ServiceAccountCredential(
    new ServiceAccountCredential.Initializer(serviceAccountEmail)
    {
      Scopes = scopes,
      User = "designated.user@sameappsdomain.com"
    }
    .FromCertificate(certificate)
  );

  var service = new DriveService(new BaseClientService.Initializer()
  {
    HttpClientInitializer = credential,
    ApplicationName = "MyAppName"
  });

  return service;
}

此方法所在的最小相关类是:

public class GoogleDrive
{
  public DriveService Service { get; private set; }

  public GoogleDrive()
  {
    this.Service = this.GetService();
  }

  private DriveService GetService()
  {
    // Code from either A, B or C
  }

  public FilesResource.ListRequest FileList()
  {
    return this.Service.Files.List();
  }
}

以这种方式使用:

var service = new GoogleDrive();
FilesResource.ListRequest listRequest = service.FileList();
FileList fileList = listRequest.Execute();

异常发生在最后一行。

我不明白为什么我的服务帐户不能代表指定用户执行操作,该用户是应用程序服务帐户应具有委派权限的域的一部分。我在这里误会了什么?

【问题讨论】:

  • 这可能是配置错误的问题。您是否在客户名称中输入了服务帐户的客户 ID?这是Delegate domain-wide authority to your service account 中的第 5 步
  • @noogui 为服务帐户密钥生成的 .json 文件包含一个“client_id”条目,它是一个 21 位十进制数字。将其用于第 5 步会显示一条错误消息:“此客户端名称尚未在 Google 上注册。”

标签: c# google-drive-api


【解决方案1】:

我自己找到了答案,配置,而不是代码。我与授权步骤共享的链接没有提及创建服务帐户时可用的选项:表示该帐户将有资格获得域范围委派 (DwD) 的复选框。 p>

此链接更准确地描述了服务帐户的创建和委派:https://developers.google.com/identity/protocols/OAuth2ServiceAccount

我在创建服务帐户时不知道 DwD,因此我没有选择该选项。可以返回并编辑服务帐户以选择它。完成此操作后,我就能够检索到正确的客户端 ID,以便在管理控制台的“管理 API 客户端访问”部分中使用。然后使用 GetServiceC() 方法按预期工作,我能够为同一 Apps 域中的用户检索文件。

这是需要勾选的复选框,服务帐户才有资格获得域范围的授权:

这是您完成此操作后可用的额外信息(旁边有一个一次性服务帐户,没有勾选框,以供比较):

【讨论】:

  • 我没有在密钥生成 UI 上看到新的域委派选项,以前也不需要它 - 它必须是一个新属性。很高兴你把它整理好了!
【解决方案2】:

当您在管理面板上创建服务帐户时,您可以勾选复选框启用 G Suite 域范围委派

问候

【讨论】:

  • 谢谢,但正如你所见,我已经设法发现我哪里出错了! :)
【解决方案3】:

大部分看起来都还可以,但是:

A.使用 ServiceC 代码,不确定对象类型是否重要,但您的行:

var credential = new ServiceAccountCredential...

应该是

ServiceAccountCredential credential = new ServiceAccountCredential...

B.检查 ServiceC 中的 P12 文件是否是您实际上传到运行它的环境的真实 P12 文件。

C.使用您用来创建和调用服务:文件列表:执行代码的确切可运行代码更新您的问题。这样就更清晰了,假设更少了。

【讨论】:

  • A.使用var 没有区别,请参阅msdn.microsoft.com/en-us/library/bb384061.aspx
  • (B) 我在本地机器上运行它;我已经澄清了代码以表明我是直接从 App_Data 文件夹加载它的。 (C) 我添加了执行 FileList 命令的实际代码(所有 GetService() 方法通用)
猜你喜欢
  • 2017-06-29
  • 2020-04-02
  • 1970-01-01
  • 1970-01-01
  • 2020-01-06
  • 1970-01-01
  • 2018-09-03
  • 2019-02-03
  • 2020-01-21
相关资源
最近更新 更多