错误信息
Jwks doesn't have key to match kid or alg from Jwt
也可能输出为
- Jwt verification fails
- Jwks remote fetch is failed
这是 401 的响应体;`WWW-Authenticate` 响应头携带 `error="invalid_token"`。如果某个提供方的 `remote_jwks` 无法获取,响应则是“Jwks remote fetch is failed”。
Envoy / Istio — envoy.filters.http.jwt_authn (401 response body; Istio RequestAuthentication)
含义
Envoy 的 `jwt_authn` 过滤器(Istio 的 `RequestAuthentication` 也使用它)在提供方的 JWKS 中查找 `kid` 与令牌相同、类型与其 `alg` 相符的密钥,但没有找到,于是返回 401,并以这段文本作为响应体。签名根本没有被检查。如果存在匹配的密钥但签名验证失败,响应体则是“Jwt verification fails”。
原因
颁发者已轮换密钥,Envoy 的缓存还是旧的
常见`remote_jwks` 会被缓存 `cache_duration` 时长(默认 5 分钟)。用上次获取之后才发布的密钥签名的令牌,在缓存刷新之前都会失败。
内联或本地的 JWKS 副本
常见`local_jwks`(或 Istio 中内联的 `jwks:`)只是一份快照,永远不会得知新密钥;每次轮换都会让身份验证失败,直到更新配置为止。
具有此 kid 的密钥类型不同
偶见JWKS 中有这个 `kid`,但对应的是 RSA 密钥,而令牌使用的是 ES256(或者 JWK 的 `alg` 与之不符)。Envoy 要求两者都匹配。
令牌属于另一个颁发者
偶见如果提供方没有严格限定 `issuer` 匹配,或者请求携带的是来自其他身份提供商的令牌,就会用错误的密钥集来检查。
修复方法
- 1.
从颁发者获取 JWKS
使用 `remote_jwks`,配合合理的 `cache_duration` 和异步获取,使密钥在令牌用到之前就已就绪。
yaml providers: issuer_example: issuer: https://issuer.example.com audiences: ["api://orders"] remote_jwks: http_uri: uri: https://issuer.example.com/.well-known/jwks.json cluster: issuer_jwks # a cluster with TLS to the issuer timeout: 5s cache_duration: 600s async_fetch: { fast_listener: false } # prefetch, and refetch in the background - 2.
在 Istio 中指向 jwksUri
Istiod 会获取并推送密钥;内联的 `jwks` 值则必须在每次轮换时手动更新。
yaml apiVersion: security.istio.io/v1 kind: RequestAuthentication metadata: name: orders-jwt spec: selector: matchLabels: { app: orders } jwtRules: - issuer: https://issuer.example.com jwksUri: https://issuer.example.com/.well-known/jwks.json # not an inline "jwks:" copy