Вход в Google: "Сертификат для ключа с идентификатором xxxx не найден" при использовании пакета Python google-auth

Я поддерживаю веб-сайт и его мобильные приложения (iOS и Android). Для входа в Google в мобильном приложении я использую пакет google-auth Python на стороне сервера.

Примерно месяц назад я начал получать сообщения об ошибках, связанных с входом в Google, со стороны сервера. Сообщение об ошибке выглядит следующим образом:

Сертификат для идентификатора ключа 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 JWT по списку общедоступных сертификатов Google.

Погружаясь в код Google-Auth, я вижу, что verify_oauth2_token() Функция извлекает публичные сертификаты Google с URL https://www.googleapis.com/oauth2/v1/certs. Кажется, что иногда для некоторых токенов Google ID, отправляемых с некоторых телефонов Android, идентификатор ключа не может быть найден в этом URL.

Вот некоторые другие детали, которые могут быть полезны:

  • Похоже, что приложение iOS не имеет такой проблемы. От USER_AGENT В шапке я вижу, что ошибка происходит только в приложении Android (USER_AGENT=okhttp/3.11.0). И это происходит только на некоторых устройствах Android, а не на всех.
  • Мне было интересно, если это происходит только для телефонов Android из Китая (например, если они подключаются через VPN). Поэтому я также проверил IP-адрес пользователя. Но оказалось, что некоторые из этих пользователей были из Европы.
  • Некоторый ключевой идентификатор повторяется снова и снова в журналах ошибок сервера. Например, идентификатор ключа aa436c3f63b281ce0d976da0b51a34860ff960eb видели десятки раз, с начала ноября по настоящее время (конец декабря).
  • Я постоянно вижу эту ошибку, несколько раз (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 (Huawei P20, купленный во Франции), я полностью застрял.

Но у одного из моих друзей возникла эта проблема, и он купил свой телефон Android в Гонконге. Это заставляет меня задуматься, возможно ли, чтобы в некоторых странах для входа в Google использовались другие сертификаты, отличные от общедоступных, по адресу https://www.googleapis.com/oauth2/v1/certs?

Я не думаю, что это ошибка в пакете google-auth. Мне интересно, слышал ли кто-нибудь из вас об этой ошибке, и мог бы дать мне подсказку о ее возможной причине?

Заранее спасибо!

0 ответов

Хорошо, я наконец понял это. Я публикую здесь свои выводы, надеясь, что это может помочь кому-то другому.

В коде 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);
}

Ключевым моментом является то, что: мне нужно проверить, вошел ли пользователь уже в Google. Если да, мне нужно вывести пользователя из системы и повторно запустить действие "Вход с помощью Google".

Поскольку Android имеет встроенную поддержку учетной записи Google, я полагаю, что ОС может кэшировать учетную запись Google пользователя, если пользователь уже аутентифицирован (в каком-то другом приложении или в масштабе всей системы). Но эта кешированная учетная запись, вероятно, может содержать просроченный токен идентификатора. Принудительный выход пользователя из системы и повторный вход даст мне новый новый идентификатор.

Это также объясняет, почему в моем приложении для iOS нет этой проблемы. Потому что iOS никогда не кеширует пользовательский аккаунт Google.

Другие вопросы по тегам