85. 其它方法优先于Java序列化
Java系列化是很危险的,反系列化过程readObject可被攻击。避免系列化攻击的最佳方式是永远不要反系列化任何东西。在新编写的任何新系统中都没有理由再使用Java系列化。建议使用其它跨平台的数据表示法,如JSON或者protobuf。
如果无法避免Java系列化,最好不要反系列化不被信任的数据。如果不能确保被反系列化是数据的安全性,应该利用Java 9中的对象反系列化过滤(可以设置白名单或黑名单),虽然不能确保阻止所有的攻击。
不要编写可系列化的类。如果必须这么做,一定要加倍小心地试验。
86. 谨慎地实现Serializable接口
实现Serializable接口付出的最大代价是,一旦一个类被发布,就大大降低了"改变这个类的实现"的灵活性。第二个代价是,增加了出现Bug和安全漏洞的可能性。第三个代价是,随着类发行新的版本,相关的测试负担也会增加。
为了继承而设计的类应该尽可能少地去实现Serializable接口,用户的接口也应该尽可能少继承Seriablizable接口。内部类不应该实现Serializable接口。
87. 考虑使用自定义的序列化形式
选择错误的序列化形式对于一个类的复杂性和性能都会有永久的负面影响。
87.1 如果事先没有认真考虑默认的序列化形式是否合适,则不要贸然接受。
如果一个对象的物理表示法等同于它的逻辑内容,可能适合使用默认的序列化形式。
即使确定了默认的序列化形式是合适的,通常还必须提供一个readObject方法以保证约束关系和安全性。
87.2 当一个对象的物理表示法与它的逻辑数据内容有实质性的区别时,使用默认的序列化形式会有以下4个缺点:
a. 它使这个类的导出API永远地束缚在该类的内部表示法上。
b. 它会消耗过多的空间。
c. 它会消耗过多的时间。比如昂贵的图遍历过程。
d. 它会引起栈溢出。对对象图执行一次递归遍历,有可能引起栈溢出。
87.3 自定义序列化
提供readObject和writeObject。readObject方法的首要任务是调用defaultReadObject;而writeObject则是调用defaultWriteObject。这样可以不影响以后增加的**非瞬时的(须系列化的)**实例域,保持类版本向前或向后兼容性。
对于散列表而言,应该自定义序列化 。接受默认的序列化形式会构成一个严重的Bug,因为JVM不保证散列码函数每次运行结果一样。
在决定将一个域做成非瞬时的之前,请确认它的值是该对象逻辑状态的一部分。
被标记为瞬时(transient)的域,当一个实例被反序列化时,这些值将被初始化为默认值。如果这些默认值不能被接受,则须在readObject里初始化或者第一次使用时初始化。
87.4 无论是否使用默认的序列化形式,如果在读取整个对象状态的任何其它方法上强制任何同步,则必须在对象序列化上强制这种同步。
不管选择哪种序列化形式,都要为自己编写的每个可序列化的类声明一个显式的系列版本UID。不要修改系列版本UID,否则将会破坏类现有的已被序列化实例的兼容性。
88. 保护性地编写readObject方法
当一个对象被反序列化的时候,对于客户端不应该拥有的对象引用,如果哪个域包含了这样的对象引用,就必须要做保护性拷贝,这是非常重要的。
编写健壮readObject方法的原则:
a. 对于对象引用域必须保持为私有的类,要保护性地拷贝这些域中的每个对象。不可变类的可变组件就属于这一类别。
b. 对于任何约束条件,如果检查失败,则抛出一个InvalidObjectException异常。这些检查动作应该跟在所有的保护性拷贝之后。readObject方法必须执行构造器所要求的所有有效性检查。
c. 如果整个对象图在被反序列化之后必须进行验证,就应该使用ObjectInputInvalidation接口。
d. 无论是直接方式还是间接方式,都不要调用类中任何可被覆盖的方法。readObject方法相当于另一公有构造器。
89. 对于实例控制,枚举类型优先于readResolve
将一个可序列化的实例受控的类编写成枚举,可以保证除了所声明的常量外,不会有其它实例。如果做不到,就必须提供一个readResolve方法,并确保该类的所有实例域都为基本类型,或者对象引用类型都是瞬时的。
90. 考虑用序列化代理代替序列化实例
序列化代理模式,就是为可序列化的类设计一个私有的静态嵌套类,这个类有一个单独的构造器(参数为外围类)。这个构造器只从参数复制数据;不需要做一致性检查或保护性拷贝。序列化代理的默认序列化形式是外围类最好的序列化形式。外围类及其序列化代理都必须声明实现Serializable接口。
序列化代理方法可以阻止伪字节流的攻击及内部域的盗用攻击。它的缺点是开销会比保护性拷贝方法的开销更大。
序列化代理的局限:
a. 不能与可以被客户端扩展的类相兼容。
b. 不能与对象图中包含循环的某些类相兼容。
当发现必须在一个不能被客户端扩展的类上编写readObject或者writeObject方法时,就应该考虑使用序列化代理模式。
全书完结