GCP Cloud Run扩容Spring Authorization Server报Wrong nonce问题咨询
根因说明
你已经完成OAuth2AuthorizationService、OAuth2AuthorizationConsentService、RegisteredClientRepository的MongoDB持久化,但Wrong nonce报错在扩容、重启场景稳定复现,问题出在两个被忽略的默认内存级配置,和你已经持久化的三个核心接口无关:
- Spring Authorization Server(SAS)默认在服务启动时自动生成OIDC Token签名用的密钥对,nonce校验依赖签名密钥的一致性。单实例重启会生成新密钥、多实例各自生成独立密钥,都会导致其他实例/重启后的实例无法校验旧nonce的签名,直接抛错。
- OIDC授权码流程中,nonce的临时比对值默认存在单实例内存级的SecurityContext中,跨实例请求时新实例拿不到之前存储的nonce明文,直接判定校验不通过。
落地修复方案
按优先级依次配置,改完即可彻底解决扩容、重启导致的活跃用户强制登出问题:
1. 配置全局统一的固定签名密钥
禁止使用框架启动时自动生成的临时密钥,所有实例加载同一份提前生成的签名密钥对,密钥统一存在配置中心或密钥管理服务,不要硬编码在代码中:
@Bean public JWKSource<SecurityContext> sharedJwkSource() { RSAKey sharedRsaKey = new RSAKey.Builder(globalPublicKey) .privateKey(globalPrivateKey) .keyID("固定全局统一的KeyID") .build(); return new ImmutableJWKSet<>(new JWKSet(sharedRsaKey)); }
注意:后续密钥走标准灰度轮换流程即可,严禁每次服务启动重新生成密钥,这是该场景90%以上用户踩的核心坑
2. 替换所有内存级状态存储为共享持久层
除了你已经实现的三个OAuth2核心服务,还要把Spring Security默认的内存级存储全部替换为MongoDB/Redis等共享存储实现:
- 替换
SecurityContextRepository为持久化实现,不要使用默认搭配内存Session的HttpSessionSecurityContextRepository - 授权码流程关联的nonce临时存储、授权请求缓存,全部从单实例内存迁移到共享存储,确保任意实例都能读取到授权阶段生成的nonce值
- 确认
NimbusJwtDecoder从统一共享JWK源加载密钥,不要读取单实例本地生成的密钥做校验
3. 放弃Session粘滞的临时方案
不要尝试用网关层Session粘滞绕过问题,该方案无法覆盖实例宕机切流、滚动重启、流量突发时负载均衡动态调度的场景,本质问题还是状态存储和实例绑定,没有彻底解耦。
验证步骤
配置完成后依次做两个场景验证,全部通过即代表问题修复:
- 启动单实例生成一批有效认证令牌,直接重启该实例,用重启前生成的令牌发起认证,确认不再抛出
Wrong nonce错误 - 启动两个配置完全一致的SAS实例,在第一个实例完成授权流程拿到授权码,将携带code、nonce的换票请求直接打到第二个实例,确认能正常换发令牌、完成认证
内容的提问来源于stack exchange,提问作者Bjorn Harvold
相关产品推荐
相关产品推荐

