【问题标题】:Invalid credentials: Google API calendar无效凭据:Google API 日历
【发布时间】:2014-08-23 17:18:13
【问题描述】:

我是使用 Google API 的新手。我按照步骤使用 Google 客户端库在 Eclipse 中设置 Google 日历示例代码。我使用 cmd 命令 - mvn appengine:update 将代码部署到应用引擎(当尝试通过 eclipse 部署时,它给了我一个错误,说该项目不是 App Engine 项目)。通过命令行部署有效,我可以启动我的 Web 应用程序。

突然,在几次 API 请求调用后,开始出现无效凭据错误:

错误:401
域:全球
位置:授权
位置类型:标题
消息:凭据无效
原因:认证错误

我搜索了这个错误,发现如果您的身份验证令牌无效或过期,可能会发生此错误。我不确定我需要做什么才能获得新的身份验证令牌。就我而言,有一个包含客户端机密的 client_secrets.json 文件。我没有在代码中的任何地方放置/保存或使用身份验证令牌。以下是我第一次部署代码时的记忆:

  • 在我使用命令行进行部署时,有人要求我将其粘贴到 cmd 上。之后我可以启动我的应用程序。
  • 之后,每当我部署代码并启动应用程序时,都没有授权(第一次发生),随后我可以使用应用程序和 API。
  • 突然我开始收到无效凭据错误。

我使用的是从开发人员控制台生成的 client_secrets.json,我没有在代码的任何地方提供身份验证令牌,也没有保存它。我读到可以使用刷新令牌,并且可以避免身份验证令牌过期。

目前,问题是我收到无效凭据错误,我认为这是因为身份验证令牌过期。我不知道这种情况下的解决方案。它是 Google 提供的示例代码,我相信它会自动处理 Oauth 授权(与我们以编程方式调用 Oauth url、获取令牌、将其存储在某处并提供给我们后续请求的情况不同)。我需要对示例代码做什么才能解决这个问题并且以后不会遇到错误?

此外,应用程序在本地运行良好,但在部署到云时出现授权问题。谢谢。

【问题讨论】:

标签: google-app-engine oauth google-api google-calendar-api google-api-java-client


【解决方案1】:

复制/粘贴令牌实际上与应用程序用于访问日历 API 的实际凭据无关;在命令行中,App Engine SDK 通过上传示例应用获得修改您的 App Engine 项目的权限,而不管应用本身做什么。

现在,对于这里的真正问题,它实际上似乎是一个错误,其中GoogleAuthorizationCodeFlow 以某种方式收到了一个不包含refreshToken 的授权代码响应,只有一个accessToken,然后仍然继续将其存储在 Datastore 中。通常,您看到的流程应该会在第一次加载时为用户弹出一个页面,上面写着“这个应用程序想要:...具有离线访问权限”,然后你的应用程序应该得到一个 accessToken 和 @ 987654326@ 对,其中 accessToken 通常会在 1 小时内过期,但随后 Credential 对象知道如何自动捕获 401 异常并执行 refreshToken 以获取新的 accessToken,这一切都在幕后。这可能被认为是后端服务器中的错误,用于返回缺少 refreshToken 的凭证,或者客户端逻辑中的错误,仍然假设存在 refreshToken 并因此卡住而不是重新发出访问功能请求。

幸运的是,有一个简单的解决方法。现在,您看到 401 错误的事实意味着您的数据存储区以及您的 Memcache 中存储了一个粘性格式错误的凭证。导航到您的 appengine.google.com 页面,假设您还没有在该页面上提供对实时生产至关重要的 Web 应用程序,请转到左侧的 Datastore Viewer,查找 Query -> By kind: 下拉菜单,找到 StoredCredential ,检查所有项目,假设它们可能都来自您的日历示例,然后单击“删除”。同样导航到左侧菜单上的Memcache Viewer,然后单击Flush Cache

现在,后端尝试返回缺少 refreshToken 的凭据的原因似乎是由于客户端尝试错误地使用“自动批准”。事实证明,当我使用新的 JSON client_secrets 创建一个全新的客户端 ID,然后在 非常第一次 加载示例日历应用程序时,我发现我的日志语句在src/main/java/com/google/api/services/samples/calendar/appengine/server/Utils.java文件:

Credential credential = newFlow().loadCredential(userId);
if (credential.getRefreshToken() != null) {
  logger.log(Level.SEVERE, "Refresh token is not null");
} else {
  logger.log(Level.SEVERE, "Refresh token is null!");
}

然后我会为我尝试访问的每个唯一登录用户名获得一个 refreshToken。但是,如果我从 Datastore 和 Memcache 中清除凭据,强制对后续请求进行重新身份验证,我将不再看到批准提示,并且我的凭据不再具有 refreshToken,从而导致它们在 1 小时后停止工作。

解决方案(tl;dr)

在清除所有 StoredCredential 类型实体的数据存储并刷新 Memcache 后,只需将 .setApprovalPrompt("force") 添加到 src/main/java/com/google/api/services/samples/calendar/appengine/server/Utils.java 内的 newFlow() 方法即可;该方法将如下所示:

static GoogleAuthorizationCodeFlow newFlow() throws IOException {
  return new GoogleAuthorizationCodeFlow.Builder(HTTP_TRANSPORT, JSON_FACTORY,
      getClientCredential(), Collections.singleton(CalendarScopes.CALENDAR)).setDataStoreFactory(
      DATA_STORE_FACTORY).setAccessType("offline").setApprovalPrompt("force").build();
}

可选地,在获取凭据的位置添加一些日志记录(记录实际的 accessToken 尤其是 refreshToken 通常是不好的做法,但在调试期间执行一次或两次可能不会有什么坏处:),然后重新更新您的应用程序.您会发现,第一次访问您的应用时,您现在会收到批准提示,然后它应该可以安全地永远工作,因为 refreshToken 永不过期并且现在保存在您的数据存储中。

请注意,每个客户端/用户 ID 对发出的 refreshToken 数量是有限制的,如果您在调试期间确实清除了 Datastore/Memcache,它实际上会泄漏一个 refreshToken。行为只是在 25 个此类令牌之后,较早的令牌将自动停用。有关该限制的更多信息,请参阅oauth2 docs

【讨论】:

  • 由于事情没有进展,我同时在谷歌开发者的控制台上创建了一个新项目并将我的应用程序链接到新项目,只是为了测试应用程序在这次给出身份验证错误之前工作了多长时间。我将为此发表我的观察。我也一定会通过返回我之前的 Google 控制台项目来尝试您的解决方法,并尽快让您知道结果和观察结果。非常感谢您的回复。
  • 请注意,如果您没有首先使用“本地”模式测试新项目中的应用程序,那么新项目的应用程序很可能会无限期地继续工作,除非您将项目从其数据存储区中删除为出色地。我昨天在重现您的错误时意识到了这一点,因为我使用了两个不同的登录来测试,我在本地测试期间也只使用了其中一个。然后只有我在本地测试中使用的一个在 1 小时后过期,而另一个则永久有效。
【解决方案2】:

为了接收刷新令牌,您需要在授权请求中设置 access_type=offline:https://developers.google.com/accounts/docs/OAuth2WebServer#offline

请注意,您只会获得一次刷新令牌以及第一个访问令牌。

【讨论】:

    猜你喜欢
    • 2020-10-21
    • 2019-09-02
    • 2018-11-07
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2023-03-18
    • 2013-02-05
    • 1970-01-01
    相关资源
    最近更新 更多