欢迎来到 黑吧安全网 聚焦网络安全前沿资讯,精华内容,交流技术心得!

浅析Java序列化和反序列化

来源:本站整理 作者:佚名 时间:2019-01-15 TAG: 我要投稿
}
它序列化后的二进制串大家可以自行消化理解一下,注意其中的嵌套对象,以及0x71表示的Reference类型标识 (形式上与JVM的常量池类似,用于非原始数据类型的引用对象池索引,这个引用对象池在序列化和反序列化创建时的元素填充顺序会保持一致) :
ac ed 00 05 73 72 00 11  53 65 72 69 61 6c 69 7a    ....sr.. Serializ
61 74 69 6f 6e 44 65 6d  6f 1a 7f cd d3 53 6f 6b    ationDem o....Sok
15 02 00 03 49 00 08 69  6e 74 46 69 65 6c 64 4c    ....I..i ntFieldL
00 09 63 6f 6d 70 6f 6e  65 6e 74 74 00 1d 4c 53    ..compon entt..LS
65 72 69 61 6c 69 7a 61  74 69 6f 6e 43 6f 6d 70    erializa tionComp
6f 6e 65 6e 74 43 6c 61  73 73 3b 4c 00 0b 73 74    onentCla ss;L..st
72 69 6e 67 46 69 65 6c  64 74 00 12 4c 6a 61 76    ringFiel dt..Ljav
61 2f 6c 61 6e 67 2f 53  74 72 69 6e 67 3b 78 72    a/lang/S tring;xr
00 17 53 65 72 69 61 6c  69 7a 61 74 69 6f 6e 53    ..Serial izationS
75 70 65 72 43 6c 61 73  73 de c6 50 b7 d1 2f a3    uperClas s..P../.
27 02 00 01 4c 00 0a 73  75 70 65 72 46 69 65 6c    '...L..s uperFiel
64 71 00 7e 00 02 78 70  70 00 01 7d f1 73 72 00    dq.~..xp p..}.sr.
1b 53 65 72 69 61 6c 69  7a 61 74 69 6f 6e 43 6f    .Seriali zationCo
6d 70 6f 6e 65 6e 74 43  6c 61 73 73 3c 76 ba b7    mponentC lass
简单的分析一下序列化的执行流程:
ObjectOutputStream实例初始化时,将魔术头和版本号写入bout (BlockDataOutputStream类型) 中
调用ObjectOutputStream.writeObject()开始写对象数据
ObjectStreamClass.lookup()封装待序列化的类描述 (返回ObjectStreamClass类型) ,获取包括类名、自定义serialVersionUID、可序列化字段 (返回ObjectStreamField类型) 和构造方法,以及writeObject、readObject方法等
writeOrdinaryObject()写入对象数据
写入对象类型标识
writeClassDesc()进入分支writeNonProxyDesc()写入类描述数据
写入类描述符标识
写入类名
写入SUID (当SUID为空时,会进行计算并赋值,细节见下面关于SerialVersionUID章节)
计算并写入序列化属性标志位
写入字段信息数据
写入Block Data结束标识
写入父类描述数据
writeSerialData()写入对象的序列化数据
若类自定义了writeObject(),则调用该方法写对象,否则调用defaultWriteFields()写入对象的字段数据 (若是非原始类型,则递归处理子对象)
反序列化
继续用简单的示例来看看反序列化:
public static void main(String[] args) throws ClassNotFoundException {
    byte[] data; // read from file or request
    ByteArrayInputStream bin = new ByteArrayInputStream(data);
    ObjectInputStream in = new ObjectInputStream(bin);
    SerializationDemo demo = (SerializationDemo) in.readObject();
}
它的执行流程如下:
ObjectInputStream实例初始化时,读取魔术头和版本号进行校验
调用ObjectInputStream.readObject()开始读对象数据
读取对象类型标识
readOrdinaryObject()读取数据对象
readClassDesc()读取类描述数据
读取类描述符标识,进入分支readNonProxyDesc()
读取类名
读取SUID
读取并分解序列化属性标志位
读取字段信息数据
resolveClass()根据类名获取待反序列化的类的Class对象,如果获取失败,则抛出ClassNotFoundException
skipCustomData()循环读取字节直到Block Data结束标识为止
读取父类描述数据
initNonProxy()中判断对象与本地对象的SUID和类名 (不含包名) 是否相同,若不同,则抛出InvalidClassException
ObjectStreamClass.newInstance()获取并调用离对象最近的非Serializable的父类的无参构造方法 (若不存在,则返回null) 创建对象实例
readSerialData()读取对象的序列化数据
若类自定义了readObject(),则调用该方法读对象,否则调用defaultReadFields()读取并填充对象的字段数据
关于SerialVersionUID
在Java的序列化机制中,SUID占据着很重要的位置,它相当于一个对象的指纹信息,可以直接决定反序列化的成功与否,通过上面对序列化和反序列化流程的分析也可以看出来,若SUID不一致,是无法反序列化成功的。
但是,SUID到底是如何生成的,它的指纹信息维度包括对象的哪些内容,可能还是有很多同学不太清楚。这里我们对照官方文档的说明,结合JDK的源代码来为大家简单的梳理一下。
首先ObjectStreamClass.getSerialVersionUID()在获取SUID时,会判断SUID是否已经存在,若不存在才调用computeDefaultSUID()计算默认的SUID:
public long getSerialVersionUID() {
    if (suid == null) {
        suid = AccessController.doPrivileged(
            new PrivilegedActionLong>() {
                public Long run() {
                    return computeDefaultSUID(cl);
                }
            }

上一页  [1] [2] [3] [4] [5] [6] [7] [8] [9] [10]  下一页

【声明】:黑吧安全网(http://www.myhack58.com)登载此文出于传递更多信息之目的,并不代表本站赞同其观点和对其真实性负责,仅适于网络安全技术爱好者学习研究使用,学习中请遵循国家相关法律法规。如有问题请联系我们,联系邮箱admin@myhack58.com,我们会在最短的时间内进行处理。
  • 最新更新
    • 相关阅读
      • 本类热门
        • 最近下载