文章目录

  • 简介
  • 序列化简介
  • 注意serialVersionUID
  • writeObject和readObject
  • readResolve和writeReplace
  • 不要序列化内部类
  • 如果类中有自定义变量,那么不要使用默认的序列化
  • 不要在readObject中调用可重写的方法

简介

序列化是java中一个非常常用又会被人忽视的功能,我们将对象写入文件需要序列化,同时,对象如果想要在网络上传输也需要进行序列化。

序列化的目的就是保证对象可以正确的传输,那么我们在序列化的过程中需要注意些什么问题呢?

一起来看看吧。

序列化简介

如果一个对象要想实现序列化,只需要实现Serializable接口即可。

奇怪的是Serializable是一个不需要任何实现的接口。如果我们implements Serializable但是不重写任何方法,那么将会使用JDK自带的序列化格式。

但是如果class发送变化,比如增加了字段,那么默认的序列化格式就满足不了我们的需求了,这时候我们需要考虑使用自己的序列化方式。

如果类中的字段不想被序列化,那么可以使用transient关键字。

同样的,static表示的是类变量,也不需要被序列化。

注意serialVersionUID

serialVersionUID 表示的是对象的序列ID,如果我们不指定的话,是JVM自动生成的。在反序列化的过程中,JVM会首先判断serialVersionUID 是否一致,如果不一致,那么JVM会认为这不是同一个对象。

如果我们的实例在后期需要被修改的话,注意一定不要使用默认的serialVersionUID,否则后期class发送变化之后,serialVersionUID也会同样的发生变化,最终导致和之前的序列化版本不兼容。

writeObject和readObject

如果要自己实现序列化,那么可以重写writeObject和readObject两个方法。

注意,这两个方法是private的,并且是non-static的:

private void writeObject(final ObjectOutputStream stream)throws IOException {stream.defaultWriteObject();
}private void readObject(final ObjectInputStream stream)throws IOException, ClassNotFoundException {stream.defaultReadObject();
}

如果不是private和non-static的,那么JVM就不能够发现这两个方法,就不会使用他们来做自定义序列化。

readResolve和writeReplace

如果class中的字段比较多,而这些字段都可以从其中的某一个字段中自动生成,那么我们其实并不需要序列化所有的字段,我们只把那一个字段序列化就可以了,其他的字段可以从该字段衍生得到。

readResolve和writeReplace就是序列化对象的代理功能。

首先,序列化对象需要实现writeReplace方法,表示替换成真正想要写入的对象:

public class CustUserV3 implements java.io.Serializable{private String name;private String address;private Object writeReplace()throws java.io.ObjectStreamException{log.info("writeReplace {}",this);return new CustUserV3Proxy(this);}
}

然后在Proxy对象中,需要实现readResolve方法,用于从系列化过的数据中重构序列化对象。如下所示:

public class CustUserV3Proxy implements java.io.Serializable{private String data;public CustUserV3Proxy(CustUserV3 custUserV3){data =custUserV3.getName()+ "," + custUserV3.getAddress();}private Object readResolve()throws java.io.ObjectStreamException{String[] pieces = data.split(",");CustUserV3 result = new CustUserV3(pieces[0], pieces[1]);log.info("readResolve {}",result);return result;}
}

我们看下怎么使用:

public void testCusUserV3() throws IOException, ClassNotFoundException {CustUserV3 custUserA=new CustUserV3("jack","www.flydean.com");try(FileOutputStream fileOutputStream = new FileOutputStream("target/custUser.ser")){ObjectOutputStream objectOutputStream = new ObjectOutputStream(fileOutputStream);objectOutputStream.writeObject(custUserA);}try(FileInputStream fileInputStream = new FileInputStream("target/custUser.ser")){ObjectInputStream objectInputStream = new ObjectInputStream(fileInputStream);CustUserV3 custUser1 = (CustUserV3) objectInputStream.readObject();log.info("{}",custUser1);}}

注意,我们写入和读出的都是CustUserV3对象。

不要序列化内部类

