Google OAuth 登录错误:凭据无效

Posted

技术标签:

【中文标题】Google OAuth 登录错误:凭据无效【英文标题】:Google OAuth Login Error: Invalid credentials 【发布时间】:2014-01-07 03:56:30 【问题描述】:

我有一个 iPad 应用程序,它允许用户使用 OAuth2 登录到他们的 Gmail 帐户。至此,登录过程和电子邮件获取成功。但是,当应用程序关闭并在(长时间)后重新打开时,会产生“无效凭据”错误,即使之前使用相同凭据的登录成功。

登录流程: 1) 用户使用 OAuth 2 登录 gmail。 2) GTMOAuth2Authentication 对象提供的用户电子邮件地址和 oAuthToken 保存到核心数据中,以供将来登录。 3) IMAP Session 是使用保存的电子邮件地址和 OAuthToken 创建的。

这里是相关代码

谷歌登录

- (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];
    

使用 MailCore2 创建 IMAP 会话

- (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 令牌停止工作或如何解决这个问题。由于用户能够保存多个帐户,我想知道是否需要将每个帐户的刷新令牌保存在核心数据中,并在访问令牌停止工作时使用它?

【问题讨论】:

【参考方案1】:

(免责声明 - 我不了解 ios 或 gtm-oauth2 库,但我知道 Google 的 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 环境来测试它。

【讨论】:

感谢您的回复。这是有道理的,只有一点混乱:如果我的应用程序允许用户保存多个 Gmail 帐户并在它们之间来回切换,那么 keychainItemName 是否会随着每个新帐户的添加而被覆盖? (如果这个问题没有意义,请原谅我,我仍在思考这一切是如何运作的)。如果是这样,那么我不确定如何获取特定帐户的身份验证对象以从中提取新令牌。 你的问题很有道理。每个用户帐户都需要为您的应用程序授予单独的刷新令牌。从粗略的代码检查来看,似乎 gtm-oauth2 库一次只能保留一个,但我不能 100% 确定。也许尝试为第二个和后续帐户合成和指定不同的 keychainItemName? 谢谢,我会试试的。这绝对澄清了一些事情。 您找到解决方案了吗?我有同样的问题。我已经尝试调用 authorizeRequest,但即便如此,新的访问令牌也会导致 MailCore 出现 AUTHENTICATIONFAILED 错误。

以上是关于Google OAuth 登录错误:凭据无效的主要内容,如果未能解决你的问题,请参考以下文章

Gmail 的 XOAuth2 错误凭据无效

请求具有无效的身份验证凭据。预期的 OAuth 2 访问令牌、登录 cookie 或其他有效的身份验证凭据 automl

FCM 推送 - 请求具有无效的身份验证凭据。预期的 OAuth 2 访问令牌、登录 cookie 或其他有效的身份验证凭据

是否可以使用用户和凭据(JWT)登录,而 Oauth2 可以使用 google 登录?

spring security reactive - 如何调试“无效凭据”错误?

来自 Youtube API 的 OAUTH 凭据无效