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

Spring Boot Security Redis共享会话序列化异常求助

嘿,我之前刚好踩过这个坑!你遇到的问题是因为Spring Security默认的安全上下文存储逻辑,会把那个没实现Serializable的SessionRepositoryFilter$SessionRepositoryRequestWrapper$HttpSessionWrapper内部类间接关联到Session中,而JDK序列化器无法处理未实现Serializable接口的对象,所以抛出了异常。下面是几个亲测有效的解决办法:

方案一:自定义SecurityContextRepository,切断不可序列化对象的关联

这个方案直接重写安全上下文的存储逻辑,确保只存入可序列化的SecurityContext对象,避免带上请求相关的不可序列化实例。

首先创建自定义的SecurityContextRepository实现类:

import org.springframework.security.core.context.SecurityContext;
import org.springframework.security.core.context.SecurityContextHolder;
import org.springframework.security.web.context.HttpSessionSecurityContextRepository;
import org.springframework.security.web.context.SecurityContextRepository;
import org.springframework.security.web.context.HttpRequestResponseHolder;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;

public class CustomSecurityContextRepository implements SecurityContextRepository {

    private final HttpSessionSecurityContextRepository delegate = new HttpSessionSecurityContextRepository();

    @Override
    public SecurityContext loadContext(HttpRequestResponseHolder requestResponseHolder) {
        return delegate.loadContext(requestResponseHolder);
    }

    @Override
    public void saveContext(SecurityContext context, HttpServletRequest request, HttpServletResponse response) {
        // 先清空当前线程的安全上下文,避免残留的请求关联对象被存入Session
        SecurityContextHolder.clearContext();
        // 仅存储纯SecurityContext对象,不携带请求相关的包装类
        delegate.saveContext(context, request, response);
    }

    @Override
    public boolean containsContext(HttpServletRequest request) {
        return delegate.containsContext(request);
    }
}

然后在你的Spring Security配置类中,替换默认的SecurityContextRepository:

@Configuration
@EnableWebSecurity
public class SecurityConfig extends WebSecurityConfigurerAdapter {

    @Override
    protected void configure(HttpSecurity http) throws Exception {
        http
            .authorizeRequests()
                .anyRequest().authenticated()
                .and()
            .formLogin()
                .and()
            // 配置自定义的安全上下文存储类
            .securityContext()
                .securityContextRepository(new CustomSecurityContextRepository());
    }
}

方案二:改用Jackson序列化替代JDK默认序列化

Spring Session默认使用JDK序列化,换成Jackson序列化器后,它不需要对象实现Serializable接口,只要能通过反射访问属性就能完成序列化/反序列化,完美解决这类内部类不可序列化的问题。

修改你的RedisSessionConfig配置类,添加Jackson序列化的配置:

import com.fasterxml.jackson.annotation.JsonAutoDetect;
import com.fasterxml.jackson.annotation.PropertyAccessor;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.fasterxml.jackson.databind.jsontype.impl.LaissezFaireSubTypeValidator;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.data.redis.connection.RedisConnectionFactory;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.data.redis.serializer.Jackson2JsonRedisSerializer;
import org.springframework.data.redis.serializer.RedisSerializer;
import org.springframework.data.redis.serializer.StringRedisSerializer;
import org.springframework.session.data.redis.config.annotation.web.http.EnableRedisHttpSession;

@Configuration
@EnableRedisHttpSession
public class RedisSessionConfig {

    @Bean
    public RedisTemplate<String, Object> redisTemplate(RedisConnectionFactory connectionFactory) {
        RedisTemplate<String, Object> template = new RedisTemplate<>();
        template.setConnectionFactory(connectionFactory);

        // 配置Jackson序列化器
        Jackson2JsonRedisSerializer<Object> jacksonSerializer = new Jackson2JsonRedisSerializer<>(Object.class);
        ObjectMapper objectMapper = new ObjectMapper();
        // 允许访问所有属性
        objectMapper.setVisibility(PropertyAccessor.ALL, JsonAutoDetect.Visibility.ANY);
        // 启用类型信息,确保反序列化时能识别正确的对象类型
        objectMapper.activateDefaultTyping(LaissezFaireSubTypeValidator.instance, ObjectMapper.DefaultTyping.NON_FINAL);
        jacksonSerializer.setObjectMapper(objectMapper);

        // 设置key和hashKey为String序列化
        template.setKeySerializer(new StringRedisSerializer());
        template.setHashKeySerializer(new StringRedisSerializer());
        // 设置value和hashValue为Jackson序列化
        template.setValueSerializer(jacksonSerializer);
        template.setHashValueSerializer(jacksonSerializer);

        template.afterPropertiesSet();
        return template;
    }

    // 配置Spring Session默认使用的序列化器
    @Bean
    public RedisSerializer<Object> springSessionDefaultRedisSerializer() {
        Jackson2JsonRedisSerializer<Object> jacksonSerializer = new Jackson2JsonRedisSerializer<>(Object.class);
        ObjectMapper objectMapper = new ObjectMapper();
        objectMapper.setVisibility(PropertyAccessor.ALL, JsonAutoDetect.Visibility.ANY);
        objectMapper.activateDefaultTyping(LaissezFaireSubTypeValidator.instance, ObjectMapper.DefaultTyping.NON_FINAL);
        jacksonSerializer.setObjectMapper(objectMapper);
        return jacksonSerializer;
    }
}

方案三:排查自定义Authentication/UserDetails是否未实现Serializable

虽然你的报错指向HttpSessionWrapper,但也顺便检查一下自己自定义的UserDetails或Authentication实现类是否忘记实现Serializable接口——这也是会话共享时常见的序列化异常诱因。比如:

// 确保自定义User类实现Serializable
public class CustomUser implements UserDetails, Serializable {
    // 你的属性和方法实现
}

补充说明

方案一的核心是切断安全上下文与请求包装类的关联,从根源上避免不可序列化对象进入Session;方案二则是换用更灵活的序列化方式,兼容未实现Serializable的对象。两种方案都能解决你的问题,你可以根据自己的项目偏好选择。

内容的提问来源于stack exchange,提问作者Zhen

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.05.27 07:13:33