对受 IAP 保护的应用程序的服务帐户请求导致“无效的 GCIP ID 令牌:JWT 签名无效”

问题描述

我正在尝试从 GCP 环境外部通过 Python 以编程方式访问受 IAP 保护的 App Engine 标准应用程序。 我尝试了各种方法包括此处文档中显示方法https://cloud.google.com/iap/docs/authentication-howto#iap-make-request-python。这是我的代码

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

def make_iap_request(url,client_id,method='GET',**kwargs):
    """Makes a request to an application protected by Identity-Aware Proxy.

    Args:
      url: The Identity-Aware Proxy-protected URL to fetch.
      client_id: The client ID used by Identity-Aware Proxy.
      method: The request method to use
              ('GET','OPTIONS','HEAD','POST','PUT','PATCH','DELETE')
      **kwargs: Any of the parameters defined for the request function:
                https://github.com/requests/requests/blob/master/requests/api.py
                If no timeout is provided,it is set to 90 by default.

    Returns:
      The page body,or raises an exception if the page Couldn't be retrieved.
    """
    # Set the default timeout,if missing
    if 'timeout' not in kwargs:
        kwargs['timeout'] = 90

    # Obtain an OpenID Connect (OIDC) token from Metadata server or using service
    # account.
    open_id_connect_token = id_token.fetch_id_token(Request(),client_id)
    print(f'{open_id_connect_token=}')

    # Fetch the Identity-Aware Proxy-protected URL,including an
    # Authorization header containing "Bearer " followed by a
    # Google-issued OpenID Connect token for the service account.
    resp = requests.request(
        method,url,headers={'Authorization': 'Bearer {}'.format(
            open_id_connect_token)},**kwargs)
    print(f'{resp=}')
    if resp.status_code == 403:
        raise Exception('Service account does not have permission to '
                        'access the IAP-protected application.')
    elif resp.status_code != 200:
        raise Exception(
            'Bad response from application: {!r} / {!r} / {!r}'.format(
                resp.status_code,resp.headers,resp.text))
    else:
        return resp.text

if __name__ == '__main__':
    res = make_iap_request(
        'https://MYAPP.ue.r.appspot.com/','Client ID from IAP>App Engine app>Edit OAuth Client>Client ID'
        )
    print(res)

当我在本地运行它时,我将 GOOGLE_APPLICATION_CREDENTIALS 环境变量设置为本地 JSON 凭据文件,其中包含我要使用的服务帐户的密钥。我也尝试在 Cloud Functions 中运行它,因此它大概会使用元数据服务来获取 App Engine 认服务帐户(我认为?)。

在这两种情况下,我都能生成一个看起来有效的令牌。使用 jwt.io,我看到它包含预期的数据并且签名有效。但是,当我使用令牌向应用程序发出请求时,我总是收到此异常:

来自应用程序的错误响应:401 / {'X-Goog-IAP-Generated-Response': 'true','Date': 'Tue,09 Feb 2021 19:25:43 GMT','Content-Type' : 'text/html','Server': 'Google Frontend','Content-Length': '47','Alt-Svc': 'h3-29=":443"; ma=2592000,h3-T051=":443"; ma=2592000,h3-Q050=":443"; ma=2592000,h3-Q046=":443"; ma=2592000,h3-Q043=":443"; ma=2592000,quic=":443"; ma=2592000; v="46,43"'} / '无效的 GCIP ID 令牌:JWT 签名无效'

我可能做错了什么?

解决方法

此问题的解决方案是将 Google Identity Token 交换为 Identity Platform Identity Token。

错误 Invalid GCIP ID token: JWT signature is invalid 的原因是使用由 Google RSA 私钥而不是 Google Identity Platform RSA 私钥签名的 Google 身份令牌引起的。我忽略了错误消息中的 GCIP,一旦我们确认令牌在使用中没有损坏,它就会告诉我解决方案。

在问题中,这行代码获取 Google 身份令牌:

open_id_connect_token = id_token.fetch_id_token(Request(),client_id)

以上代码行要求设置 Google Cloud 应用程序默认凭据。示例:set GOOGLE_APPLICATION_CREDENTIALS=c:\config\service-account.json

下一步是将此令牌交换为 Identity Platform 令牌:

def exchange_google_id_token_for_gcip_id_token(google_open_id_connect_token):
    SIGN_IN_WITH_IDP_API = 'https://identitytoolkit.googleapis.com/v1/accounts:signInWithIdp'
    API_KEY = '';

    url = SIGN_IN_WITH_IDP_API + '?key=' + API_KEY;

    data={
        'requestUri': 'http://localhost','returnSecureToken': True,'postBody':'id_token=' + google_open_id_connect_token + '&providerId=google.com'}

    try:
        resp = requests.post(url,data)

        res = resp.json()

        if 'error' in res:
            print("Error: {}".format(res['error']['message']))
            exit(1)

        # print(res)

        return res['idToken']
    except Exception as ex:
        print("Exception: {}".format(ex))
        exit(1)

API 密钥可以在 Google Cloud Console -> Identity Platform 中找到。右上角“应用程序设置详细信息”。这将显示 apiKeyauthDomain

可以在此链接中找到更多信息:

Exchanging a Google token for an Identity Platform token

相关问答

Selenium Web驱动程序和Java。元素在(x,y)点处不可单击。其...
Python-如何使用点“。” 访问字典成员?
Java 字符串是不可变的。到底是什么意思?
Java中的“ final”关键字如何工作?(我仍然可以修改对象。...
“loop:”在Java代码中。这是什么,为什么要编译?
java.lang.ClassNotFoundException:sun.jdbc.odbc.JdbcOdbc...