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
相关产品推荐
相关产品推荐

