本文目录导读:

在Java分布式系统中,反序列化(Deserialization)是一个关键但潜在危险的操作,根据你的问题,我推测你可能在讨论以下几种情况之一:
- 分布式缓存/消息队列中的对象反序列化(如 Redis、Kafka、RabbitMQ)
- RPC 框架的数据反序列化(如 Dubbo、gRPC、Thrift)
- JSON/Protobuf 等跨语言数据格式的解析
我会覆盖常见场景并提供安全实践。
核心问题:为什么反序列化在分布式系统中危险?
Java 原生反序列化(ObjectInputStream.readObject())存在 远程代码执行(RCE)漏洞,攻击者可以构造恶意二进制流,触发 readObject() 执行任意代码。
经典攻击链:
Java反序列化 → CommonsCollections → Runtime.exec()
安全反序列化方案
方案 A:使用安全的序列化格式(推荐)
避免使用 Java 原生序列化,改用跨语言、安全的格式:
| 格式 | 库 | 特点 |
|---|---|---|
| JSON | Jackson / Gson | 文本格式,安全,但性能一般 |
| Protobuf | protobuf-java | 二进制,高效,需定义 schema |
| Avro | avro | 二进制,支持 schema 演化 |
| Kryo | kryo | 高性能 Java 专用,需配置白名单 |
| FST | fst | 类似 Kryo,速度极快 |
示例:使用 Jackson 解析 JSON(安全)
// 可靠的反序列化,仅解析数据字段,不执行魔法方法 ObjectMapper mapper = new ObjectMapper(); MyData data = mapper.readValue(jsonString, MyData.class);
方案 B:强制白名单(若必须用 Java 原生序列化)
如果团队坚持用 Java 原生序列化(如某些遗留 RPC 框架),必须配置白名单:
// 使用 ValidatingObjectInputStream(Apache Commons IO)
import org.apache.commons.io.serialization.ValidatingObjectInputStream;
try (ValidatingObjectInputStream vois = new ValidatingObjectInputStream(inputStream)) {
vois.accept(MySafeClass.class, AnotherSafeDTO.class); // 仅允许这些类
// 或使用 accept(Pattern.compile("com.myapp.dto.*"))
Object obj = vois.readObject();
}
方案 C:使用非 Java 原生 RPC 框架
现代分布式框架已内置安全机制:
| 框架 | 默认序列化 | 安全建议 |
|---|---|---|
| gRPC | Protobuf | 天然安全 |
| Dubbo | Hessian2 / JSON | 建议使用 JSON 或配置安全过滤器 |
| Spring Cloud | JSON | 使用 Jackson,安全 |
| Thrift | Thrift 二进制 | 本身安全(不执行代码) |
实际场景处理
场景 1:Redis 中存储 Java 对象
错误做法:直接使用 RedisTemplate 默认的 JdkSerializationRedisSerializer。
正确做法:
// 使用 Jackson2JsonRedisSerializer
@Bean
public RedisTemplate<String, Object> redisTemplate(RedisConnectionFactory factory) {
RedisTemplate<String, Object> template = new RedisTemplate<>();
template.setConnectionFactory(factory);
template.setDefaultSerializer(new Jackson2JsonRedisSerializer<>(Object.class));
// 或指定具体类型:new Jackson2JsonRedisSerializer<>(MyData.class)
return template;
}
场景 2:Kafka 消息消费
错误做法:使用 Kafka 自带的 KafkaAvroDeserializer 不配置 Schema Registry。
安全实践:
// 使用 JSON Deserializer props.put(ConsumerConfig.VALUE_DESERIALIZER_CLASS_CONFIG, JsonDeserializer.class); props.put(JsonDeserializer.TRUSTED_PACKAGES, "com.myapp.dto"); // 白名单
场景 3:Dubbo 反序列化
# dubbo 配置安全反序列化
dubbo:
provider:
filter: -exception # 可选
consumer:
filter: -generic
serialization: json # 更安全,不使用 hessian2
如果必须解析二进制数据(如 RPC 框架)
对于必须处理二进制反序列化的场景(如自定义协议解析器),采用 手动解析 + 类型校验:
// 自己实现解析器,避免 readObject()
public class SafeBinaryParser {
public MyDTO parse(byte[] data) {
try (DataInputStream dis = new DataInputStream(new ByteArrayInputStream(data))) {
int magic = dis.readInt(); // 校验魔数
if (magic != 0xCAFEBABE) throw new SecurityException("非法数据");
int objType = dis.readByte(); // 类型标识
if (objType != TYPE_MY_DTO) throw new SecurityException("类型不匹配");
MyDTO dto = new MyDTO();
dto.setId(dis.readLong());
dto.setName(dis.readUTF());
// 只解析已知字段
return dto;
}
}
}
检测与防护措施
即使使用了安全方案,建议在网关或入口处增加防护:
- 流量检测:使用
SerialKiller等工具检测恶意序列化流 - WAF 规则:拦截包含
java.io.Serializable或java.lang.Runtime的请求体 - 沙箱执行:对反序列化结果进行类型校验后再使用
// 严格的类型检查
Object obj = deserializer.readObject(inputStream);
if (!(obj instanceof AllowedDTO)) {
throw new SecurityException("非法反序列化类型");
}
最佳实践清单
| 措施 | 优先级 | 说明 |
|---|---|---|
| 使用 JSON / Protobuf | 完全避免 Java 原生反序列化风险 | |
| 配置类型白名单 | 如果必须用 Java 序列化 | |
| 升级依赖库 | 修复已知 CVE(如 Jackson、Fastjson) | |
禁止 readObject() 暴露 |
不要直接暴露读取序列化流的接口 | |
| 监控异常堆栈 | 发现 Runtime、ProcessBuilder 等高危类调用 |
一句话建议:
在分布式系统中,永远不要直接使用 Java 原生反序列化,优先选择 JSON 或 Protobuf,配合 schema 校验和白名单机制。