You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

SpringBoot中如何在Redis Session中持久化OAuth2AuthorizedClient?

解决方案:Spring Boot Session + Redis 保留 OAuth2 AuthorizedClients

问题根源

默认的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

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.05.07 17:32:33