You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

GCP签名JWT的kid消失导致验证失败问题咨询

GCP托管服务账号JWT签名与验证问题

我们通过Python的IAMCredentialsClient实现了JWT签名流程,代码如下:

def sign_jwt(self, payload, aud=None):
    """
    @param payload:
    @param aud:
    @return:
    """
    self.logger.info("Signing jwt")
    payload.update({
        "iss": self.service_account_email,
        "aud": aud if aud else Identity.JWT_AUDIENCE,
        "iat": int(time.time()),
    })
    self.logger.info("Payload", extra=payload)
    if not self.signer:
        self.signer = IAMCredentialsClient()
    signed_jwt_resp = self.signer.sign_jwt(name=self.signer_name, payload=json.dumps(payload))
    self.logger.info("Jwt signed successfully")
    return signed_jwt_resp.signed_jwt

后续将签名后的JWT用于内部交互,依赖GCP的验证能力保障机制运行,验证流程代码如下:

def _get_certificates(self, service_account):
    """
    @param service_account:
    @return:
    """
    return requests.get(f"https://www.googleapis.com/robot/v1/metadata/x509/{service_account}").json()

def _get_jwt_pkey(self, iss, kid):
    """
    @param iss:
    @param kid:
    @return:
    """
    if not iss.endswith(f"@{self.app.project_id}.iam.gserviceaccount.com"):
        raise Exception("Invalid issuer")
    if iss not in self.certificates:
        self.certificates[iss] = self._get_certificates(iss)

    certificate = self.certificates.get(iss).get(kid)
    # now translate to pkey
    # load the public key from the certificate
    private_key = load_pem_x509_certificate(certificate.encode()).public_key()
    # translate public key bytes to PEM
    public_key = private_key.public_bytes(Encoding.PEM, PublicFormat.SubjectPublicKeyInfo)
    return public_key

def verify_jwt(self, jwt):
    """
    @param jwt:
    @return:
    """
    try:
        # extract metadata [NO VERIFICATION]
        header, payload, signed_section, signature = google_unverified_decode(jwt)
        # get appropriate public key
        public_key = self._get_jwt_pkey(payload.get("iss"), header.get("kid"))

        # Actual verify the token
        decoded_token = pyjwt.decode(jwt, key=public_key, algorithms=["RS256"], audience=Identity.JWT_AUDIENCE)
        return decoded_token
    except Exception:
        self.logger.exception("Failed to verify jwt")
        raise

运行一段时间后,突然出现签名JWT的kid不存在于对应证书地址返回内容中的情况,导致验证失败。针对该问题,解答如下:


问题解答

1. 该地址中的密钥为何会发生轮换?

GCP自动轮换托管服务账号密钥是安全机制的核心环节,目的是避免长期使用同一密钥带来的泄露风险,遵循安全最佳实践,降低密钥被恶意利用的潜在危害。托管服务账号的密钥完全由GCP管理,用户无法手动干预轮换操作。

2. Google何时轮换托管服务账号密钥?

GCP未公开具体轮换周期,但通常会定期执行(比如每90天左右);同时在检测到潜在安全风险时,会触发紧急轮换;此外,服务账号权限发生重大变更时,也可能触发密钥轮换。

3. 如何解决此验证失败问题?

当前代码的核心问题是证书缓存未更新,导致旧缓存中没有新轮换的密钥。解决方案如下:

  • 移除永久缓存逻辑,每次验证JWT时重新拉取最新证书列表;或设置合理的缓存过期时间(比如1小时),到期后强制刷新。
  • 当首次查找kid失败时,主动重新拉取证书列表再尝试查找,避免缓存过期导致的问题。
  • 推荐使用GCP官方的google-auth库处理JWT验证,该库已内置密钥轮换的自动处理逻辑,无需手动管理证书缓存。

修改后的_get_jwt_pkey示例(添加缓存失效重试):

def _get_jwt_pkey(self, iss, kid):
    if not iss.endswith(f"@{self.app.project_id}.iam.gserviceaccount.com"):
        raise Exception("Invalid issuer")
    
    # 先尝试缓存查找,失败则重新拉取证书
    certificate = self.certificates.get(iss, {}).get(kid)
    if not certificate:
        self.certificates[iss] = self._get_certificates(iss)
        certificate = self.certificates[iss].get(kid)
    
    if not certificate:
        raise Exception(f"kid {kid} not found for issuer {iss}")
    
    # 公钥转换逻辑不变
    private_key = load_pem_x509_certificate(certificate.encode()).public_key()
    public_key = private_key.public_bytes(Encoding.PEM, PublicFormat.SubjectPublicKeyInfo)
    return public_key

4. 相关技术文档在哪里可以找到?

可在GCP官方文档中查找以下内容:

  • 托管服务账号密钥管理说明
  • IAMCredentials API的JWT签名文档
  • JWT验证最佳实践指南
  • google-auth库使用文档

内容的提问来源于stack exchange,提问作者Tal Borenstein

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.08.21 22:15:36