1. 程式人生 > >Java設計模式(一):單例模式,防止反射和反序列化漏洞

Java設計模式(一):單例模式,防止反射和反序列化漏洞

package com.iter.devbox.singleton;

import java.io.ObjectStreamException;
import java.io.Serializable;

/**
 * 靜態內部類實現方式(也是一種懶載入方式)
 * 這種方式:執行緒安全,呼叫效率高,並且實現了延遲載入
 * 解決反射和反序列化漏洞
 * @author Shearer
 *
 */
public class SingletonDemo7 implements Serializable{
	
	private static class SingletonClassInstance {
		private static final SingletonDemo7 instance = new SingletonDemo7();
	}
	
	// 方法沒有同步,呼叫效率高
	public static SingletonDemo7 getInstance() {
		return SingletonClassInstance.instance;
	}
	
	// 防止反射獲取多個物件的漏洞
	private SingletonDemo7() {
		if (null != SingletonClassInstance.instance)
			throw new RuntimeException();
	}
	
	// 防止反序列化獲取多個物件的漏洞
	private Object readResolve() throws ObjectStreamException {  
		return SingletonClassInstance.instance;
	}
}


package com.iter.devbox.singleton;

import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.ObjectInputStream;
import java.io.ObjectOutputStream;
import java.lang.reflect.Constructor;

public class Client3 {

	public static void main(String[] args) throws Exception {
		SingletonDemo7 sc1 = SingletonDemo7.getInstance();
		SingletonDemo7 sc2 = SingletonDemo7.getInstance();
		System.out.println(sc1); // sc1,sc2是同一個物件
		System.out.println(sc2);
		
		// 通過反射的方式直接呼叫私有構造器(通過在構造器裡丟擲異常可以解決此漏洞)
		Class<SingletonDemo7> clazz = (Class<SingletonDemo7>) Class.forName("com.iter.devbox.singleton.SingletonDemo7");
		Constructor<SingletonDemo7> c = clazz.getDeclaredConstructor(null);
		c.setAccessible(true); // 跳過許可權檢查
		SingletonDemo7 sc3 = c.newInstance();
		SingletonDemo7 sc4 = c.newInstance();
		System.out.println("通過反射的方式獲取的物件sc3:" + sc3);  // sc3,sc4不是同一個物件
		System.out.println("通過反射的方式獲取的物件sc4:" + sc4);
		
		// 通過反序列化的方式構造多個物件(類需要實現Serializable介面)
		
		// 1. 把物件sc1寫入硬碟檔案
		FileOutputStream fos = new FileOutputStream("object.out");
		ObjectOutputStream oos = new ObjectOutputStream(fos);
		oos.writeObject(sc1);
		oos.close();
		fos.close();
		
		// 2. 把硬碟檔案上的物件讀出來
		ObjectInputStream ois = new ObjectInputStream(new FileInputStream("object.out"));
		// 如果物件定義了readResolve()方法,readObject()會呼叫readResolve()方法。從而解決反序列化的漏洞
		SingletonDemo7 sc5 = (SingletonDemo7) ois.readObject();
		// 反序列化出來的物件,和原物件,不是同一個物件。如果物件定義了readResolve()方法,可以解決此問題。
		System.out.println("物件定義了readResolve()方法,通過反序列化得到的物件:" + sc5); 
		ois.close();
	}

}