使用存储的标记从C#Windows服务访问DriveService - invalid_grant异常

时间:2013-12-17 20:32:32

标签: c# google-api google-drive-api google-oauth google-api-dotnet-client

这是我第一次使用Google的Drive api,所以如果我出错了,我会提前道歉。

这就是我所拥有的:

我使用网络应用程序手动浏览OAuth2舞蹈,以便在TokenResponse中获取AccessToken,RefreshToken和TokenType。这些价值都成功回归,我存储了它们。

现在,我想在Windows服务中使用相同的信息,以及我为此目的明确创建的Google帐户的相同用户名,该服务将监控该用户的云端硬盘的特定目录中的文件。

我手动创建了TokenResponse,GoogleAuthorizationCodeFlow,UserCredential和DriveService,如下所示:

//Populate the token response with the stored values
_tokenResponse = new TokenResponse
{
    AccessToken = AppConfiguration.GetSetting("StoredAccessToken"),
    TokenType = AppConfiguration.GetSetting("StoredTokenType"),
    RefreshToken = AppConfiguration.GetSetting("StoredRefreshToken"),
    Issued = DateTime.Parse(AppConfiguration.GetSetting("StoredTokenIssuedDate")),
    ExpiresInSeconds = long.Parse(AppConfiguration.GetSetting("StoredExpiresInSeconds"))
};

//Next, build our code flow
_apiCodeFlow = new GoogleAuthorizationCodeFlow(new GoogleAuthorizationCodeFlow.Initializer
    {
        ClientSecrets = new ClientSecrets
            {
                ClientId = AppConfiguration.GetSetting("ApiClientId"),
                ClientSecret = AppConfiguration.GetSetting("ApiClientSecret")
            },
        Scopes = new[] { DriveService.Scope.Drive },
        DataStore = new FileDataStore(AppConfiguration.GetSetting("RegisteredApplicationName"))
    });
//Now create the UserCredential with those stored values        
_credential = new UserCredential(_apiCodeFlow, AppConfiguration.GetSetting("DriveApiServiceAccountEmail"), _tokenResponse);
//And generate a DriveService instance using the UserCredential
_driveService = new DriveService(new BaseClientService.Initializer
{
    HttpClientInitializer = _credential,
    ApplicationName = AppConfiguration.GetSetting("RegisteredApplicationName")
});

这一切似乎都可行,但是当我尝试做类似

的事情时
DriveService.Files.List.Execute()

我得到一个例外:

"Error:\"invalid_grant\", Description:\"\", Uri:\"\""

现在,我已经在谷歌文档中挖掘了好几天了,而且我几乎不知所措。我开始查看它们提供的DrEdit解决方案,但它引用了我在最新的API命名空间中看不到的对象,例如:IAuthorizationState,NativeApplicationClient。我正在使用Google.Apis.Drive.v2,Google.Apis.Auth.OAuth2,Google.Apis.Auth.OAuth2.Flows,Google.Apis.OAuth2.Responses,Google.Apis.Services,Google.Apis.Util,和Google.Apis.Util.Store名称空间。有什么我想念的吗?

我想知道它是否与请求来自的网址有任何关系,因为您必须在Google控制台中注册所有重定向网址以注册这些应用。但是,我没有看到一种方法可以指定从Windows服务执行此操作时要使用的重定向URL。这可能不是问题,只是一个想法。

我的需求相当简单 - 我只需要能够使用我保存的刷新令牌,以及仅为此目的创建的新用户的用户名,并获得一个DriveService实例,让我遍历该用户可以访问的文件。

真诚地感谢任何帮助。在此先感谢!!

修改

使用Fiddler来监控网络流量,看起来请求正在按预期进行。 grant_type标记为“refresh_token”,所有其他项目都填充了正确的值。见附图: Token Refresh Request Body from Fiddler

编辑2

在GoogleAuthorizationCodeFlow类中,有一个名为“RefreshTokenAsync”的方法,它采用用户名,刷新令牌和取消令牌。它在Fiddler中产生与调用DriveService类中的任何方法相同的结果,因此我认为它在调用DriveService时会自动尝试在幕后刷新令牌。不确定这对那些能够回答这个问题的人是否有帮助,但我想我还是会提到的。

12/18/2013根据DaImTo的建议更新

