10.6.5 安全性

在使用了对象序列化机制之后,在虚拟机中运行的活动对象的内部状态被持久化,可以通过网络及其他方式进行传输。这会使一些内部数据存在暴露的风险。对象序列化后的字节流的格式是固定的,可以很容易地从中分析出相关的数据。为了避免产生安全方面的问题,应该从多个方面着手解决。

第一个方面是根据信息隐藏的原则,尽可能地减少序列化结果中包含的信息。所包含的信息越少,泄露之后造成的影响就越小。通过自定义的序列化格式和对象替换可以实现这一点。

第二个方面是对包含序列化结果的字节流进行保护。措施主要是加密和解密。可以对序列化后的字节流的整体使用各种加密算法进行处理。在反序列化之前再使用相应的解密算法进行处理即可。加密和解密也可以在序列化的过程中进行。在Java类的writeObject方法中,可以在写入域的值之前,先进行加密操作。在readObject方法中,在读取域的值之后,先进行解密操作,再赋值给对象中的域。这两种方式可以结合起来形成复杂的加密方案。

第三个方面是在从字节流中进行反序列化操作时进行数据完整性验证。在使用ObjectInputStream类的对象进行读取操作时,输入字节流的内容有可能已经被篡改,因此在Java类的readObject方法中需要添加相应的验证代码来检查完整性是否被破坏,比如验证域的值是否为null,以及域的值是否在合理的范围之内等。在readObject方法中的处理适合于对单个Java类的对象进行验证。要对一个完整的对象图进行验证,可以通过ObjectInputStream类的registerValidation方法添加java.io.ObjectInputValidation接口的实现对象,进而添加完整的对象验证逻辑。通常的做法是在readObject方法中处理完所有域之后,再添加一个ObjectInputValidation接口的实现对象来进行完整的验证。验证通常在引用关系根节点的Java类的readObject方法中进行。代码清单10-22给出了NewUser类的对象的验证方法的示例。类UserValidator完成具体的验证工作,检查age域的值是否合法。如果验证中发现了错误,则直接抛出java.io.InvalidObjectException异常。在readObject方法中,对作为参数传入的ObjectInputStream类的对象添加UserValidator类的对象来执行验证逻辑。

代码清单10-22 对象完整性验证的示例


private void readObject(ObjectInputStream input)throws IOException,

ClassNotFoundException{

input.defaultReadObject();

int age=input.readInt();

this.birthDate=ageToDate(age);

input.registerValidation(new UserValidator(this),0);

}

private static class UserValidator implements ObjectInputValidation{

private NewUser user;

public UserValidator(NewUser user){

this.user=user;

}

public void validateObject()throws InvalidObjectException{

if(user.getAge()<0){

throw new InvalidObjectException("非法的年龄数值。");

}

}

}