SpringBoot中如何在Redis Session中持久化OAuth2AuthorizedClient?
问题根源
默认的InMemoryOAuth2AuthorizedClientService是内存级实现,重启应用或多容器部署时会直接丢失授权信息;同时,默认的Session序列化逻辑不支持Spring Security OAuth2的特殊对象(比如OAuth2AuthorizedClient、OAuth2AccessToken),导致即便用了Redis Session,这些授权数据也无法正确持久化和恢复。
方案1:基于Spring Session Redis + 正确序列化OAuth2对象
这是最贴合Spring Boot标准的实现方式,核心是让Session中的OAuth2对象能被Redis正确序列化/反序列化,从而跨实例、重启后保留授权信息。
步骤1:添加必要依赖
确保你的pom.xml(或build.gradle)包含Spring Security OAuth2的Jackson序列化模块,版本要和你的Spring Security 5.1.10.RELEASE一致:
<!-- Maven --> <dependency> <groupId>org.springframework.security</groupId> <artifactId>spring-security-oauth2-jackson2</artifactId> <version>5.1.10.RELEASE</version> </dependency>
步骤2:配置Redis Session的序列化器
自定义Redis序列化器,让它支持Spring Security OAuth2的对象:
@Configuration public class RedisSessionConfig { @Bean public RedisSerializer<Object> springSessionDefaultRedisSerializer() { Jackson2JsonRedisSerializer<Object> serializer = new Jackson2JsonRedisSerializer<>(Object.class); ObjectMapper objectMapper = new ObjectMapper(); // 注册Spring Security和OAuth2的Jackson序列化模块 objectMapper.registerModule(new SpringSecurityJackson2Module()); objectMapper.registerModule(new OAuth2Jackson2Module()); // 启用类型信息,避免反序列化时多态对象类型丢失 objectMapper.enableDefaultTyping(ObjectMapper.DefaultTyping.NON_FINAL, JsonTypeInfo.As.PROPERTY); serializer.setObjectMapper(objectMapper); return serializer; } }
步骤3:配置Security使用Session存储授权信息
Spring Security默认会用HttpSessionOAuth2AuthorizedClientRepository将授权信息存在Session中,只要Session能正确同步到Redis,重启或多容器环境下就能自动恢复AuthorizedClients。在Security配置中显式指定(可选,但逻辑更清晰):
@Configuration @EnableWebSecurity public class SecurityConfig extends WebSecurityConfigurerAdapter { @Autowired private OAuth2AuthorizedClientRepository authorizedClientRepository; @Override protected void configure(HttpSecurity http) throws Exception { http .authorizeRequests() .anyRequest().authenticated() .and() .oauth2Login() .authorizedClientRepository(authorizedClientRepository) .and() .oauth2Client(); } }
方案2:自定义Redis-backed OAuth2AuthorizedClientService(不依赖Session)
如果你不想依赖HttpSession,也可以直接实现一个基于Redis的OAuth2AuthorizedClientService,绕过默认的内存实现。
步骤1:实现Redis版OAuth2AuthorizedClientService
@Service public class RedisOAuth2AuthorizedClientService implements OAuth2AuthorizedClientService { private static final String KEY_PREFIX = "oauth2:authorized-client:"; private final RedisTemplate<String, OAuth2AuthorizedClient> redisTemplate; public RedisOAuth2AuthorizedClientService(RedisConnectionFactory connectionFactory) { this.redisTemplate = new RedisTemplate<>(); this.redisTemplate.setConnectionFactory(connectionFactory); // 配置序列化器,同方案1 Jackson2JsonRedisSerializer<OAuth2AuthorizedClient> serializer = new Jackson2JsonRedisSerializer<>(OAuth2AuthorizedClient.class); ObjectMapper objectMapper = new ObjectMapper(); objectMapper.registerModule(new SpringSecurityJackson2Module()); objectMapper.registerModule(new OAuth2Jackson2Module()); objectMapper.enableDefaultTyping(ObjectMapper.DefaultTyping.NON_FINAL, JsonTypeInfo.As.PROPERTY); serializer.setObjectMapper(objectMapper); this.redisTemplate.setValueSerializer(serializer); this.redisTemplate.setKeySerializer(new StringRedisSerializer()); this.redisTemplate.afterPropertiesSet(); } @Override public <T extends OAuth2AuthorizedClient> T loadAuthorizedClient(String clientRegistrationId, String principalName) { String key = generateKey(clientRegistrationId, principalName); return (T) redisTemplate.opsForValue().get(key); } @Override public void saveAuthorizedClient(OAuth2AuthorizedClient authorizedClient, Authentication principal) { String key = generateKey( authorizedClient.getClientRegistration().getRegistrationId(), principal.getName() ); redisTemplate.opsForValue().set(key, authorizedClient); } @Override public void removeAuthorizedClient(String clientRegistrationId, String principalName) { String key = generateKey(clientRegistrationId, principalName); redisTemplate.delete(key); } private String generateKey(String clientRegistrationId, String principalName) { return KEY_PREFIX + clientRegistrationId + ":" + principalName; } }
步骤2:在Security配置中使用自定义Service
@Configuration @EnableWebSecurity public class SecurityConfig extends WebSecurityConfigurerAdapter { @Autowired private OAuth2AuthorizedClientService authorizedClientService; @Override protected void configure(HttpSecurity http) throws Exception { http .authorizeRequests() .anyRequest().authenticated() .and() .oauth2Login() .authorizedClientService(authorizedClientService) .and() .oauth2Client(); } }
关键注意事项
- 依赖版本一致性:务必保证
spring-security-oauth2-jackson2的版本与Spring Security 5.1.10.RELEASE、Spring Cloud Greenwich.SR5完全匹配,避免依赖冲突。 - Redis集群一致性:多容器部署时,所有容器必须连接同一个Redis实例/集群,Spring Session才能正确同步数据。
- 序列化类型信息:启用Jackson的默认类型信息是必要的,否则反序列化
OAuth2AuthorizedClient这类多态对象时会失败。
内容的提问来源于stack exchange,提问作者Danidhsm