所谓内部类就是未显式或隐式声明为静态的嵌套类,为什么我们不要序列化内部类呢?

  • 序列化在非静态上下文中声明的内部类,该内部类包含对封闭类实例的隐式非瞬态引用,从而导致对其关联的外部类实例的序列化。

  • Java编译器对内部类的实现在不同的编译器之间可能有所不同。从而导致不同版本的兼容性问题。

  • 因为Externalizable的对象需要一个无参的构造函数。但是内部类的构造函数是和外部类的实例相关联的,所以它们无法实现Externalizable。

所以下面的做法是正确的:

public class OuterSer implements Serializable {private int rank;class InnerSer {protected String name;}
}

如果你真的想序列化内部类,那么把内部类置为static吧。

如果类中有自定义变量,那么不要使用默认的序列化

如果是Serializable的序列化,在反序列化的时候是不会执行构造函数的。所以,如果我们在构造函数或者其他的方法中对类中的变量有一定的约束范围的话,反序列化的过程中也必须要加上这些约束,否则就会导致恶意的字段范围。

我们举几个例子:

public class SingletonObject implements Serializable {private static final SingletonObject INSTANCE = new SingletonObject ();public static SingletonObject getInstance() {return INSTANCE;}private SingletonObject() {}public static Object deepCopy(Object obj) {try {ByteArrayOutputStream bos = new ByteArrayOutputStream();new ObjectOutputStream(bos).writeObject(obj);ByteArrayInputStream bin =new ByteArrayInputStream(bos.toByteArray());return new ObjectInputStream(bin).readObject();} catch (Exception e) {throw new IllegalArgumentException(e);}}public static void main(String[] args) {SingletonObject singletonObject= (SingletonObject) deepCopy(SingletonObject.getInstance());System.out.println(singletonObject == SingletonObject.getInstance());}
}

上面是一个singleton对象的例子,我们在其中定义了一个deepCopy的方法,通过序列化来对对象进行拷贝,但是拷贝出来的是一个新的对象,尽管我们定义的是singleton对象,最后运行的结果还是false,这就意味着我们的系统生成了一个不一样的对象。

怎么解决这个问题呢?

加上一个readResolve方法就可以了:

    protected final Object readResolve() throws NotSerializableException {return INSTANCE;}

在这个readResolve方法中,我们返回了INSTANCE,以确保其是同一个对象。

还有一种情况是类中字段是有范围的。

public class FieldRangeObject implements Serializable {private int age;public FieldRangeObject(int age){if(age < 0 || age > 100){throw new IllegalArgumentException("age范围不对");}this.age=age;}
}

上面的类在反序列化中会有什么问题呢?

因为上面的类在反序列化的过程中,并没有对age字段进行校验,所以,恶意代码可能会生成超出范围的age数据,当反序列化之后就溢出了。

怎么处理呢?

很简单,我们在readObject方法中进行范围的判断即可:

    private  void readObject(java.io.ObjectInputStream s)throws IOException, ClassNotFoundException {ObjectInputStream.GetField fields = s.readFields();int age = fields.get("age", 0);if (age > 100 || age < 0) {throw new InvalidObjectException("age范围不对!");}this.age = age;}

不要在readObject中调用可重写的方法

为什么呢?readObject实际上是反序列化的构造函数,在readObject方法没有结束之前,对象是没有构建完成,或者说是部分构建完成。如果readObject调用了可重写的方法,那么恶意代码就可以在方法的重写中获取到还未完全实例化的对象,可能造成问题。

本文的代码:

learn-java-base-9-to-20/tree/master/security

本文已收录于 http://www.flydean.com/java-security-code-line-serialization/

最通俗的解读,最深刻的干货,最简洁的教程,众多你不知道的小技巧等你来发现!

欢迎关注我的公众号:「程序那些事」,懂技术,更懂你!

