Spring Session Redis 是不安全的
当在多服务之间使用 Spring Session Redis 进行 Session 共享要非常小心,因为它很不安全,很有可能导致整个服务实例不可用,无法处理任何请求。其中比较危险的地方就是在进行序列化,反序列化的时候(这种类型的错误尤其容易在没有开发规范的团队内发生,就是什么样的数据可以往共享存储里面存,什么样的不能存。存的时候要以什么样的格式去存,这些都要有规定才比较安全。因为共享存储是会影响到别人的不仅仅是为了自己的服务用起来方便)。RedisSerializer 接口的实现都是在序列化和反序列化出错的时候直接抛出异常从而导致整个请求错误。
public interface RedisSerializer {
/**
* Serialize the given object to binary data.
*
* @param t object to serialize
* @return the equivalent binary data
*/
byte[] serialize(T t) throws SerializationException;
/**
* Deserialize an object from the given binary data.
*
* @param bytes object binary representation
* @return the equivalent object instance
*/
T deserialize(byte[] bytes) throws SerializationException;
}
下面用一张图来说明我遇到的问题。Spring Session 的诞生老实说并不是为了分布式系统,而是为集群系统提供了一种 Session 解决方案。但是我们把 Spring Session 用在了分布式系统上用以解决 Session 共享的问题老实说本身就是有点难为人家 Spring Session 了 。
Spring Session 实现 Session 共享的大致原理
Spring Session 实现 Session 共享的大致原理如下图所示 , 使用一个 Filter 来拦截所有请求,在拦截到请求之后对 HttpServletRequest 和 HttpServletResponse 进行包装 (HttpServletRequestWrapper , HttpServletResponseWrapper)。在包装中对 session 进行控制 ,将 session 数据都存储在第三方存储当中。
Spring Session Redis 在不同服务间共享 Session 时的类共享方案
在了解了 Spring Session 的工作原理后再去考虑这个问题就有头绪了 。还是通过图形的方式来做大概的说明。
学习 Spring Session 的 SessionRepositoryFilter 的实现方式 , 添加一个 Filter 顺序在 SessionRepositoryFilter 之后 , 在拦截过程中包装 HttpServletRequest , 重写 getSession(boolean create) 和 getSession() 方法, 自定义一个 SafetyHttpSessionWrapper 包装 Session ,重写 setAttribute(String name , Object value) 函数 , 在保存属性成功后利用 redis 的发布订阅机制发送消息到 redis , 消息的内容为所保存对象的 .class 文件数据。在消息订阅端 , 接收到消息后利用 javassist 和 net.bytebuddy.dynamic.loading.ByteArrayClassLoader 将 .class 文件数据加载转换成 Class 的实例对象,但是这个 Class 实例的范围被限定在 ByteArrayClassLoader 中 , 而这个 ByteArrayClassLoader 是提供给 RedisSerializer 内部使用的 , 比如 JdkSerializationRedisSerializer , GenericJackson2JsonRedisSerializer 都需要使用到 ClassLoader 。这样当 服务A 在存储任何自定义的对象在 Session 中时, 访问服务 B 也不会出现读取 Session 反序列化 ClassNotFoundException 的错误了。
初版代码实现
以上思路的代码实现在 WORKX 项目中,感兴趣的同学可以参考 org.hepeng.workx.spring.session.redis.serializer 这个包下的代码。从 org.hepeng.workx.spring.session.redis.serializer.SafetyRedisSerializerConfiguration 这个类入手看源码。目前 RedisSerializer 组件只支持了 JdkSerializationRedisSerializer , GenericJackson2JsonRedisSerializer 以及它们的子类。