Вход в 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.