谷歌+ API:如何使用refreshtoken避免在每次我的应用启动时请求访问?

时间:2023-01-22 09:15:01

I'm trying to use the Google+ API to access info for the authenticated user. I've copied some code from one of the samples, which works fine (below), however I'm having trouble making it work in a way I can reuse the token across app-launches.

我正在尝试使用谷歌+ API访问经过身份验证的用户的信息。我已经从其中一个示例中复制了一些代码,它们工作得很好(见下文),但是我在使其工作时遇到了麻烦,无法在应用程序启动时重用该标记。

I tried capturing the "RefreshToken" property and using provider.RefreshToken() (amongst other things) and always get a 400 Bad Request response.

我尝试捕获“RefreshToken”属性并使用provider.RefreshToken()(在其他事情中),并总是得到400个糟糕的请求响应。

Does anyone know how to make this work, or know where I can find some samples? The Google Code site doesn't seem to cover this :-(

有没有人知道怎么做这个工作,或者知道哪里可以找到一些样品?谷歌代码站点似乎没有包含以下内容:-(

class Program
{
    private const string Scope = "https://www.googleapis.com/auth/plus.me";

    static void Main(string[] args)
    {
        var provider = new NativeApplicationClient(GoogleAuthenticationServer.Description);
        provider.ClientIdentifier = "BLAH";
        provider.ClientSecret = "BLAH";
        var auth = new OAuth2Authenticator<NativeApplicationClient>(provider, GetAuthentication);

        var plus = new PlusService(auth);
        plus.Key = "BLAH";
        var me = plus.People.Get("me").Fetch();
        Console.WriteLine(me.DisplayName);
    }

    private static IAuthorizationState GetAuthentication(NativeApplicationClient arg)
    {
        // Get the auth URL:
        IAuthorizationState state = new AuthorizationState(new[] { Scope });
        state.Callback = new Uri(NativeApplicationClient.OutOfBandCallbackUrl);
        Uri authUri = arg.RequestUserAuthorization(state);

        // Request authorization from the user (by opening a browser window):
        Process.Start(authUri.ToString());
        Console.Write("  Authorization Code: ");
        string authCode = Console.ReadLine();
        Console.WriteLine();

        // Retrieve the access token by using the authorization code:
        return arg.ProcessUserAuthorization(authCode, state);
    }
}

5 个解决方案

#1


20  

Here is an example. Make sure you add a string setting called RefreshToken and reference System.Security or find another way to safely store the refresh token.

这是一个例子。确保您添加了一个名为RefreshToken和reference System的字符串设置。安全性或找到另一种安全存储刷新令牌的方法。

    private static byte[] aditionalEntropy = { 1, 2, 3, 4, 5 };

    private static IAuthorizationState GetAuthorization(NativeApplicationClient arg)
    {
        // Get the auth URL:
        IAuthorizationState state = new AuthorizationState(new[] { PlusService.Scopes.PlusMe.GetStringValue() });
        state.Callback = new Uri(NativeApplicationClient.OutOfBandCallbackUrl);

        string refreshToken = LoadRefreshToken();
        if (!String.IsNullOrWhiteSpace(refreshToken))
        {
            state.RefreshToken = refreshToken;

            if (arg.RefreshToken(state))
                return state;
        }

        Uri authUri = arg.RequestUserAuthorization(state);

        // Request authorization from the user (by opening a browser window):
        Process.Start(authUri.ToString());
        Console.Write("  Authorization Code: ");
        string authCode = Console.ReadLine();
        Console.WriteLine();

        // Retrieve the access token by using the authorization code:
        var result = arg.ProcessUserAuthorization(authCode, state);

        StoreRefreshToken(state);
        return result;
    }

    private static string LoadRefreshToken()
    {
        return Encoding.Unicode.GetString(ProtectedData.Unprotect(Convert.FromBase64String(Properties.Settings.Default.RefreshToken), aditionalEntropy, DataProtectionScope.CurrentUser));
    }

    private static void StoreRefreshToken(IAuthorizationState state)
    {
        Properties.Settings.Default.RefreshToken = Convert.ToBase64String(ProtectedData.Protect(Encoding.Unicode.GetBytes(state.RefreshToken), aditionalEntropy, DataProtectionScope.CurrentUser));
        Properties.Settings.Default.Save();
    }

#2


11  

The general idea is as follows:

大意是:

  1. You redirect the user to Google's Authorization Endpoint.

    您将用户重定向到谷歌的授权端点。

  2. You obtain a short-lived Authorization Code.

    您获得了一个短期的授权代码。

  3. You immediately exchange the Authorization Code for a long-lived Access Token using Google's Token Endpoint. The Access Token comes with an expiry date and a Refresh Token.

    您可以立即使用谷歌的令牌端点为一个长期存在的访问令牌交换授权代码。访问令牌带有过期日期和刷新令牌。

  4. You make requests to Google's API using the Access Token.

    使用访问令牌对谷歌的API发出请求。

You can reuse the Access Token for as many requests as you like until it expires. Then you can use the Refresh Token to request a new Access Token (which comes with a new expiry date and a new Refresh Token).

在访问令牌过期之前,您可以对任意多个请求重用它。然后,您可以使用Refresh Token请求新的访问令牌(带有新的过期日期和新的刷新令牌)。

See also:

参见:

#3


3  

I also had problems with getting "offline" authentication to work (i.e. acquiring authentication with a refresh token), and got HTTP-response 400 Bad request with a code similar to the OP's code. However, I got it to work with the line client.ClientCredentialApplicator = ClientCredentialApplicator.PostParameter(this.clientSecret); in the Authenticate-method. This is essential to get a working code -- I think this line forces the clientSecret to be sent as a POST-parameter to the server (instead of as a HTTP Basic Auth-parameter).

我还遇到了使“脱机”身份验证工作的问题(例如,使用refresh令牌获取身份验证),以及使用类似OP代码的HTTP-response 400错误请求。但是,我让它与直线客户端一起工作。ClientCredentialApplicator = ClientCredentialApplicator.PostParameter(this.clientSecret);authenticate方法。这对于获得一个工作代码至关重要——我认为这一行将把clientSecret作为后参数发送给服务器(而不是作为一个HTTP基本的Auth-parameter)。

This solution assumes that you've already got a client ID, a client secret and a refresh-token. Note that you don't need to enter an access-token in the code. (A short-lived access-code is acquired "under the hood" from the Google server when sending the long-lived refresh-token with the line client.RefreshAuthorization(state);. This access-token is stored as part of the auth-variable, from where it is used to authorize the API-calls "under the hood".)

这个解决方案假定您已经拥有了一个客户ID、一个客户端秘密和一个刷新令牌。注意,您不需要在代码中输入访问令牌。(在用行客户端. refreshauthorization (state)发送长寿命刷新令牌时,从谷歌服务器获取短寿命的访问代码。这个访问令牌被存储为auth-variable的一部分,从它被用来授权api调用“在hood”中。

A code example that works for me with Google API v3 for accessing my Google Calendar:

使用谷歌API v3访问我的谷歌日历的代码示例:

class SomeClass
{

    private string clientID         = "XXXXXXXXX.apps.googleusercontent.com";
    private string clientSecret     = "MY_CLIENT_SECRET";
    private string refreshToken     = "MY_REFRESH_TOKEN";
    private string primaryCal       = "MY_GMAIL_ADDRESS";

    private void button2_Click_1(object sender, EventArgs e)
    {
        try
        {
            NativeApplicationClient client = new NativeApplicationClient(GoogleAuthenticationServer.Description, this.clientID, this.clientSecret);
            OAuth2Authenticator<NativeApplicationClient> auth = new OAuth2Authenticator<NativeApplicationClient>(client, Authenticate);

            // Authenticated and ready for API calls...

            // EITHER Calendar API calls (tested):
            CalendarService cal = new CalendarService(auth);
            EventsResource.ListRequest listrequest = cal.Events.List(this.primaryCal);
            Google.Apis.Calendar.v3.Data.Events events = listrequest.Fetch();
            // iterate the events and show them here.

            // OR Plus API calls (not tested) - copied from OP's code:
            var plus = new PlusService(auth);
            plus.Key = "BLAH";  // don't know what this line does.
            var me = plus.People.Get("me").Fetch();
            Console.WriteLine(me.DisplayName);

            // OR some other API calls...
        }
        catch (Exception ex)
        {
            Console.WriteLine("Error while communicating with Google servers. Try again(?). The error was:\r\n" + ex.Message + "\r\n\r\nInner exception:\r\n" + ex.InnerException.Message);
        }
    }

    private IAuthorizationState Authenticate(NativeApplicationClient client)
    {
        IAuthorizationState state = new AuthorizationState(new string[] { }) { RefreshToken = this.refreshToken };

        // IMPORTANT - does not work without:
        client.ClientCredentialApplicator = ClientCredentialApplicator.PostParameter(this.clientSecret);

        client.RefreshAuthorization(state);
        return state;
    }
}

#4


2  

The OAuth 2.0 spec is not yet finished, and there is a smattering of spec implementations out there across the various clients and services that cause these errors to appear. Mostly likely you're doing everything right, but the DotNetOpenAuth version you're using implements a different draft of OAuth 2.0 than Google is currently implementing. Neither part is "right", since the spec isn't yet finalized, but it makes compatibility something of a nightmare.

OAuth 2.0规范还没有完成,在不同的客户端和服务中有少量的规范实现会导致这些错误的出现。大多数情况下,您都做得很好,但是您使用的DotNetOpenAuth版本实现的OAuth 2.0草案与谷歌当前实现的版本不同。这两个部分都不是“正确的”,因为规范还没有最终确定,但是它使兼容性变成了一场噩梦。

You can check that the DotNetOpenAuth version you're using is the latest (in case that helps, which it might), but ultimately you may need to either sit tight until the specs are finalized and everyone implements them correctly, or read the Google docs yourself (which presumably describe their version of OAuth 2.0) and implement one that specifically targets their draft version.

你可以检查DotNetOpenAuth你用的是最新的版本(帮助,它可能),但最终你可能需要静观其变,直到完成规格正确,每个人实现他们,或自己阅读Google docs(大概描述他们的版本的OAuth 2.0)和实现一个专门针对他们的草案。

#5


2  

I would recommend looking at the "SampleHelper" project in the Samples solution of the Google .NET Client API:

我建议在谷歌.NET客户端API的示例解决方案中查看“SampleHelper”项目:

This file shows both how to use Windows Protected Data to store a Refresh token, and it also shows how to use a Local Loopback Server and different techniques to capture the Access code instead of having the user enter it manually.

这个文件展示了如何使用Windows Protected数据来存储一个Refresh token,它还展示了如何使用本地环回服务器和不同的技术来捕获访问代码,而不是让用户手工输入。

One of the samples in the library which use this method of authorization can be found below:

图书馆中使用这种授权方法的一个示例如下:

#1


20  

Here is an example. Make sure you add a string setting called RefreshToken and reference System.Security or find another way to safely store the refresh token.

这是一个例子。确保您添加了一个名为RefreshToken和reference System的字符串设置。安全性或找到另一种安全存储刷新令牌的方法。

    private static byte[] aditionalEntropy = { 1, 2, 3, 4, 5 };

    private static IAuthorizationState GetAuthorization(NativeApplicationClient arg)
    {
        // Get the auth URL:
        IAuthorizationState state = new AuthorizationState(new[] { PlusService.Scopes.PlusMe.GetStringValue() });
        state.Callback = new Uri(NativeApplicationClient.OutOfBandCallbackUrl);

        string refreshToken = LoadRefreshToken();
        if (!String.IsNullOrWhiteSpace(refreshToken))
        {
            state.RefreshToken = refreshToken;

            if (arg.RefreshToken(state))
                return state;
        }

        Uri authUri = arg.RequestUserAuthorization(state);

        // Request authorization from the user (by opening a browser window):
        Process.Start(authUri.ToString());
        Console.Write("  Authorization Code: ");
        string authCode = Console.ReadLine();
        Console.WriteLine();

        // Retrieve the access token by using the authorization code:
        var result = arg.ProcessUserAuthorization(authCode, state);

        StoreRefreshToken(state);
        return result;
    }

    private static string LoadRefreshToken()
    {
        return Encoding.Unicode.GetString(ProtectedData.Unprotect(Convert.FromBase64String(Properties.Settings.Default.RefreshToken), aditionalEntropy, DataProtectionScope.CurrentUser));
    }

    private static void StoreRefreshToken(IAuthorizationState state)
    {
        Properties.Settings.Default.RefreshToken = Convert.ToBase64String(ProtectedData.Protect(Encoding.Unicode.GetBytes(state.RefreshToken), aditionalEntropy, DataProtectionScope.CurrentUser));
        Properties.Settings.Default.Save();
    }

#2


11  

The general idea is as follows:

大意是:

  1. You redirect the user to Google's Authorization Endpoint.

    您将用户重定向到谷歌的授权端点。

  2. You obtain a short-lived Authorization Code.

    您获得了一个短期的授权代码。

  3. You immediately exchange the Authorization Code for a long-lived Access Token using Google's Token Endpoint. The Access Token comes with an expiry date and a Refresh Token.

    您可以立即使用谷歌的令牌端点为一个长期存在的访问令牌交换授权代码。访问令牌带有过期日期和刷新令牌。

  4. You make requests to Google's API using the Access Token.

    使用访问令牌对谷歌的API发出请求。

You can reuse the Access Token for as many requests as you like until it expires. Then you can use the Refresh Token to request a new Access Token (which comes with a new expiry date and a new Refresh Token).

在访问令牌过期之前,您可以对任意多个请求重用它。然后,您可以使用Refresh Token请求新的访问令牌(带有新的过期日期和新的刷新令牌)。

See also:

参见:

#3


3  

I also had problems with getting "offline" authentication to work (i.e. acquiring authentication with a refresh token), and got HTTP-response 400 Bad request with a code similar to the OP's code. However, I got it to work with the line client.ClientCredentialApplicator = ClientCredentialApplicator.PostParameter(this.clientSecret); in the Authenticate-method. This is essential to get a working code -- I think this line forces the clientSecret to be sent as a POST-parameter to the server (instead of as a HTTP Basic Auth-parameter).

我还遇到了使“脱机”身份验证工作的问题(例如,使用refresh令牌获取身份验证),以及使用类似OP代码的HTTP-response 400错误请求。但是,我让它与直线客户端一起工作。ClientCredentialApplicator = ClientCredentialApplicator.PostParameter(this.clientSecret);authenticate方法。这对于获得一个工作代码至关重要——我认为这一行将把clientSecret作为后参数发送给服务器(而不是作为一个HTTP基本的Auth-parameter)。

This solution assumes that you've already got a client ID, a client secret and a refresh-token. Note that you don't need to enter an access-token in the code. (A short-lived access-code is acquired "under the hood" from the Google server when sending the long-lived refresh-token with the line client.RefreshAuthorization(state);. This access-token is stored as part of the auth-variable, from where it is used to authorize the API-calls "under the hood".)

这个解决方案假定您已经拥有了一个客户ID、一个客户端秘密和一个刷新令牌。注意,您不需要在代码中输入访问令牌。(在用行客户端. refreshauthorization (state)发送长寿命刷新令牌时,从谷歌服务器获取短寿命的访问代码。这个访问令牌被存储为auth-variable的一部分,从它被用来授权api调用“在hood”中。

A code example that works for me with Google API v3 for accessing my Google Calendar:

使用谷歌API v3访问我的谷歌日历的代码示例:

class SomeClass
{

    private string clientID         = "XXXXXXXXX.apps.googleusercontent.com";
    private string clientSecret     = "MY_CLIENT_SECRET";
    private string refreshToken     = "MY_REFRESH_TOKEN";
    private string primaryCal       = "MY_GMAIL_ADDRESS";

    private void button2_Click_1(object sender, EventArgs e)
    {
        try
        {
            NativeApplicationClient client = new NativeApplicationClient(GoogleAuthenticationServer.Description, this.clientID, this.clientSecret);
            OAuth2Authenticator<NativeApplicationClient> auth = new OAuth2Authenticator<NativeApplicationClient>(client, Authenticate);

            // Authenticated and ready for API calls...

            // EITHER Calendar API calls (tested):
            CalendarService cal = new CalendarService(auth);
            EventsResource.ListRequest listrequest = cal.Events.List(this.primaryCal);
            Google.Apis.Calendar.v3.Data.Events events = listrequest.Fetch();
            // iterate the events and show them here.

            // OR Plus API calls (not tested) - copied from OP's code:
            var plus = new PlusService(auth);
            plus.Key = "BLAH";  // don't know what this line does.
            var me = plus.People.Get("me").Fetch();
            Console.WriteLine(me.DisplayName);

            // OR some other API calls...
        }
        catch (Exception ex)
        {
            Console.WriteLine("Error while communicating with Google servers. Try again(?). The error was:\r\n" + ex.Message + "\r\n\r\nInner exception:\r\n" + ex.InnerException.Message);
        }
    }

    private IAuthorizationState Authenticate(NativeApplicationClient client)
    {
        IAuthorizationState state = new AuthorizationState(new string[] { }) { RefreshToken = this.refreshToken };

        // IMPORTANT - does not work without:
        client.ClientCredentialApplicator = ClientCredentialApplicator.PostParameter(this.clientSecret);

        client.RefreshAuthorization(state);
        return state;
    }
}

#4


2  

The OAuth 2.0 spec is not yet finished, and there is a smattering of spec implementations out there across the various clients and services that cause these errors to appear. Mostly likely you're doing everything right, but the DotNetOpenAuth version you're using implements a different draft of OAuth 2.0 than Google is currently implementing. Neither part is "right", since the spec isn't yet finalized, but it makes compatibility something of a nightmare.

OAuth 2.0规范还没有完成,在不同的客户端和服务中有少量的规范实现会导致这些错误的出现。大多数情况下,您都做得很好,但是您使用的DotNetOpenAuth版本实现的OAuth 2.0草案与谷歌当前实现的版本不同。这两个部分都不是“正确的”,因为规范还没有最终确定,但是它使兼容性变成了一场噩梦。

You can check that the DotNetOpenAuth version you're using is the latest (in case that helps, which it might), but ultimately you may need to either sit tight until the specs are finalized and everyone implements them correctly, or read the Google docs yourself (which presumably describe their version of OAuth 2.0) and implement one that specifically targets their draft version.

你可以检查DotNetOpenAuth你用的是最新的版本(帮助,它可能),但最终你可能需要静观其变,直到完成规格正确,每个人实现他们,或自己阅读Google docs(大概描述他们的版本的OAuth 2.0)和实现一个专门针对他们的草案。

#5


2  

I would recommend looking at the "SampleHelper" project in the Samples solution of the Google .NET Client API:

我建议在谷歌.NET客户端API的示例解决方案中查看“SampleHelper”项目:

This file shows both how to use Windows Protected Data to store a Refresh token, and it also shows how to use a Local Loopback Server and different techniques to capture the Access code instead of having the user enter it manually.

这个文件展示了如何使用Windows Protected数据来存储一个Refresh token,它还展示了如何使用本地环回服务器和不同的技术来捕获访问代码,而不是让用户手工输入。

One of the samples in the library which use this method of authorization can be found below:

图书馆中使用这种授权方法的一个示例如下: