Xamarin.Auth与谷歌的API:更新凭据?

问题描述:

我试图使用Xamarin.Auth与 Xamarin谷歌的API 来登录谷歌和访问硬盘。我已经成功地得到几乎一切工作正常,但身份验证令牌看起来大约一个小时后过期。一切一段时间的伟大工程,但约一个小时后,当我尝试访问,我得到一个凭据无效[401]错误和泄漏:

I'm trying to use Xamarin.Auth with the Xamarin Google-APIs to login to Google and access Drive. I've managed to get nearly everything working, but the authentication tokens seem to expire after about an hour. Everything works great for awhile, but after about an hour, when I attempt access, I get an Invalid Credentials [401] error and a leak:

Google.Apis.Requests.RequestError
Invalid Credentials [401]
Errors [
    Message[Invalid Credentials] Location[Authorization - header] Reason[authError] Domain[global]
]
: GoogleDriveAgent: FetchRemoteFileList() Failed! with Exception: {0}
[0:] Google.Apis.Requests.RequestError
Invalid Credentials [401]
Errors [
    Message[Invalid Credentials] Location[Authorization - header] Reason[authError] Domain[global]
]
: GoogleDriveAgent: FetchRemoteFileList() Failed! with Exception: {0}

objc[37488]: Object 0x7f1530c0 of class __NSDate autoreleased with no pool in place - just leaking - break on objc_autoreleaseNoPool() to debug
objc[37488]: Object 0x7f151e50 of class __NSCFString autoreleased with no pool in place - just leaking - break on objc_autoreleaseNoPool() to debug
//...more leaks.

我想确保我使用Xamarin.Auth和谷歌的API如预期,所以这里是我的code:

I'd like to make sure I'm using Xamarin.Auth and the Google APIs as intended, so here is my code:

在我GoogleDriveService类中,我有一个帐户存储和保存的帐户:

In my GoogleDriveService class, I've got an account store and a saved account:

AccountStore Store { 
    get {
        if (m_store == null)
            m_store = AccountStore.Create ();
        return m_store;
    }
}

Account SavedAccount { 
    get {
        var savedAccounts = Store.FindAccountsForService ("google");
        m_savedAccount = (savedAccounts as List<Account>).Count > 0 ? (savedAccounts as List<Account>) [0] : null;

        return m_savedAccount;
    }
}

我初始化会话并启动服务:

I initialize a session and start the service:

void InitializeSession ()
{
    Authenticator = new GoogleAuthenticator (ClientID, new Uri (RedirectUrl), GoogleDriveScope);
    Authenticator.Completed += HandleAuthenticationCompletedEvents;

    if (SavedAccount != null) {
        Authenticator.Account = SavedAccount;
        StartService ();
    }

    UpdateSignInStatus ();
}

bool StartService ()
{
    try {
        Service = new DriveService (Authenticator);
        return true;
    } catch (Exception ex) {
        // Log exception
        return false;
    }
}

...并响应身份完成的事件:

...and respond to authentication completed events:

void HandleAuthenticationCompletedEvents (object sender, AuthenticatorCompletedEventArgs e)
{
    if (e.IsAuthenticated) {  // Success
        UpdateSignInStatus();
        Store.Save (e.Account, "google");
        Authenticator.Account = e.Account;
        StartService();
        LoginController.DismissViewController(true, null);
    } else {                                    // Cancelled or no success
        UpdateSignInStatus();
        LoginController.DismissViewController(true, null);
        LoginController = null;
        InitializeSession ();  // Start a new session
    }
}

再次一切正常,一段时间,但随后的验证过期。据我所知,应该的,但我认为保存在AccountStore凭据应当仍然可以工作。

Again, everything works fine, for awhile, but then the authentication expires. I understand that it should, but I thought the credentials saved in the AccountStore ought to still work.

Xamarin.Auth入门文档,它说,再次调用保存会覆盖凭据,并说:这是方便到期的存储在帐户对象凭据的服务。听起来前途...

In the Xamarin.Auth getting started docs, it says that calling Save again will overwrite the credentials and that "This is convenient for services that expire the credentials stored in the account object." Sounds promising...

所以,我想另一种方法:具有IsSignedIn属性,始终覆盖在吸气的凭据...

So I tried another approach: having an IsSignedIn property that always overwrites the credentials in the getter...

public bool IsSignedIn { 
    get {
        if (Authenticator == null) {
            m_isSignedIn = false;
            return m_isSignedIn;
        }

        if (Authenticator.Account != null) {
            Store.Save (Authenticator.Account, "google"); // refresh the account store
            Authenticator.Account = SavedAccount;
            m_isSignedIn = StartService ();
        } else {
            m_isSignedIn = false;
        }

        return m_isSignedIn;
    }
}

...然后我之前的任何API调用(抓取元数据,下载等)访问IsSignedIn。这是行不通的。我仍然得到如上图所示凭据过期的错误

...and then I access IsSignedIn before any API calls (Fetching metadata, downloading, etc). It doesn't work: I'm still getting expired credentials errors shown above.

这是需要来刷新令牌的情况?我究竟做错了什么?

Is this a case of needing to refresh the token? What am I doing wrong?

访问令牌都应该比较快到期。这就是为什么第一次身份验证后,您还会收到一个refresh_token,您可以使用,如果当前到期以获得新的访问令牌。连续AUTHS不会给你一个刷新令牌必然,所以一定要保持你收到一个!

Access tokens are supposed to expire relatively quickly. This is why after the first auth you also receive a refresh_token that you can use to get a new access token if the current one expires. Consecutive auths will not give you a refresh token necessarily, so make sure you keep the one you receive!

所有你需要做一个访问令牌无效后使用refresh_token并发送OAuthRequest到谷歌的OAuth端点的TOKEN_URL。

All you have to do after an access token becomes invalid is use the refresh_token and send an OAuthRequest to the token_url of Google's OAuth endpoint.

var postDictionary = new Dictionary<string, string>();
postDictionary.Add("refresh_token", googleAccount.Properties["refresh_token"]);
postDictionary.Add("client_id", "<<your_client_id>>");
postDictionary.Add("client_secret", "<<your_client_secret>>");
postDictionary.Add("grant_type", "refresh_token");

var refreshRequest = new OAuth2Request ("POST", new Uri (OAuthSettings.TokenURL), postDictionary, googleAccount);
        refreshRequest.GetResponseAsync().ContinueWith (task => {
            if (task.IsFaulted)
                Console.WriteLine ("Error: " + task.Exception.InnerException.Message);
            else {
                string json = task.Result.GetResponseText();
                Console.WriteLine (json);
                try {
                    <<just deserialize the json response, eg. with Newtonsoft>>
                }
                catch (Exception exception) {
                    Console.WriteLine("!!!!!Exception: {0}", exception.ToString());
                    Logout();
                }
            }
        });