使用OAuth 2和服务帐户访问旧版GData API(电子表格API)

16
简短的问题是,这是否可能?如果可能,如何实现?
大纲:
我有一个.NET应用程序,目前使用服务账户通过Google Drive API跨Google Apps域访问信息。这在google-api-dotnet-client库中运作良好,并且代码与示例中的基本示例非常相似, 这是我正在做的内容的很好的基础示例。
现在我想扩展它,除了使用“新”的google-api-dotnet-client库提供的API外,还使用较旧的“GData”库,由于旧版的google-gdata库提供了电子表格API(也许还有更多)。
问题:
这就是困难所在。前一个库完全符合我的要求,如上文第一段中的第二个链接所证明的那样,而且我已经让它自己完成了这个任务。然而...虽然第二个库已经更新,支持OAuth 2.0以及OAuth 1.0和其他旧的授权技术,但据我通过广泛的谷歌搜索和试错发现,它不允许我需要的“代表所有用户的服务帐户”操作。
我的问题是,我是否遗漏了什么(可能是难以找到或未记录的内容),可以让我做我想做的事情。如果失败,是否有任何办法可以强制实现这种行为,并使这两个库并行运作?
理想的解决方案
理想情况下,我希望某种方式能够让Google.GData.Spreadsheets.SpreadsheetsService实例能够利用我已经使用的Google.Apis.Authentication.Auth2Authenticator<AssertionFlowClient>实例...不知道这种魔法是否可能?我是否忽略了显而易见的东西?
如果不能实现,我很乐意再次进行整个OAuth2“assertion flow client”过程,以某种旧库可以处理的方式。

需要帮忙吗?

其他想法

我已经考虑过,但暂时拒绝了从头开始编写自己的库来实现这一目标的选项。原因有两个:

  1. gdata库已经存在,并且由许多比我聪明的人开发。我并不傲慢到认为我能做得更好。
  2. 我不确定使用服务账户的OAuth2方法是否在这些旧API上受支持/允许。

备选方案是我一直希望避免但可能不得不回退的方法,即对某些部分使用2-legged OAuth 1.0。我不想这样做,因为让应用程序的某些部分依赖于一个旧的授权方法,而其他部分则采用新的授权方法,感觉很奇怪。而且出错的可能性就更大了...


更新

我考虑了子类化 GDataRequestFactory 和 GDataRequest 的可能性,以便创建自己的请求工厂并将其实例传递给 Google.Apis.Authentication.Auth2Authenticator<AssertionFlowClient>(或者至少是一个实现了 Google.Apis.Authentication.IAuthenticator 接口的类),从而在调用请求之前进行身份验证。但是... GDataRequest 的构造函数是内部的,这使我无法进行操作。

看起来真的不太可能实现这个目标。


这个对你有帮助吗?http://googleappsdeveloper.blogspot.com/2011/09/python-oauth-20-google-data-apis.html - Bolutife Ogunsola
3个回答

20

为了其他人看到这个问题时(因为被接受的答案中链接的解决方案使用了弃用代码),这里是我如何解决它的方法:

首先,进入“新API”领域(使用Google.Apis.Auth NuGet包),通过按照谷歌的服务账户示例设置一个ServiceAccountCredential

//In the old api, this accessed the main api accounts' sheets, not anymore
//** Important ** share spreadsheets with the Service Account by inviting the "serviceAccountEmail" address to the sheet
string serviceAccountEmail = "12345697-abcdefghijklmnop@developer.gserviceaccount.com";

var certificate = new X509Certificate2(@"key.p12", "notasecret", X509KeyStorageFlags.Exportable);

ServiceAccountCredential credential = new ServiceAccountCredential(
   new ServiceAccountCredential.Initializer(serviceAccountEmail)
   {
       Scopes = new[] { "https://spreadsheets.google.com/feeds", "https://docs.google.com/feeds" }
   }.FromCertificate(certificate));

告诉凭据请求访问令牌:

credential.RequestAccessTokenAsync(System.Threading.CancellationToken.None).Wait();

现在是时候切换回“旧API”领域(使用Google.GData.Spreadsheets nuget包)。首先构建SpreadsheetsService(类似于谷歌的示例):

SpreadsheetsService service = new SpreadsheetsService("MySpreadsheetIntegration-v1");

为了使用服务帐号身份验证,我们将创建一个 GDataRequestFactory 实例并设置自定义的 Authorization 标头:

var requestFactory = new GDataRequestFactory("My App User Agent");
requestFactory.CustomHeaders.Add(string.Format("Authorization: Bearer {0}", credential.Token.AccessToken));

最后,将SpreadsheetsServiceRequestFactory属性设置为这个新工厂:

