集群环境下Spring Authorization Server令牌续期:Spring Session能否替代粘性会话?
问题背景与现象
单实例Spring Authorization Server搭配独立客户端、资源服务器时,访问令牌自动续期功能正常;但部署为集群(Docker Compose + Nginx代理 + 2个授权服务器实例)后,续期功能失效:
- 客户端调试发现:
DefaultRefreshTokenTokenResponseClient.getResponse()调用http://localhost:9000/oauth2/token时,授权服务器重定向至http://localhost:9000/login,最终导致getTokenResponse()抛出NullPointerException:Cannot invoke "org.springframework.security.oauth2.core.endpoint.OAuth2AccessTokenResponse.getAccessToken()" because "tokenResponse" is null
- 授权服务器已集成Spring Session(会话存储至数据库),并采用Rob Winch实现的密钥轮换策略(密钥存储至数据库)
- 客户端已配置
HttpSessionOAuth2AuthorizedClientRepository:
且能在@Bean public OAuth2AuthorizedClientRepository authorizedClientRepository() { return new HttpSessionOAuth2AuthorizedClientRepository(); }SPRING_SESSION_ATTRIBUTES中看到org.springframework.security.oauth2.client.web.HttpSessionOAuth2AuthorizedClientRepository.AUTHORIZED_CLIENTS,但客户端仍运行失败 - 配置Nginx
ip_hash粘性会话后问题解决,但期望通过Spring Session实现无粘性会话的集群部署
问题根源分析
Spring Session本身完全适配Spring Authorization Server,出现该问题并非框架不适配,而是配置遗漏或错误:
/oauth2/token端点未允许匿名访问:刷新令牌流程是无状态的,基于客户端凭证和刷新令牌完成,不需要用户会话。若集群中某个实例未配置该端点匿名访问,会触发登录重定向。- 刷新令牌未配置分布式存储:默认情况下,刷新令牌存储在内存中,集群实例间不共享。当请求落到没有该刷新令牌的实例时,验证失败会触发认证拦截。
- Nginx代理丢失关键请求头:转发时未保留
Authorization、Cookie等头信息,导致授权服务器无法识别客户端凭证或会话信息,进而触发重定向。
解决方案
1. 确保/oauth2/token端点允许匿名访问
在授权服务器的SecurityFilterChain中明确配置:
@Bean public SecurityFilterChain authorizationServerSecurityFilterChain(HttpSecurity http) throws Exception { OAuth2AuthorizationServerConfiguration.applyDefaultSecurity(http); http .exceptionHandling(exceptions -> exceptions .authenticationEntryPoint(new LoginUrlAuthenticationEntryPoint("/login")) ) .authorizeHttpRequests(auth -> auth .requestMatchers("/oauth2/token").permitAll() .anyRequest().authenticated() ); return http.build(); }
2. 配置刷新令牌的分布式存储
将刷新令牌存储到共享数据库或Redis,以实现集群实例间共享:
JDBC存储示例
引入依赖:
<dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-jdbc</artifactId> </dependency> <dependency> <groupId>org.springframework.security</groupId> <artifactId>spring-security-oauth2-authorization-server-jdbc</artifactId> </dependency>
配置授权服务使用JDBC存储:
@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); }
3. 修正Nginx代理配置
确保转发时保留关键请求头:
http { upstream loadbalancer { server authz1:9000; server authz2:9000; } server { listen 9000; location / { proxy_pass http://loadbalancer; proxy_set_header Host $host; proxy_set_header X-Real-IP $remote_addr; proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; proxy_set_header X-Forwarded-Proto $scheme; proxy_set_header Cookie $http_cookie; } } }
4. 验证Spring Session配置一致性
确保所有授权服务器实例的Spring Session配置完全一致:
- 依赖引入正确(如
spring-boot-starter-data-redis或spring-boot-starter-jdbc) - 数据库/Redis连接配置统一
- 会话序列化配置正确(避免跨实例会话无法反序列化)
是否需要放弃Spring Session改用粘性会话?
不需要。粘性会话只是规避问题的临时方案,会导致负载不均、单点故障等隐患。Spring Session完全支持Spring Authorization Server的集群部署,只要完成上述配置修正,就能实现无粘性会话的高可用集群。
内容的提问来源于stack exchange,提问作者Eduardo Guimaraes
相关产品推荐
相关产品推荐