我创建了自己的IDataStore实现,这里是GetAsync方法的代码,我已经介入了它并且它应该返回一个Token对象:

    public Task<T> GetAsync<T>(string key)
    {
        TaskCompletionSource<T> tcs = new TaskCompletionSource<T>();
        try
        {
            //First, load the token from our local json file
            //NOTE: If using the built in FileDataStore, Google would store this same json info in the local users appdata\roaming\{appname} folder
            Stream stream = new FileStream("StoredToken.json", FileMode.Open, FileAccess.Read);
            //Read the file contents into a string
            string sToken = (new StreamReader(stream)).ReadToEnd();
            //Close the stream so that the file isn't locked
            stream.Close();
            stream.Dispose();
            //Deserialize into the type that Google requests
            var token = Google.Apis.Json.NewtonsoftJsonSerializer.Instance.Deserialize<T>(sToken);
            //Set the result of the TaskCompletionSource to our value
            tcs.SetResult(token);
        }
        catch (Exception exc)
        {
            //No need to throw exception here - whatever is calling this method would want to handle exceptions.  
            //So, just set the Exception of the TaskCompletionSource and return it
            tcs.SetException(exc);
        }

        return tcs.Task;
    }

现在,这是我更新的获取UserCredential对象并使用它来填充我的DriveService实例的方法:

        _credential = GoogleWebAuthorizationBroker.AuthorizeAsync(
            clientSecrets: new ClientSecrets
                {
                    ClientId = AppConfiguration.GetSetting("ApiClientId"),
                    ClientSecret = AppConfiguration.GetSetting("ApiClientSecret")
                },
            scopes: new[] {DriveService.Scope.Drive},
            dataStore: new CustomTokenDataStore(),
            user: AppConfiguration.GetSetting("DriveApiServiceAccountEmail"),
            taskCancellationToken: CancellationToken.None).Result;

        _driveService = new DriveService(new BaseClientService.Initializer
        {
            HttpClientInitializer = _credential,
            ApplicationName = AppConfiguration.GetSetting("RegisteredApplicationName")
        });

然后我尝试使用DriveService API进行简单查询:

            using (GoogleDriveApiUtils util = new GoogleDriveApiUtils())
            {
                FilesResource.ListRequest request = util.DriveService.Files.List();

                /*Documentation for using the search parameters:
                 * https://developers.google.com/drive/search-parameters
                 */

                //Add this to the query to get only folders
                request.Q = "mimeType='application/vnd.google-apps.folder'";

                FileList fList = request.Execute();
            }

这是我得到相同异常的地方 - “invalid_grant”。从我能够研究的内容来看,这与响应的来源有关。但是,我是通过Windows服务进行此操作的 - 所以我无法知道如何将它从请求发送到DriveService的注册网址中获取。但是,我可能完全走错了路。

还有其他想法吗?再次,非常感谢所有帮助:)

2 个答案:

答案 0 :(得分:2)

FileDataStore将信息存储在%appdata%及其获取位置。要使用存储的数据,您需要对iDataStore进行自己的限制,然后您可以发送FileDataStore的内容,这也是iDataStore的另一个限制。

这只是从我的GoogleAnalytics代码中复制而来,您可以将其更改为您的范围,一旦您创建了自己对iDatastore()的限制,它就会起作用;

IDataStore StoredRefreshToken = new myDataStore();
// Oauth2 Autentication.
using (var stream = new System.IO.FileStream("client_secret.json", System.IO.FileMode.Open, System.IO.FileAccess.Read))
{
 credential = GoogleWebAuthorizationBroker.AuthorizeAsync(
 GoogleClientSecrets.Load(stream).Secrets,
 new[] { AnalyticsService.Scope.AnalyticsReadonly },
 "user", CancellationToken.None, StoredRefreshToken).Result;
}

您可以在http://daimto.com/google-oauth2-csharp/#Loading_Stored_refresh-Token

找到它的基本示例

答案 1 :(得分:1)

我弄清楚它是什么,我觉得自己像个白痴。

Back-story - 这开始是一个MVC应用程序,我在Google云端控制台中注册了应用程序,并在其中运行OAuth舞蹈。然后我被告知我们需要一个Windows服务来解析某些特定位置的一些数据,所以我为此创建了一个新用户并手动完成了OAuth舞蹈并存储了刷新令牌......但是,我完全忽略了在云控制台中注册一个新应用程序作为“已安装的应用程序”,而我的第一个应用程序是“Web应用程序”,其中存在问题。我直到今天下午才意识到我只是从网络应用程序重新使用相同的ClientId和ClientSecret值...一旦我将新应用程序注册为已安装的应用程序,然后手动浏览OAuth舞蹈以获得新的令牌,我在胜利服务中更新了我的所有参考资料,指出这些东西并完全访问了所有内容,因为我应该能够......

非常感谢DaImTo,他指出我应该使用自己的IDataStore实现 - 这是我将继续使用的最有用的信息。

如果我浪费了任何人的时间,我很抱歉,希望这有助于某人:)