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

