【发布时间】: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