错误信息
Unable to find a signing key that matches: "2026-10"
也可能输出为
- jwt.exceptions.PyJWKClientError: Unable to find a signing key that matches: "2026-10"
- Unable to find a signing key that matches: "None"
引号中的值是令牌的 `kid`;`"None"` 表示头部中没有 `kid`。
PyJWT (Python) — jwt.exceptions.PyJWKClientError from PyJWKClient.get_signing_key_from_jwt
含义
`PyJWKClient` 获取了密钥集,但没有找到 `kid` 与令牌头部中的 `kid`(即引号中的值)相同的签名密钥。遇到未知的 `kid` 时,PyJWKClient 会刷新一次密钥集,所以这个密钥目前确实没有发布在该 URL 上。引号中的 `"None"` 表示令牌根本没有 `kid`。
原因
签名密钥先轮换、后发布
常见如果颁发者在把新密钥加入 JWKS 之前就用它签名(或者 CDN 提供的是陈旧的 JWKS 副本),验证方就找不到这个密钥。颁发者应当先发布新密钥,等缓存过期后再切换过去。
客户端指向了另一个颁发者的密钥集
常见各租户专属的 JWKS URL(Cognito 用户池、Auth0 租户、Keycloak 领域、Entra ID 租户)看起来都很相似。来自其他环境的 URL 返回的也是真实的密钥——只是不是这个令牌的密钥。
令牌头部中没有 kid
偶见`PyJWKClient.get_signing_key_from_jwt` 严格按 `kid` 匹配。如果颁发者不提供 kid,它的令牌就无法用这种方式找到密钥;这时应改为显式使用已发布的那个唯一密钥来验证。
密钥存在,但不是可用的签名密钥
偶见PyJWKClient 只考虑 `use` 不存在或为 `"sig"`、并且 PyJWT 能够加载的密钥;类型不受支持的密钥会被跳过。如果 `kid` 出现在原始 JSON 中,却不在 `get_signing_keys()` 的结果中,原因就在这里。
修复方法
- 1.
将令牌的 kid 与已加载的密钥对比
`get_signing_keys(refresh=True)` 会准确显示 PyJWKClient 能使用哪些密钥。如果其中没有令牌的 `kid`,问题就出在颁发者或 URL 上,而不在你的代码。
python print(jwt.get_unverified_header(token).get("kid")) for key in client.get_signing_keys(refresh=True): print(key.key_id, key.algorithm_name) - 2.
复用同一个 PyJWKClient
每次请求都新建客户端等于禁用了缓存,每次调用都会请求颁发者。带有密钥缓存的长期客户端在出现新的 `kid` 时仍会重新获取。
python from jwt import PyJWKClient # Module level: one client, cached keys, refetch on an unknown kid. JWKS = PyJWKClient("https://issuer.example.com/.well-known/jwks.json", cache_keys=True, lifespan=600) def verify(token: str) -> dict: signing_key = JWKS.get_signing_key_from_jwt(token) return jwt.decode(token, signing_key.key, algorithms=["ES256"], audience="https://api.example.com")