Spring Boot OAuth 2授权服务器多实例下JWK值不一致导致资源服务器JWT验证失败
你遇到的跨实例JWT验证失败问题,核心原因非常清晰:每个授权服务器实例都在维护独立的JWK(JSON Web Key)集合,而且密钥ID(kid)是随机生成的。当资源服务器的请求路由到非签发该JWT的实例时,该实例的JWK集合里找不到对应令牌的kid,自然就会抛出Failed to authenticate since the JWT was invalid错误。
具体代码问题点
看你的generateRsa()方法,每次实例启动都会生成一个全新的随机keyID(UUID.randomUUID().toString()),而且每个实例的JWKSet只包含自身加载的RSA密钥(虽然公私钥来自配置文件,但kid不统一)。资源服务器会从授权服务器的/.well-known/jwks.json端点拉取JWK来验证JWT签名,不同实例返回的JWK集kid不一致,就导致了跨实例验证失败。
修复步骤
1. 固定密钥ID(kid)
不要使用随机UUID生成kid,改为从配置文件读取固定值,或者硬编码一个统一的kid(配置文件方式更灵活)。这样所有实例的JWK中的kid完全一致,资源服务器拿到的JWK集就能匹配令牌中的kid。
修改后的代码示例:
// 从配置文件注入固定的key-id,比如在application.yml中配置 jwt.key-id=my-oauth2-server-kid @Value("${jwt.key-id}") private String fixedKeyId; @Bean public JWKSource<SecurityContext> jwkSource() { RSAKey rsaKey = generateRsa(); JWKSet jwkSet = new JWKSet(rsaKey); return (jwkSelector, securityContext) -> jwkSelector.select(jwkSet); } private RSAKey generateRsa() { RSAPublicKey publicKey = RsaKeyProperties.getPublicKey(); RSAPrivateKey privateKey = RsaKeyProperties.getPrivateKey(); return new RSAKey.Builder(publicKey) .privateKey(privateKey) .keyID(fixedKeyId) // 使用固定keyId,替代随机UUID .build(); }
2. 确保所有实例使用相同的公私钥对
既然你已经自定义了RSA密钥,必须保证所有授权服务器实例加载的是完全相同的公私钥文件/配置。如果公私钥本身不一致,即使kid统一,签名验证也会失败。
3. 验证JWK端点一致性
部署完成后,分别访问每个授权服务器实例的/.well-known/jwks.json端点,检查返回的JWK集合内容是否完全一致(包括kid、公钥参数等)。如果所有实例返回的JWK集相同,说明配置生效了。
额外建议
你当前使用的spring-security-oauth2-authorization-server版本是0.3.1,这是一个较早的预览版本。建议升级到最新的稳定版(比如1.x系列),新版本不仅修复了大量bug,还提供了更完善的集群部署支持,能避免一些潜在的兼容性问题。
另外,你提到会话存储使用Redis,这部分配置是没问题的,它和JWK的跨实例一致性问题无关,不用调整。
内容的提问来源于stack exchange,提问作者Pradeep N S

