Effective Java之考虑用序列化代理代理序列化实例(七十八)

我们知道,实现了序列化的类。在反序列化时,实例的创建是由readObject方法来完成的。由于这是一个不同于构造函数的创建类实例的通道,因此在构造函数中的状态约束条件在readObjetc中也得一条不落下的实现。这很让人头大。
而且readObject的出现,让伪字节流攻击和内部域的盗用攻击成为可能。伪字节流攻击就是伪造一个字节流,通过readObject读取,内部域的盗用攻击是指用一个外部类包含该类,用外部类的字段去指向该类的可变对象。
因此这里介绍一种模式,将实例的反序列化也交给改造函数来完成,即序列化代理模式。要想稳健的将带有重要约束条件的对象序列化时,这种模式可能是最容易的方法。代码如下:

public class Period implements Serializable{  

    private static final long serialVersionUID = 1L;  
    private final Date start;  
    private final Date end;  

    public Period(Date start, Date end) {  

        if(null == start || null == end || start.after(end)){  

            throw new IllegalArgumentException("请传入正确的时间区间!");  
        }  
        this.start = start;  
        this.end = end;  
    }  

    public Date start(){  

        return new Date(start.getTime());  
    }  

    public Date end(){  

        return new Date(end.getTime());  
    }  

    @Override  
    public String toString(){  

        return "起始时间:" + start + " , 结束时间:" + end;  
    }  

    /** 
     * 序列化外围类时,虚拟机会转掉这个方法,最后其实是序列化了一个内部的代理类对象! 
     * @return 
     */  
    private Object writeReplace(){  

        System.out.println("进入writeReplace()方法!");  
        return new SerializabtionProxy(this);  
    }  

    /** 
     * 如果攻击者伪造了一个字节码文件,然后来反序列化也无法成功,因为外围类的readObject方法直接抛异常! 
     * @param ois 
     * @throws InvalidObjectException 
     */  
    private void readObject(ObjectInputStream ois) throws InvalidObjectException{  

        throw new InvalidObjectException("Proxy required!");  
    }  

    /** 
     * 序列化代理类,他精确表示了其当前外围类对象的状态!最后序列化时会将这个私有内部内进行序列化! 
     */  
    private static class SerializabtionProxy implements Serializable{  

        private static final long serialVersionUID = 1L;  
        private final Date start;  
        private final Date end;  
        SerializabtionProxy(Period p){  

            this.start = p.start;  
            this.end = p.end;  
        }  

        /** 
         * 反序列化这个类时,虚拟机会调用这个方法,最后返回的对象是一个Period对象!这里同样调用了Period的构造函数, 
         * 会进行构造函数的一些校验!  
         */  
        private Object readResolve(){  

            System.out.println("进入readResolve()方法,将返回Period对象!");  
            // 这里进行保护性拷贝!  
            return new Period(new Date(start.getTime()), new Date(end.getTime()));  
        }  

    }  

思路很清晰,拒绝外围类的readObject的调用,也就防止了伪字节流攻击和内部域的盗用攻击,同时无需保护性拷贝。

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值