1

我正在维护一个网站及其移动应用程序(iOS 和 Android)。对于移动应用程序中的 Google 登录,我在服务器端使用google-auth Python 包

从大约一个月前开始,我开始从服务器端收到与 Google 登录相关的错误报告。错误消息如下所示:

未找到密钥 ID 728f4016652079b9ed99861bb09bafc5a45baa86 的证书。

服务器端 Google 登录身份验证后端遵循此文档

from google.oauth2 import id_token
from google.auth.transport import requests

# ...

try:
    # The following line may raise ValueError with message:
    # Certificate for key id xxxx not found.
    id_info = id_token.verify_oauth2_token(google_id_token, requests.Request())

    if id_info['aud'] not in VALID_CLIENT_IDS:
        logger.error('Invalid aud from Google ID token: %s', id_info['aud'])
        raise ValueError('Unverified audience.')
    # ...
except ValueError as exc:
    logger.error('Fail to verify Google ID token: %s', exc, extra={'request': request})

该错误来自google.auth.jwt 模块,在根据 Google 公共证书列表验证 Google 颁发的 JWT 时。

深入研究 google-auth 代码,我可以看到该verify_oauth2_token()函数正在从 URL https://www.googleapis.com/oauth2/v1/certs获取 Google 公开证书。似乎有时,对于从某些 Android 手机发送的某些 Google ID 令牌,在该 URL 中找不到密钥 ID。

以下是一些可能有用的其他细节:

  • iOS应用似乎没有这种问题。从USER_AGENT标题中,我可以看到该错误仅发生在 Android 应用程序 ( USER_AGENT=okhttp/3.11.0) 中。它只发生在某些 Android 设备中,而不是全部。
  • 我想知道这是否只发生在中国的安卓手机上(例如,如果他们通过 VPN 连接)。所以我也检查了用户IP地址。但事实证明,其中一些用户来自欧洲。
  • 服务器错误日志中一次又一次地重复出现某些密钥 ID。例如,aa436c3f63b281ce0d976da0b51a34860ff960eb从 11 月初到现在(12 月底),密钥 id 被看到了几十次。
  • 我经常看到这个错误,每天几次(10~30次)。

我的网站在以下环境中运行:

  • 操作系统:Linux (CentOS 7) 64 位
  • Apache 2 与 mod_wsgi 4.5.24
  • Python 3.6.7 和 Django 2.1.2
  • google-auth 版本:尝试了 1.3.0 和 1.6.1

由于我无法用我的 iPhone 或我的 Android 手机(华为 P20,在法国购买)重现此问题,我完全被卡住了。

但是,我的一个朋友现在有这个问题,他从香港买了他的安卓手机。这让我想到,对于某些国家/地区,Google 登录是否有可能使用一些不同的证书,而不是https://www.googleapis.com/oauth2/v1/certs上的公共证书?

我不认为这是 google-auth 包中的错误。我想知道你们中是否有人听说过这个错误,并且可以给我一个关于它可能的原因的提示?

提前致谢!

4

1 回答 1

3

好的,我终于想通了。我在这里发布我的发现,希望它可以帮助其他人。

服务器端 Python 代码没有任何问题。失败的原因是客户端应用提交了过期的 Google ID 令牌。

这是我的 LoginActivity 的固定版本:

...

@OnClick(R.id.google_sign_in_button)
void loginWithGoogle() {
    //
    // If user has already signed in to our app with Google, sign him out first.
    //
    // NOTE: This step is required, or the ID token might not pass the server-side validation!
    //
    // After sign-in, we need to get the user's ID token issued and signed by Google, and send
    // it back to our server for validation.
    //
    // Google is rotating its OAuth2 certificate regularly, so an old ID token issued long time
    // ago by Google might not pass the server-side validation -- if the certificate used to
    // sign the ID token has expired.
    //
    // This may happen when user has already signed in to our app with Google. In such case,
    // the ID token we get from the user's Google account is obsolete. Our server will fail to
    // validate it, with the error message:
    //
    //     Fail to verify Google ID token: Certificate for key id xxx not found.
    //
    // Google recommends using the `silentSignIn` method for the already-signed-in user
    // (see step 2 of: https://developers.google.com/identity/sign-in/android/backend-auth).
    // For the sake of simplicity, we don't do that. Instead, we go directly to step 3
    // by signing user out, giving him the option to sign-in again.
    //
    final GoogleSignInAccount account = GoogleSignIn.getLastSignedInAccount(this);
    if (account != null) {
        // User has already signed in: Sign out and sign in again.
        // NOTE: THIS IS THE FIX TO MY PROBLEM.
        mGoogleSignInClient.signOut().addOnCompleteListener(this, new OnCompleteListener<Void>() {
            @Override
            public void onComplete(@NonNull Task<Void> task) {
                startGoogleSignInActivity();
            }
        });
    } else {
        // User is not yet signed in: Start the Google sign-in flow.
        startGoogleSignInActivity();
    }
}

private void startGoogleSignInActivity() {
    final Intent intent = mGoogleSignInClient.getSignInIntent();
    startActivityForResult(intent, REQUEST_LOGIN_WITH_GOOGLE);
}

关键是:我需要检查用户是否已经用谷歌登录。如果是,我需要注销用户并重新启动 Sign-in-with-Google 活动。

由于 Android 原生支持 Google 帐户,我想如果用户已经通过身份验证(在其他一些应用程序或系统范围内),操作系统可能会缓存用户的 Google 帐户。但该缓存帐户可能包含过期的 ID 令牌。强制让用户退出并重新登录会给我一个全新的 ID 令牌。

这也解释了为什么我的 iOS 应用没有这个问题。因为 iOS 从不缓存用户的 Google 帐户。

于 2019-06-24T19:31:26.797 回答