将Spring Authorization Server作为Keycloak IDP时JWK验证失败问题
问题描述
基于Spring Authorization Server官方示例搭建OAuth2授权服务器,将其配置为Keycloak的身份提供商(IDP)后,Keycloak可正常重定向到Spring授权服务器的登录页面,但登录完成后抛出签名验证失败异常:
2024-08-07 17:57:36,179 WARN [org.keycloak.keys.infinispan.InfinispanPublicKeyStorageProvider] (executor-thread-11) PublicKey wasn't found in the storage. Requested kid: 'd2ff780e-5b42-4f7a-83dc-e7bd23d838de' . Available kids: '[]' 2024-08-07 17:57:36,180 ERROR [org.keycloak.broker.oidc.AbstractOAuth2IdentityProvider] (executor-thread-11) Failed to make identity provider oauth callback: org.keycloak.broker.provider.IdentityBrokerException: token signature validation failed
直接访问Spring授权服务器的JWK URI,能看到Keycloak请求的kid对应的密钥,且通过授权码模式直接对接Spring授权服务器生成的JWT,签名和头在本地验证完全正常。
解决方向
1. 确认Keycloak对JWK端点的访问有效性
- 检查Keycloak IDP的OpenID Connect配置中,
JWKS URL是否准确指向Spring Authorization Server的/oauth2/jwks端点 - 在Keycloak服务器上通过
curl命令测试JWK端点的连通性,排查网络隔离、防火墙或反向代理导致的访问失败问题 - 若Spring Authorization Server启用HTTPS,需确保Keycloak信任其SSL证书,否则会因证书验证失败无法拉取密钥
2. 规范Spring Authorization Server的密钥配置
- 避免动态生成的密钥随服务重启变更:配置持久化密钥存储(如
JdbcKeyStore),或固定kid值,示例配置如下:@Bean public JWKSource<SecurityContext> jwkSource() { RSAKey rsaKey = generateRsaKey(); JWKSet jwkSet = new JWKSet(rsaKey); return (jwkSelector, securityContext) -> jwkSelector.select(jwkSet); } private static RSAKey generateRsaKey() { KeyPair keyPair = generateRsaKeyPair(); RSAPublicKey publicKey = (RSAPublicKey) keyPair.getPublic(); RSAPrivateKey privateKey = (RSAPrivateKey) keyPair.getPrivate(); // 使用固定字符串作为kid,避免重启后变更 return new RSAKey.Builder(publicKey) .privateKey(privateKey) .keyID("fixed-key-id-123") .build(); } - 确认
JWKSource配置中包含对应签名算法的密钥,且alg字段与JWT使用的签名算法(如RS256)一致
3. 重置Keycloak的密钥缓存
- 进入Keycloak控制台,找到对应IDP,点击Save重新保存配置,触发JWK的重新拉取
- 调整Keycloak的JWK缓存过期时间:在配置文件中修改
keycloak.keys.infinispan.cache-ttl参数,缩短缓存周期 - 手动清除缓存:重启Keycloak服务,或通过管理API清空Infinispan中的旧密钥存储
4. 验证签名算法匹配性
- 确认Spring Authorization Server生成的JWT签名算法,与JWK中声明的
alg字段、Keycloak IDP配置的签名算法完全一致
内容的提问来源于stack exchange,提问作者Keith
相关产品推荐
相关产品推荐

