问题
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.
I'd like to make sure I'm using Xamarin.Auth and the Google APIs as intended, so here is my code:
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
}
}
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.
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...
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;
}
}
...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?
回答1:
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!
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();
}
}
});
来源:https://stackoverflow.com/questions/22967291/xamarin-auth-with-google-apis-renew-credentials