ARTICLE DETAIL

资讯详情

深耕网站建设、视觉设计与SEO优化的一线实战洞察。

Java ObjectInputStream 详解:从基础用法到反序列化安全

Java ObjectInputStream 详解:从基础用法到反序列化安全 1. 引言在 Java 的 I/O 体系中ObjectInputStream是一个非常重要的类。它负责将之前通过ObjectOutputStream序列化写入的字节流重新还原为 Java 对象。这个过程被称为反序列化Deserialization。无论是本地缓存、网络传输还是分布式系统中的 RPC 调用ObjectInputStream都在背后默默承担着数据还原的重任。本文将带你从零开始系统掌握ObjectInputStream的核心用法、底层原理以及必须警惕的安全陷阱。2. 什么是 ObjectInputStreamObjectInputStream位于java.io包下它继承自InputStream是一个对象输入流。它的核心职责是读取字节流并将其反序列化为 Java 对象。publicclassObjectInputStreamextendsInputStreamimplementsObjectInput,ObjectStreamConstants它与ObjectOutputStream是成对出现的ObjectOutputStream把 Java 对象序列化为字节流写出去。ObjectInputStream把字节流反序列化为 Java 对象读进来。2.1 核心构造方法// 从输入流中创建 ObjectInputStreampublicObjectInputStream(InputStreamin)throwsIOException2.2 核心方法方法说明readObject()读取并反序列化一个对象readInt()/readUTF()等读取基本数据类型available()返回可读取的字节数close()关闭流并释放资源3. 基础用法序列化与反序列化3.1 准备一个可序列化的类要让对象能够被序列化必须实现Serializable接口importjava.io.Serializable;publicclassUserimplementsSerializable{privatestaticfinallongserialVersionUID1L;privateStringname;privateintage;publicUser(Stringname,intage){this.namename;this.ageage;}OverridepublicStringtoString(){returnUser{namename, ageage};}}3.2 使用 ObjectOutputStream 写入importjava.io.FileOutputStream;importjava.io.IOException;importjava.io.ObjectOutputStream;publicclassSerializeDemo{publicstaticvoidmain(String[]args){UserusernewUser(张三,25);try(ObjectOutputStreamoosnewObjectOutputStream(newFileOutputStream(user.dat))){oos.writeObject(user);System.out.println(对象已序列化到 user.dat);}catch(IOExceptione){e.printStackTrace();}}}3.3 使用 ObjectInputStream 读取importjava.io.FileInputStream;importjava.io.IOException;importjava.io.ObjectInputStream;publicclassDeserializeDemo{publicstaticvoidmain(String[]args){try(ObjectInputStreamoisnewObjectInputStream(newFileInputStream(user.dat))){Useruser(User)ois.readObject();System.out.println(反序列化成功: user);}catch(IOException|ClassNotFoundExceptione){e.printStackTrace();}}}运行结果反序列化成功: User{name张三, age25}4. 底层原理反序列化过程ObjectInputStream的反序列化过程远比想象中复杂其核心流程如下普通对象数组/枚举/字符串读取字节流解析流头 magic version读取对象描述信息判断对象类型创建实例不调用构造器特殊处理恢复字段值回调 readObject / readResolve返回对象4.1 关键步骤说明读取流头ObjectInputStream会先读取两个字节的魔数0xACED和版本号0x0005用于校验流格式。读取类描述读取类的全限定名、serialVersionUID以及字段描述符。创建实例通过sun.reflect.ReflectionFactory.newConstructorForSerialization()创建对象不会调用构造函数。恢复字段根据类描述中的字段信息逐个恢复字段值。回调钩子如果类中定义了readObject()或readResolve()方法会在此阶段被调用。4.2 serialVersionUID 的作用serialVersionUID是序列化版本号用于验证反序列化时的类与序列化时的类是否兼容privatestaticfinallongserialVersionUID1L;如果序列化和反序列化时的serialVersionUID不一致会抛出InvalidClassException。建议显式声明serialVersionUID避免因类结构微调导致反序列化失败。5. 高级特性5.1 transient 关键字被transient修饰的字段不会被序列化publicclassUserimplementsSerializable{privatestaticfinallongserialVersionUID1L;privateStringname;privatetransientStringpassword;// 不会被序列化}5.2 自定义 readObject可以在类中自定义readObject()方法控制反序列化过程privatevoidreadObject(ObjectInputStreamois)throwsIOException,ClassNotFoundException{// 先调用默认反序列化ois.defaultReadObject();// 自定义校验逻辑if(age0||age150){thrownewInvalidObjectException(年龄不合法: age);}}5.3 readResolve 单例保护对于单例类反序列化会破坏单例特性。通过readResolve()可以解决publicclassSingletonimplementsSerializable{privatestaticfinalSingletonINSTANCEnewSingleton();privateSingleton(){}publicstaticSingletongetInstance(){returnINSTANCE;}// 反序列化时返回同一个实例privateObjectreadResolve(){returnINSTANCE;}}6. 安全陷阱反序列化漏洞ObjectInputStream最大的风险在于反序列化漏洞。攻击者可以构造恶意字节流在readObject()时触发任意代码执行。6.1 漏洞原理当程序对不可信的字节流调用readObject()时攻击者可以利用 Java 原生库中的Gadget链如CommonsCollections构造恶意对象在反序列化过程中执行系统命令。6.2 防护措施方案一使用安全的对象过滤JEP 290JDK 9 提供了ObjectInputFilter可以限制允许反序列化的类ObjectInputFilterfilterObjectInputFilter.Config.createFilter(com.example.*;java.lang.*;!*);try(ObjectInputStreamoisnewObjectInputStream(newFileInputStream(data.bin))){ois.setObjectInputFilter(filter);Objectobjois.readObject();}方案二使用白名单校验privatestaticfinalSetStringWHITE_LISTSet.of(com.example.User,java.lang.String);privatevoidvalidateClass(Class?clazz)throwsInvalidClassException{if(!WHITE_LIST.contains(clazz.getName())){thrownewInvalidClassException(禁止反序列化的类: clazz.getName());}}方案三使用安全的序列化框架Fastjson2内置autoType安全校验。Jackson通过activateDefaultTyping配合白名单。Kryo默认不信任任意类。6.3 最佳实践永远不要对不可信数据直接调用readObject()。使用ObjectInputFilter设置类白名单。优先使用 JSON 等文本格式替代 Java 原生序列化。对敏感数据加密传输。7. 常见异常与排查异常原因解决方案InvalidClassExceptionserialVersionUID不匹配显式声明serialVersionUIDClassNotFoundException类不存在或类加载器问题检查类路径StreamCorruptedException流头校验失败确认数据来源正确OptionalDataException读取顺序与写入不一致按写入顺序读取EOFException提前到达流末尾检查数据完整性8. 总结ObjectInputStream是 Java 反序列化的核心入口掌握它需要理解三个层面用法层面与ObjectOutputStream配对使用实现对象的持久化与传输。原理层面理解流头校验、类描述读取、实例创建不调用构造器等底层机制。安全层面警惕反序列化漏洞使用ObjectInputFilter白名单机制防护。在实际开发中建议优先考虑 JSON 等跨语言格式若必须使用 Java 原生序列化务必做好安全防护。
返回列表