2013-12-18 380 views
2

我有一個iPad應用程序,允許用戶使用OAuth2登錄到他們的Gmail帳戶。到目前爲止,登錄過程和電子郵件抓取都是成功的。但是,當應用程序關閉並在(很長)時間段後重新打開時,即使先前使用相同憑據的登錄成功,也會生成「無效憑據」錯誤。Google OAuth登錄錯誤:憑證無效

登錄流程: 1 )到Gmail使用OAuth 2. 2)用戶的電子郵件地址和oAuthToken由GTMOAuth2Authentication對象提供的用戶日誌保存用於以後登錄的核心數據。 3)IMAP會話使用保存的電子郵件地址和OAuthToken創建。

這裏是相關的代碼

Google登錄

- (void)gmailOAuthLogin 
{ 
    NSDictionary *googleSettings = [[EmailServicesInfo emailServicesInfoDict] objectForKey:Gmail]; 

    GTMOAuth2ViewControllerTouch *googleSignInController = 
    [[GTMOAuth2ViewControllerTouch alloc] initWithScope:GmailScope clientID:GmailAppClientID clientSecret:GmailClientSecret keychainItemName:KeychainItemName completionHandler:^(GTMOAuth2ViewControllerTouch *googleSignInController, GTMOAuth2Authentication *auth, NSError *error){ 

    if (error != nil) { 
     //handle error 

    } else { 

    [[ModelManager sharedInstance] authenticateWithEmailAddress:[auth userEmail] 
     oAuthToken:[auth accessToken] imapHostname:[googleSettings objectForKey:IMAPHostName] imapPort:[[googleSettings objectForKey:IMAPPort]integerValue] smtpHostname:[googleSettings objectForKey:SMTPHostName] smtpPort:[[googleSettings objectForKey:SMTPPort]integerValue] type:EmailProtocolTypeImapAndSmtpGMail success:^(Account *account) { 

     //create IMAP session using above arguments 

     } failure:^(NSError *error) { 
      //handle error      
     }]; 

     } 
    }]; 

     [self presentGoogleSignInController:googleSignInController]; 
    } 

創建IMAP會話使用MailCore2

- (void)authenticateWithEmailAddress:(NSString *)emailAddress password:(NSString *)password oAuthToken:(NSString *)oAuthToken imapHostname:(NSString *)imapHostname imapPort:(NSInteger)imapPort smtpHostname:(NSString *)smtpHostname smtpPort:(NSInteger)smtpPort success:(void (^)())success failure:(void (^)(NSError *))failure 
{ 
    self.imapSession = [[MCOIMAPSession alloc] init]; 
    self.imapSession.hostname = imapHostname; 
    self.imapSession.port = imapPort; 
    self.imapSession.username = emailAddress; 
    self.imapSession.connectionType = MCOConnectionTypeTLS; 
    self.imapSession.password = nil; 
    self.imapSession.OAuth2Token = oAuthToken; 
    self.imapSession.authType = nil != oAuthToken ? MCOAuthTypeXOAuth2 :  
    self.imapSession.authType; 

    [self.imapSession setConnectionLogger:^(void * connectionID, MCOConnectionLogType type,  
NSData * data){ 
NSLog(@"MCOIMAPSession: [%i] %@", type, [[NSString alloc] initWithData:data encoding:NSUTF8StringEncoding]); 
    }]; 

    self.smtpSession = [[MCOSMTPSession alloc] init]; 
    self.smtpSession.hostname = smtpHostname; 
    self.smtpSession.port = smtpPort; 
    self.smtpSession.username = emailAddress; 
    self.smtpSession.connectionType = MCOConnectionTypeTLS; 
    self.smtpSession.password = nil; 
    self.smtpSession.OAuth2Token = oAuthToken; 
    self.smtpSession.authType = nil != oAuthToken ? MCOAuthTypeXOAuth2 : 
    self.smtpSession.authType; 

    [self.smtpSession setConnectionLogger:^(void * connectionID, MCOConnectionLogType type, NSData * data){ 
    NSLog(@"MCOSMTPSession: [%i] %@", type, [[NSString alloc] initWithData:data encoding:NSUTF8StringEncoding]); 
    }]; 

    [[self.imapSession checkAccountOperation] start:^(NSError *error) { 
    if (nil == error) { 
     success(); 
    } else { 
     failure(error); //FAILS WITH INVALID CREDENTIALS ERROR 
    } 
    }]; 
} 