service.RequestFactory = requestFactory;

按照任何其他技术进行身份验证后,继续使用SpreadsheetsService。 (提示:通过将serviceAccountEmail地址邀请到表格中,与服务帐户共享电子表格)


已更新接受的答案,因为这是现在实现此目的的最佳(唯一)方法,并且它使用新库完全与我的原始解决方案一样有效。值得注意的是,范围需要适当地设置用于所使用的服务 :-). - Mark Embling
感谢您的回答,帮助我找到了正确的方向。对于任何其他人要将其适应于访问EmailSettings API,您需要在.Initializer中添加一个“User”属性,并附上管理员的主电子邮件地址。 - WhoIsRich

4
我通过创建自己的GDataRequestFactory子类并实现GDataRequest接口来解决了这个问题。该实现使用反射实例化了一个GDataRequest实例,并添加了必要的代码来使用IAuthenticator(在我的情况下是Auth2Authenticator)执行身份验证。
我写了一篇博客文章,并添加了一个示例作为Gist: 如果有帮助,请随意使用它(BSD许可证)。

1
这是一些优秀的代码。不幸的是,自那时以来,Google已经将IAuthenticator标记为过时,并使用ServiceAccountCredential。它似乎是一个完全不同的机制,所以我无法使用你的方法来创建适配器。希望他们能在某个时候发布与电子表格兼容的API! - JonH
IAuthenticator 现在不可用吗? - PreguntonCojoneroCabrón

3

嘿,我也遇到了同样的问题,并想出了一种不同的解决方案:

有没有人考虑过直接将凭据对象中的参数写入OAuth2Parameters对象中呢?

我这样做了,效果很好:

public class OAuthTest
{  
    OAuth2Parameters param = new OAuth2Parameters();

    public OAuthTest()
    {
        Debug.WriteLine("Calling: AuthGoogleDataInterface()");
        bool init = AuthGoogleDataInterface();
        if (init)
        {
            GOAuth2RequestFactory requestFactory = new GOAuth2RequestFactory(null, "My App User Agent", this.param);
            //requestFactory.CustomHeaders.Add(string.Format("Authorization: Bearer {0}", credential.Token.AccessToken));
            var service = new SpreadsheetsService("MyService");
            service.RequestFactory = requestFactory;
            SpreadsheetQuery query = new SpreadsheetQuery();

            // Make a request to the API and get all spreadsheets.
            SpreadsheetFeed feed = service.Query(query);

            // Iterate through all of the spreadsheets returned
            foreach (SpreadsheetEntry entry in feed.Entries)
            {
                // Print the title of this spreadsheet to the screen
                Debug.WriteLine(entry.Title.Text);
            }
        }
        Debug.WriteLine(m_Init);
    }

    private bool AuthGoogleDataInterface()
    {
        bool b_success;
        try
        {
            Console.WriteLine("New User Credential");
            // New User Credential
            UserCredential credential;
            using (var stream = new FileStream("client_secrets.json", FileMode.Open, FileAccess.Read))
            {
                GoogleClientSecrets GCSecrets = GoogleClientSecrets.Load(stream);
                string[] ArrScope = new[] { "https://spreadsheets.google.com/feeds", "https://docs.google.com/feeds" };
                credential = GoogleWebAuthorizationBroker.AuthorizeAsync(
                    GCSecrets.Secrets,
                    ArrScope,
                    "user", CancellationToken.None,
                new FileDataStore("My.cal")).Result;
                // put the Information generated for the credentials object into the OAuth2Parameters-Object to access the Spreadsheets
                this.param.ClientId = GCSecrets.Secrets.ClientId; //CLIENT_ID;
                this.param.ClientSecret = GCSecrets.Secrets.ClientSecret; //CLIENT_SECRET;
                this.param.RedirectUri = "urn:ietf:wg:oauth:2.0:oob"; //REDIRECT_URI;
                this.param.Scope = ArrScope.ToString();
                this.param.AccessToken = credential.Token.AccessToken;
                this.param.RefreshToken = credential.Token.RefreshToken;
            }

            Debug.WriteLine("AuthGoogleDataInterface: Success");
            b_success = true;
        }
        catch (Exception e)
        {
            Debug.WriteLine(e.ToString());
            b_success = false;
        }
        return b_success;
    }
}

我该如何获取 client_secrets.json 文件? - Kiquenet
据我所记,您可以直接从您的Google开发者应用程序中获取它。 - mhaenssgen
@ abatishchev:真的吗? - mhaenssgen
当我尝试时,出现了“至少应设置一个客户端秘钥(已安装或Web)”的提示。我为已安装的客户端创建了JSON文件。 - PreguntonCojoneroCabrón

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接