真实的国产乱ⅩXXX66竹夫人,五月香六月婷婷激情综合,亚洲日本VA一区二区三区,亚洲精品一区二区三区麻豆

成都創(chuàng)新互聯(lián)網站制作重慶分公司

序列化——對于實例控制,枚舉類型優(yōu)先于readResolve-創(chuàng)新互聯(lián)

普通單例模式的漏洞

第3條講述了 Singleton 模式,并且給出了以下這個 Singleton 類的示例。

成都創(chuàng)新互聯(lián)專注于企業(yè)全網營銷推廣、網站重做改版、蘭山網站定制設計、自適應品牌網站建設、H5開發(fā)、商城系統(tǒng)網站開發(fā)、集團公司官網建設、成都外貿網站建設、高端網站制作、響應式網頁設計等建站業(yè)務,價格優(yōu)惠性價比高,為蘭山等各大城市提供網站開發(fā)制作服務。

這個類限制了對其構造器的訪問,確保永遠只創(chuàng)建一個實例:

public class Singer{
    public static final Singer INSTANCE = new Singer();
    private Singer(){}

    public static Singer getInstance(){
        return INSTANCE;
    }
}

但如果在這個類實現(xiàn)序列化,這種方式就不能保證安全了。序列化可以輕松突破這種機制,甚至,這種錯誤是在無意間的,并不是蓄意破壞。之前曾說過,序列化是一種獨立于構造器的創(chuàng)建對象的機制,或者你可以變相地認為序列化是一種以 byte[] 為參數(shù)的隱形構造器。

演示:

public class Singer implements Serializable {
    public static final Singer INSTANCE = new Singer();
    private Singer(){}

    public static Singer getInstance(){
        return INSTANCE;
    }

    private Object readResolve(){
        return INSTANCE;
    }
}
public class Client {
    public static void main(String[] args) throws IOException, ClassNotFoundException {
        Singer instance = Singer.getInstance();
        //序列化
        File file = new File("C:\\Users\\admin\\Desktop\\file_upload\\1.txt");
        ser(instance,file);

        //反序列化
        byte[] bytes = readBytes(file);
        Singer instance2 = deSer(bytes);

        //比較兩個對象是否相同
        System.out.println(instance == instance2);
    }

    static void ser(Singer s,File file) throws IOException {
        FileOutputStream fos = new FileOutputStream(file);
        ObjectOutputStream os = new ObjectOutputStream(fos);
        os.writeObject(s);
        os.close();
    }

    static Singer deSer(byte[] bytes) throws IOException, ClassNotFoundException {
        ObjectInputStream in = new ObjectInputStream(new ByteArrayInputStream(bytes));
        Singer p = (Singer) in.readObject();
        in.close();
        return p;
    }

    static byte[] readBytes(File file) throws IOException {
        FileInputStream in = null;
        try {
            in =new FileInputStream(file);
            //當文件沒有結束時,每次讀取一個字節(jié)顯示
            byte[] data=new byte[in.available()];
            in.read(data);
            in.close();
            return data;
        } catch (IOException e) {
            e.printStackTrace();
        }finally {
            in.close(); //關閉流
        }
        return null;
    }
}

false

可以看出,反序列化的對象和單例獲得的對象并不相同,從而導致單例模式失效


使用 readResole 方法

readResole 特性允許你用 readObject 創(chuàng)建的實例代替另一個實例。對于一個正在被反序列化的對象,如果它的類定義了一個 readResolve 方法,并且具備正確的聲明,那么在反序列化之后,新建對象上的 readResolve 方法就會被調用。然后,該方法返回的對象應用將被返回,取代新建的對象。在這個特性的絕大多數(shù)用法中,指向新建對象的引用不需要再被保留,因此立即成為垃圾回收的對象。

比如,使用以下的方法保證單例

private Object readResolve(){
    return INSTANCE;
}

該方法忽略了被反序列化的對象,只返回該類初始化時創(chuàng)建的那個特殊的實例。因此,Singer 實例的序列化形式并不需要包含任何實際的數(shù)據;所有的實例域都應該聲明為 transient。


readResole 方法的不足?

事實上,如果依賴 readResolve 進行實例控制,帶有對象引用類型的所有實例域則都必須聲明為 transient。否則,攻擊者依然可以想辦法在 readResolve 運行之前,獲取反序列化的對象引用,得到一個單例之外的“副本”,類似于上一條中提到的 MutablePeriod攻擊。

