K8s扩容Authorization Server后令牌校验异常的解决方法及指南
问题原因与解决方案
核心问题分析
你的问题出在Authorization Server扩容后实例间状态与密钥不共享,具体两点:
- 每个实例启动时自动生成独立的RSA密钥对,导致跨实例JWT签名验证不兼容;
- 默认使用内存存储令牌授权信息(
InMemoryOAuth2AuthorizationService),令牌状态仅存在于生成它的实例本地,其他实例无法查询到。
另外需确认issuer配置是否为统一的负载均衡地址,若每个实例使用自身节点地址,也会导致JWT的iss声明校验失败。
正确扩容方案
1. 统一共享RSA密钥对
停止每个实例自行生成密钥,改用外部存储的共享密钥对,所有实例使用同一套密钥签名和验证JWT。
修改代码加载外部密钥
@Autowired private OAuthConfig authConfig; @Autowired PasswordEncoder passwordEncoder; @Value("${auth.server.issuer}") private String issuerAuth; // 新增密钥路径配置 @Value("${auth.jwt.private-key-path}") private String privateKeyPath; @Value("${auth.jwt.public-key-path}") private String publicKeyPath; // ... 其他原有Bean保持不变 ... @Bean public JWKSource<SecurityContext> jwkSource() throws Exception { // 从挂载的密钥文件加载私钥 RSAPrivateKey privateKey = (RSAPrivateKey) KeyFactory.getInstance("RSA") .generatePrivate(new PKCS8EncodedKeySpec(Files.readAllBytes(Paths.get(privateKeyPath)))); // 加载公钥 RSAPublicKey publicKey = (RSAPublicKey) KeyFactory.getInstance("RSA") .generatePublic(new X509EncodedKeySpec(Files.readAllBytes(Paths.get(publicKeyPath)))); RSAKey rsaKey = new RSAKey.Builder(publicKey) .privateKey(privateKey) .keyID("shared-auth-key") .build(); JWKSet jwkSet = new JWKSet(rsaKey); return new ImmutableJWKSet<>(jwkSet); } // ... 其他原有方法保持不变 ...
K8s侧配置密钥存储
生成密钥对并存储为K8s Secret:
# 生成3072位RSA密钥对 openssl genrsa -out private.key 3072 openssl rsa -in private.key -pubout -out public.key # 创建Secret存储密钥 kubectl create secret generic auth-server-jwt-keys \ --from-file=private.key=./private.key \ --from-file=public.key=./public.key
在Deployment中挂载Secret到容器路径:
spec: containers: - name: auth-server image: your-auth-server-image volumeMounts: - name: jwt-keys mountPath: /opt/auth/keys readOnly: true volumes: - name: jwt-keys secret: secretName: auth-server-jwt-keys
在配置文件中指定密钥路径:
auth.jwt.private-key-path=/opt/auth/keys/private.key auth.jwt.public-key-path=/opt/auth/keys/public.key
2. 共享令牌授权存储
将默认的内存存储替换为JDBC存储,所有实例共享同一个数据库,确保令牌状态跨实例可访问。
添加JDBC版授权服务Bean:
// 新增以下两个Bean @Bean public OAuth2AuthorizationService authorizationService(JdbcTemplate jdbcTemplate, RegisteredClientRepository registeredClientRepository) { return new JdbcOAuth2AuthorizationService(jdbcTemplate, registeredClientRepository); } @Bean public OAuth2AuthorizationConsentService authorizationConsentService(JdbcTemplate jdbcTemplate, RegisteredClientRepository registeredClientRepository) { return new JdbcOAuth2AuthorizationConsentService(jdbcTemplate, registeredClientRepository); }
确保所有Auth Server实例连接同一个数据库(如K8s内部部署的MySQL/PostgreSQL),数据库连接配置统一。
3. 统一Issuer地址
将auth.server.issuer配置为Auth Server的负载均衡地址(而非单个实例地址),确保JWT的iss声明在所有实例中一致。
例如,使用K8s Service的内部域名:
auth.server.issuer=https://auth-service.default.svc.cluster.local
若对外提供服务,使用外部统一域名:
auth.server.issuer=https://auth.yourdomain.com
4. 验证配置有效性
- 启动两个Auth Server实例,检查所有实例加载的是同一套密钥对;
- 从任意实例获取令牌,调用任意实例的introspect端点,验证返回
active: true; - 确认资源服务器通过负载均衡地址调用introspect,而非直接访问单个实例。
内容的提问来源于stack exchange,提问作者kazuya komatsu
相关产品推荐
相关产品推荐