java安全编码指南之:序列化Serialization相关推荐

  1. java安全编码指南之:拒绝Denial of Service

    文章目录 简介 为什么会有DOS 不合理的资源使用 请求用于矢量图的SVG文件和字体文件 字符串或二进制表示的图片转换 zip炸弹 billion laughs attack hashMap中插入太多 ...

  2. java安全编码指南之:输入校验

    文章目录 简介 在字符串标准化之后进行校验 注意不可信字符串的格式化 小心使用Runtime.exec() 正则表达式的匹配 简介 为了保证java程序的安全,任何外部用户的输入我们都认为是可能有恶意 ...

  3. java安全编码指南之:字符串和编码

    文章目录 简介 使用变长编码的不完全字符来创建字符串 char不能表示所有的Unicode 注意Locale的使用 文件读写中的编码格式 不要将非字符数据编码为字符串 简介 字符串是我们日常编码过程中 ...

  4. java安全编码指南之:Mutability可变性

    文章目录 简介 可变对象和不可变对象 创建mutable对象的拷贝 为mutable类创建copy方法 不要相信equals 不要直接暴露可修改的属性 public static fields应该被置 ...

  5. java安全编码指南之:堆污染Heap pollution

    简介 什么是堆污染呢?堆污染是指当参数化类型变量引用的对象不是该参数化类型的对象时而发生的. 我们知道在JDK5中,引入了泛型的概念,我们可以在创建集合类的时候,指定该集合类中应该存储的对象类型. 如 ...

  6. java安全编码指南之:异常处理

    简介:异常是java程序员无法避免的一个话题,我们会有JVM自己的异常也有应用程序的异常,对于不同的异常,我们的处理原则是不是一样的呢? 一起来看看吧. 简介 异常是java程序员无法避免的一个话题, ...

  7. java安全编码指南之:文件和共享目录的安全性

    文章目录 简介 linux下的文件基本权限 linux文件的特殊权限 Set UID 和 Set GID Sticky Bit SUID/SGID/SBIT权限设置 文件隐藏属性 特殊文件 java中 ...

  8. java安全编码指南之:文件IO操作

    文章目录 简介 创建文件的时候指定合适的权限 注意检查文件操作的返回值 删除使用过后的临时文件 释放不再被使用的资源 注意Buffer的安全性 注意 Process 的标准输入输出 InputStre ...

  9. java安全编码指南之:线程安全规则

    文章目录 简介 注意线程安全方法的重写 构造函数中this的溢出 不要在类初始化的时候使用后台线程 简介 如果我们在多线程中引入了共享变量,那么我们就需要考虑一下多线程下线程安全的问题了.那么我们在编 ...

最新文章

  1. Python数据集可视化:抽取数据集的两个特征进行二维可视化、主成分分析PCA对数据集降维进行三维可视化(更好地理解维度之间的相互作用)
  2. silk 编解码_silk音频编解码C++类
  3. 关于MYSQL 字符转义问题总结
  4. android Service oncreate 在UI线程 何时用service,何时用thread
  5. 兰大计算机学院保研,兰州大学的保研情况怎么样?保研率高吗?
  6. Windows PE导出表编程2(重组导出表函数地址)
  7. 程序员找工作那些事(一)幸存者偏差
  8. java获取500错误_HTTP 500错误
  9. Ant Design 3.15.0 发布,企业级 UI 设计语言和 React 实现
  10. python中的URL编码和解码
  11. 不敌 Java、C/C++、Python,28 岁 VB 究竟输在了哪?
  12. 变身抓重点小能手:机器学习中的文本摘要入门指南 | 资源
  13. java newtonsoft.json_Newtonsoft.Json(Json.Net)学习笔记
  14. php网页动态加载swf,HTML_如何修改网页中的FlashSWF文件,看到一个网站用flash做的,很 - phpStudy...
  15. 五金冲压模具设计分享pressCAD外挂使用小窍门
  16. dp hp oracle 备份软件_HP DP备份软件设置
  17. 软件项目管理 1.3.敏捷项目管理概念
  18. 2022-2027年中国OLED市场竞争态势及行业投资前景预测报告
  19. python基础入门1
  20. 解决[“usingcomponents“][“van-button“]: “@vant/weapp/button/index“ 未找到:

热门文章

  1. Linux服务-FTP文件服务器部署
  2. Collecting package metadata (current_repodata.json): done Solving environment: failed with repodata
  3. HDU2227(非降子序列的个数)
  4. [luogu2042] [NOI2005]维护数列
  5. TIME_WAIT简介
  6. svchost.exe启动服务原理
  7. Shell case esac语句
  8. boost中unordered_map的用法
  9. 《RabbitMQ实战指南》笔误及改进记录
  10. 实战:如何对磁盘和网络IO进行评估、监控、定位和优化?