這種攻擊有點復雜,但背后的思想卻很簡單。如果 Singleton 包含一個非 transient 對象引用域,這個域的內容就可以在 Singleton 的 readResolve 方法運行之前被反序列化。當對象引用域的內容被反序列化時,它就允許一個精心制作的流“盜用”指向最初被反序列化的 Singleton 引用。

實例演示:

import java.io.Serializable;
import java.util.Arrays;

public class Elvis implements Serializable {
    public static final Elvis INSTANCE = new Elvis();
    private Elvis(){}
    private String[] favoriteSongs = {"紅顏如霜","發(fā)如雪"};
    public void printFavorites(){
        System.out.println(Arrays.toString(favoriteSongs));
    }

    private Object readResolve(){
        return INSTANCE;
    }

}
import java.io.Serializable;

public class ElvisStealer implements Serializable {

    static Elvis impersonator;
    private Elvis payload;

    private Object readResolve(){
        impersonator = payload;
        return new String[]{"發(fā)如霜"};
    }
    private static final long serialVersionUID = 0;

}
import lombok.SneakyThrows;
import java.io.ByteArrayInputStream;
import java.io.ObjectInputStream;

public class ElvisImpersonator {
    private static final byte[] bytes = new byte[]{
            (byte) 0xac, (byte) 0xed,0x00,0x05,0x73,0x72,0x00,0x05,
            0x45,0x6c,0x76,0x69,0x73, (byte) 0x84, (byte) 0xe6,
            (byte) 0x93,0x33, (byte) 0xc3, (byte) 0xf4, (byte) 0x8b,
            0x32,0x02,0x00,0x01,0x4c,0x00,0x0d,0x66,0x61,0x76,
            0x6f,0x72,0x69,0x74,0x65,0x53,0x6f,0x6e,0x67,0x73,
            0x74,0x00,0x12,0x4c,0x6a,0x61,0x76,0x61,0x2f,0x6c,
            0x61,0x6e,0x67,0x2f,0x4f,0x62,0x6a,0x65,0x63,0x74,
            0x3b,0x78,0x70,0x73,0x72,0x00,0x0c,0x45,0x6c,0x76,
            0x69,0x73,0x53,0x74,0x65,0x61,0x6c,0x65,0x72,0x00,
            0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x02,0x00,0x01,
            0x4c,0x00,0x07,0x70,0x61,0x79,0x6c,0x6f,0x61,0x64,
            0x74,0x00,0x07,0x4c,0x45,0x6c,0x76,0x69,0x73,0x3b,
            0x78,0x70,0x71,0x00,0x7e,0x00,0x02
    };

    public static void main(String[] args) {
        Elvis elvis = deser(bytes);
        Elvis impersonator = ElvisStealer.impersonator;
        elvis.printFavorites();
        impersonator.printFavorites();
    }

    @SneakyThrows
    static Elvis deser(byte[] bytes) {
        ObjectInputStream in = new ObjectInputStream(new ByteArrayInputStream(bytes));
        Elvis p = (Elvis) in.readObject();
        in.close();
        return p;
    }
}

通過將 favorites 聲明為 transient 可以解決這個問題。但如果確實要避免出現(xiàn)這種錯誤,最好的還是使用 枚舉來解決這種問題。

正如之前所提到過的:自從 jdk1.5之后,單例的最佳實現(xiàn)方式就是枚舉。當然,readResolve 并非完全過時,有些情況可能不適合使用枚舉,這時候依然需要這種方案。但一定要注意,屬性設置為transient 或者 基本類型

你是否還在尋找穩(wěn)定的海外服務器提供商?創(chuàng)新互聯(lián)www.cdcxhl.cn海外機房具備T級流量清洗系統(tǒng)配攻擊溯源,準確流量調度確保服務器高可用性,企業(yè)級服務器適合批量采購,新人活動首月15元起,快前往官網查看詳情吧


網站名稱:序列化——對于實例控制,枚舉類型優(yōu)先于readResolve-創(chuàng)新互聯(lián)
網頁鏈接:http://weahome.cn/article/dochpc.html

其他資訊

在線咨詢

微信咨詢

電話咨詢

028-86922220(工作日)

18980820575(7×24)

提交需求

返回頂部