序列化代理模式

本文介绍了一种增强Java序列化安全性的方法——序列化代理模式。通过使用内部私有静态类作为序列化代理,并实现readResolve()和writeReplace()方法,可以有效避免序列化带来的安全隐患。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

Java序列化也带来了一些严重的误区,比如:

  • 类的结构无法大量改变,除非中断序列化处理,因此即便我们之后已经不需要某些变量了,我们也需要保留它们,仅仅是为了向后兼容。
  • 序列化会导致巨大的安全性危机,一个攻击者可以更改流的顺序,继而对系统造成伤害。举个例子,用户角色被序列化了,攻击者可以更改流的值为admin,再执行恶意代码。

序列化代理模式是一种使序列化能达到极高安全性的方式,在这个模式下,一个内部的私有静态类被用作序列化的代理类,该类的设计目的是用于保留主类的状态。这个模式的实现需要合理实现readResolve()和writeReplace()方法。

让我们先来写一个类,实现了序列化代码模式,之后再对其进行分析,以便更好的理解原理。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
package com.journaldev.serialization.proxy;
 
import java.io.InvalidObjectException;
import java.io.ObjectInputStream;
import java.io.Serializable;
 
public class Data implements Serializable{
 
     private static final long serialVersionUID = 2087368867376448459L;
 
     private String data;
 
     public Data(String d){
         this .data=d;
     }
 
     public String getData() {
         return data;
     }
 
     public void setData(String data) {
         this .data = data;
     }
 
     @Override
     public String toString(){
         return "Data{data=" +data+ "}" ;
     }
 
     //serialization proxy class
     private static class DataProxy implements Serializable{
 
         private static final long serialVersionUID = 8333905273185436744L;
 
         private String dataProxy;
         private static final String PREFIX = "ABC" ;
         private static final String SUFFIX = "DEFG" ;
 
         public DataProxy(Data d){
             //obscuring data for security
             this .dataProxy = PREFIX + d.data + SUFFIX;
         }
 
         private Object readResolve() throws InvalidObjectException {
             if (dataProxy.startsWith(PREFIX) && dataProxy.endsWith(SUFFIX)){
             return new Data(dataProxy.substring( 3 , dataProxy.length() - 4 ));
             } else throw new InvalidObjectException( "data corrupted" );
         }
 
     }
 
     //replacing serialized object to DataProxy object
     private Object writeReplace(){
         return new DataProxy( this );
     }
 
     private void readObject(ObjectInputStream ois) throws InvalidObjectException{
         throw new InvalidObjectException( "Proxy is not used, something fishy" );
     }
}
  • Data和DataProxy类都应该实现序列化接口。
  • DataProxy应该能够保留Data对象的状态。
  • DataProxy是一个内部的私有静态类,因此其他类无法访问它。
  • DataProxy应该有一个单独的构造方法,接收Data作为参数。
  • Data类应该提供writeReplace()方法,返回DataProxy实例,这样当Data对象被序列化时,返回的流是属于DataProxy类的,不过DataProxy类在外部是不可见的,所有它不能被直接使用。
  • DataProxy应该实现readResolve()方法,返回Data对象,这样当Data类被反序列化时,在内部其实是DataProxy类被反序列化了,之后它的readResolve()方法被调用,我们得到了Data对象。
  • 最后,在Data类中实现readObject()方法,抛出InvalidObjectException异常,防止黑客通过伪造Data对象的流并对其进行解析,继而执行攻击。

我们来写一个小测试,检查一下这样的实现是否能工作。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
package com.journaldev.serialization.proxy;
 
import java.io.IOException;
 
import com.journaldev.serialization.SerializationUtil;
 
public class SerializationProxyTest {
 
     public static void main(String[] args) {
         String fileName = "data.ser" ;
 
         Data data = new Data( "Pankaj" );
 
         try {
             SerializationUtil.serialize(data, fileName);
         } catch (IOException e) {
             e.printStackTrace();
         }
 
         try {
             Data newData = (Data) SerializationUtil.deserialize(fileName);
             System.out.println(newData);
         } catch (ClassNotFoundException | IOException e) {
             e.printStackTrace();
         }
     }
 
}

运行以上测试程序,可以得到以下输出。

1
Data{data=Pankaj}

如果你打开data.ser文件,可以看到DataProxy对象已经被作为流存入了文件中。

这就是Java序列化的所有内容,看上去很简单但我们应当谨慎地使用它,通常来说,最好不要依赖于默认实现。你可以从上面的链接中下载项目,玩一玩,这能让你学到更多。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值