再次,上面的代碼工作正常,除非應用程序還沒有在一段時間內使用。我不知道如果我需要刷新OAuthToken與否,所以我試着做在啓動應用程序的以下情況:

GTMOAuth2Authentication *auth = [GTMOAuth2ViewControllerTouch authForGoogleFromKeychainForName:KeychainItemName clientID:GmailAppClientID clientSecret:GmailClientSecret]; 

    BOOL canAuthorize = [auth canAuthorize]; //returns YES 
    NSDictionary *googleSettings = [[EmailServicesInfo emailServicesInfoDict] objectForKey:Gmail]; 

    [[ModelManager sharedDefaultInstance] authenticateWithEmailAddress:[auth userEmail] oAuthToken:[auth refreshToken] imapHostname:[googleSettings objectForKey:IMAPHostName] imapPort:[[googleSettings objectForKey:IMAPPort]integerValue] smtpHostname:[googleSettings objectForKey:SMTPHostName] smtpPort:[[googleSettings objectForKey:SMTPPort]integerValue] type:EmailProtocolTypeImapAndSmtpGMail success:^(Account *account) { 

    //create IMAP session 

} failure:^(NSError *error) { 
    NSLog(@"failure %@", error); 
}]; 

但我仍然得到同樣的錯誤。我不知道爲什麼OAuth令牌停止工作或如何解決此問題。由於用戶能夠保存多個帳戶,我想知道是否需要爲核心數據中的每個帳戶保存刷新令牌,並在訪問令牌停止工作時使用該令牌?

回答

0

(免責聲明 - 我不知道的iOS或GTM-的oauth2庫,但我不知道谷歌的OAuth實現。)

概念你需要堅持用戶的刷新令牌。刷新令牌是一個長期生存的憑證,它與您的客戶端密鑰一起使用來獲取用於實際API調用的短期訪問令牌。

如果您預計在短時間內進行多次通話,那麼您的應用程序通常會同時持續刷新令牌和訪問令牌(當前訪問令牌將持續1小時)。

大家都說,它看起來像gtm-oauth2庫應該照顧堅持這些已經(看起來像authForGoogleFromKeychainForName這樣做)。

我認爲您需要幫助的是獲取可用於啓動IMAP會話的最新訪問令牌。

gtm-oauth2庫確實包含authorizeRequest方法。它會獲取有關您打算創建的HTTP請求的信息並添加相應的授權標頭。它看起來像這個代碼將檢查訪問令牌的狀態,並在必要時刷新它。

雖然我知道您無法發出HTTP請求(您需要說IMAP),但我的建議是使用此方法與虛擬NSMutableURLRequest - 然後,一旦它完成,不實際發送HTTP請求,而是檢查它添加的頭並從那裏拉取訪問令牌。

參見: https://code.google.com/p/gtm-oauth2/wiki/Introduction#Using_the_Authentication_Tokens

希望幫助 - 我沒有在iOS環境中測試它。

+0

感謝您的回覆。這是有道理的,只有一點困惑:如果我的應用程序允許用戶保存多個Gmail帳戶並在它們之間來回切換,那麼keychainItemName是否會因爲添加每個新帳戶而被覆蓋? (如果這個問題沒有意義,請原諒我,我仍然圍繞着這一切如何運作的大腦)。如果是這樣,那麼我不知道我將如何獲得特定帳戶的身份驗證對象以從中獲取新的令牌。 – jac300

+0

你的問題是有道理的。每個用戶帳戶都需要爲您的應用程序授予一個單獨的刷新令牌。從粗略的代碼檢查看起來好像gtm-oauth2庫一次只能存在一個,但我不是100%確定的。也許嘗試合成併爲第二個和後續帳戶指定一個不同的keychainItemName? – aeijdenberg

+0

謝謝,我會嘗試。這明確說明了一些事情。 – jac300

相關問題