单例设计模式-反射攻击解决方案及原理分析
生活随笔
收集整理的這篇文章主要介紹了
单例设计模式-反射攻击解决方案及原理分析
小編覺得挺不錯的,現(xiàn)在分享給大家,幫大家做個參考.
package com.learn.design.pattern.creational.singleton;import java.io.Serializable;public class HungrySingleton implements Serializable,Cloneable{private final static HungrySingleton hungrySingleton;static{hungrySingleton = new HungrySingleton();}/*** 我們關(guān)注的是私有構(gòu)造器* 那因為這個構(gòu)造器是私有的* 在Test里面是無法new的* 但是我們能不能通過反射* 并且把構(gòu)造器的權(quán)限打開* 然后去獲取這個對象* 我們現(xiàn)在來試一下* 先不打開他的權(quán)限* * */private HungrySingleton(){/*** 直接在這里面做一個判斷* 如果這個不是空* 那我們就直接throw一個異常* 異常是一個runtimeException* 里面寫上"單例構(gòu)造器禁止反射調(diào)用"* 那我們現(xiàn)在再回到Test里面* 可以看到我們的異常已經(jīng)打印出來* 單例構(gòu)造器禁止反射調(diào)用* 那這種方式有一種特點* 也就是在類加載這個時刻就把類加載好了* 這種類是OK的* 對于這種單例模式有效的* 那還有那種單例模式可以用呢* 很簡單那就是靜態(tài)類* * */if(hungrySingleton != null){throw new RuntimeException("單例構(gòu)造器禁止反射調(diào)用");}}public static HungrySingleton getInstance(){return hungrySingleton;}/*** 現(xiàn)在序列化和反序列化方法已經(jīng)有了* * * @return*/private Object readResolve(){return hungrySingleton;}@Overrideprotected Object clone() throws CloneNotSupportedException {return getInstance();}
}
package com.learn.design.pattern.creational.singleton;import java.io.IOException;
import java.lang.reflect.Constructor;
import java.lang.reflect.InvocationTargetException;public class Test {public static void main(String[] args) throws IOException, ClassNotFoundException, NoSuchMethodException, IllegalAccessException, InvocationTargetException, InstantiationException {
// LazySingleton lazySingleton = LazySingleton.getInstance();// System.out.println("main thread"+ThreadLocalInstance.getInstance());
// System.out.println("main thread"+ThreadLocalInstance.getInstance());
// System.out.println("main thread"+ThreadLocalInstance.getInstance());
// System.out.println("main thread"+ThreadLocalInstance.getInstance());
// System.out.println("main thread"+ThreadLocalInstance.getInstance());
// System.out.println("main thread"+ThreadLocalInstance.getInstance());// Thread t1 = new Thread(new T());
// Thread t2 = new Thread(new T());
// t1.start();
// t2.start();
// System.out.println("program end");// HungrySingleton instance = HungrySingleton.getInstance();
// EnumInstance instance = EnumInstance.getInstance();
// instance.setData(new Object());
//
// ObjectOutputStream oos = new ObjectOutputStream(new FileOutputStream("singleton_file"));
// oos.writeObject(instance);
//
// File file = new File("singleton_file");
// ObjectInputStream ois = new ObjectInputStream(new FileInputStream(file));
//
// HungrySingleton newInstance = (HungrySingleton) ois.readObject();
// EnumInstance newInstance = (EnumInstance) ois.readObject();
//
// System.out.println(instance.getData());
// System.out.println(newInstance.getData());
// System.out.println(instance.getData() == newInstance.getData());Class objectClass = HungrySingleton.class;
// Class objectClass = StaticInnerClassSingleton.class;// Class objectClass = LazySingleton.class;
// Class objectClass = EnumInstance.class;/*** 用構(gòu)造器這個類* 反射里邊的* 聲明的構(gòu)造器getDeclaredConstructor* 報紅線了* 有異常* 直接拋出* 那我們在寫實際業(yè)務(wù)的時候* 根據(jù)業(yè)務(wù)場景確定向上拋出還是try catch* 那我們這里面為了方便* 所以都在主函數(shù)里邊拋出了* 這個也是一樣的* * * */Constructor constructor = objectClass.getDeclaredConstructor();
// Constructor constructor = objectClass.getDeclaredConstructor(String.class,int.class);
///*** 我們設(shè)置一下權(quán)限* */constructor.setAccessible(true);
// EnumInstance instance = (EnumInstance) constructor.newInstance("Geely",666);//
// LazySingleton newInstance = (LazySingleton) constructor.newInstance();
// LazySingleton instance = LazySingleton.getInstance();// StaticInnerClassSingleton instance = StaticInnerClassSingleton.getInstance();
// StaticInnerClassSingleton newInstance = (StaticInnerClassSingleton) constructor.newInstance();/*** 我們再拿一個對象* 爆紅了看一下* 拋出異常* 然后呢強轉(zhuǎn)* * */HungrySingleton newInstance = (HungrySingleton) constructor.newInstance();/*** 首先我們從這個類里面拿到一個instance* 直接調(diào)用它的getInstance* * */HungrySingleton instance = HungrySingleton.getInstance();/*** 我們把這兩個也看一下* 結(jié)果已經(jīng)出來了* 我們看一下* 通過反射把這個權(quán)限置為true的話* 那他的構(gòu)造器private* 這個權(quán)限就放開了* 所以Test里面調(diào)用newInstance的時候* 就直接調(diào)用了這個構(gòu)造器* 對象就實例化出來了* 我們看一下這兩個不是同一個對象* 結(jié)果也是false* 那對于餓漢式有什么特點呢* 首先它是在類加載的時候就生成了這個實例* 那是因為它是在類加載的時候生成* 所以我們可以在構(gòu)造器里面進(jìn)行一個判斷* 現(xiàn)在我們就寫一下反射防御的一些代碼* * * */System.out.println(instance);System.out.println(newInstance);System.out.println(instance == newInstance);// EnumInstance instance = EnumInstance.getInstance();
// instance.printTest();}
}
package com.learn.design.pattern.creational.singleton;public class StaticInnerClassSingleton {private static class InnerClass{private static StaticInnerClassSingleton staticInnerClassSingleton = new StaticInnerClassSingleton();}public static StaticInnerClassSingleton getInstance(){return InnerClass.staticInnerClassSingleton;}/*** 我們現(xiàn)在把防止單列反射的代碼也加上* StaticInnerClassSingleton這個是私有構(gòu)造器* InnerClass.staticInnerClassSingleton* 這樣這段防御代碼也加好了* 我們再run一下Test* 異常就正如我們期望的拋出了* 那這兩個單例模式* 剛剛說了* 他們的特點就是在他們加載的時候* 這個實例就會生成好* 因為這是靜態(tài)內(nèi)部類* 前面我們已經(jīng)講了* InnerClass.staticInnerClassSingleton;* 里面調(diào)用靜態(tài)內(nèi)部類* 然后就會把這個對象new出來* staticInnerClassSingleton = new StaticInnerClassSingleton();* 這里就不debug了* debug也會到這里面拋出異常* 很簡單* 那接下來要說的是* 對于不是在類加載的時候* 創(chuàng)建單例對象的* 這種情況* 我們也來測試一下* 我們來看一下* 有懶漢式的* 我們隨便選一個* 選一個簡單的就可以* 這兩個并沒有什么區(qū)別* 因為他們都不是在類加載的時候就初始化對象了* 我們就用LazySingleton* * */private StaticInnerClassSingleton(){if(InnerClass.staticInnerClassSingleton != null){throw new RuntimeException("單例構(gòu)造器禁止反射調(diào)用");}}
}
package com.learn.design.pattern.creational.singleton;import java.io.IOException;
import java.lang.reflect.Constructor;
import java.lang.reflect.InvocationTargetException;public class Test {public static void main(String[] args) throws IOException, ClassNotFoundException, NoSuchMethodException, IllegalAccessException, InvocationTargetException, InstantiationException {
// LazySingleton lazySingleton = LazySingleton.getInstance();// System.out.println("main thread"+ThreadLocalInstance.getInstance());
// System.out.println("main thread"+ThreadLocalInstance.getInstance());
// System.out.println("main thread"+ThreadLocalInstance.getInstance());
// System.out.println("main thread"+ThreadLocalInstance.getInstance());
// System.out.println("main thread"+ThreadLocalInstance.getInstance());
// System.out.println("main thread"+ThreadLocalInstance.getInstance());// Thread t1 = new Thread(new T());
// Thread t2 = new Thread(new T());
// t1.start();
// t2.start();
// System.out.println("program end");// HungrySingleton instance = HungrySingleton.getInstance();
// EnumInstance instance = EnumInstance.getInstance();
// instance.setData(new Object());
//
// ObjectOutputStream oos = new ObjectOutputStream(new FileOutputStream("singleton_file"));
// oos.writeObject(instance);
//
// File file = new File("singleton_file");
// ObjectInputStream ois = new ObjectInputStream(new FileInputStream(file));
//
// HungrySingleton newInstance = (HungrySingleton) ois.readObject();
// EnumInstance newInstance = (EnumInstance) ois.readObject();
//
// System.out.println(instance.getData());
// System.out.println(newInstance.getData());
// System.out.println(instance.getData() == newInstance.getData());// Class objectClass = HungrySingleton.class;Class objectClass = StaticInnerClassSingleton.class;// Class objectClass = LazySingleton.class;
// Class objectClass = EnumInstance.class;Constructor constructor = objectClass.getDeclaredConstructor();
// Constructor constructor = objectClass.getDeclaredConstructor(String.class,int.class);
//constructor.setAccessible(true);
// EnumInstance instance = (EnumInstance) constructor.newInstance("Geely",666);//
// LazySingleton newInstance = (LazySingleton) constructor.newInstance();
// LazySingleton instance = LazySingleton.getInstance();/*** 這兩個是不同的對象* * */StaticInnerClassSingleton instance = StaticInnerClassSingleton.getInstance();StaticInnerClassSingleton newInstance = (StaticInnerClassSingleton) constructor.newInstance();// HungrySingleton newInstance = (HungrySingleton) constructor.newInstance();
// HungrySingleton instance = HungrySingleton.getInstance();System.out.println(instance);System.out.println(newInstance);System.out.println(instance == newInstance);// EnumInstance instance = EnumInstance.getInstance();
// instance.printTest();}
}
package com.learn.design.pattern.creational.singleton;/*** * @author Leon.Sun**/ public class LazySingleton {/*** 首先他在類加載的時候沒有生成* 只有調(diào)用getInstance方法的時候* 才會生成這個對象* * */private static LazySingleton lazySingleton = null;/*** */private LazySingleton(){/*** 如果把這段代碼加載這里邊* 這個就和我們創(chuàng)建實例的順序有關(guān)了* 為什么這么說呢* 回到Test里邊* 我們是先調(diào)用的getInstance* 而且這個方法是同步方法* * */if(lazySingleton != null){throw new RuntimeException("單例構(gòu)造器禁止反射調(diào)用");}}public synchronized static LazySingleton getInstance(){ // public static LazySingleton getInstance(){ // synchronized (LazySingleton.class) {if(lazySingleton == null){/*** 先調(diào)用之后這個對象就會new出來* 后調(diào)用的反射的時候* 也就走到異常里面了* 那run一下看一下現(xiàn)在的效果* 效果和哦我們預(yù)期的是一致的* */lazySingleton = new LazySingleton();} // }return lazySingleton;}// public static void main(String[] args) throws Exception { // Class objectClass = LazySingleton.class; // Constructor c = objectClass.getDeclaredConstructor(); // c.setAccessible(true); // // LazySingleton o1 = LazySingleton.getInstance(); // System.out.println("Program end....."); // // Field flag = o1.getClass().getDeclaredField("flag"); // flag.setAccessible(true); // flag.set(o1,true); // // // LazySingleton o2 = (LazySingleton) c.newInstance(); // // System.out.println(o1); // System.out.println(o2); // System.out.println(o1==o2); // }} package com.learn.design.pattern.creational.singleton;import java.io.IOException; import java.lang.reflect.Constructor; import java.lang.reflect.InvocationTargetException;public class Test {public static void main(String[] args) throws IOException, ClassNotFoundException, NoSuchMethodException, IllegalAccessException, InvocationTargetException, InstantiationException { // LazySingleton lazySingleton = LazySingleton.getInstance();// System.out.println("main thread"+ThreadLocalInstance.getInstance()); // System.out.println("main thread"+ThreadLocalInstance.getInstance()); // System.out.println("main thread"+ThreadLocalInstance.getInstance()); // System.out.println("main thread"+ThreadLocalInstance.getInstance()); // System.out.println("main thread"+ThreadLocalInstance.getInstance()); // System.out.println("main thread"+ThreadLocalInstance.getInstance());// Thread t1 = new Thread(new T()); // Thread t2 = new Thread(new T()); // t1.start(); // t2.start(); // System.out.println("program end");// HungrySingleton instance = HungrySingleton.getInstance(); // EnumInstance instance = EnumInstance.getInstance(); // instance.setData(new Object()); // // ObjectOutputStream oos = new ObjectOutputStream(new FileOutputStream("singleton_file")); // oos.writeObject(instance); // // File file = new File("singleton_file"); // ObjectInputStream ois = new ObjectInputStream(new FileInputStream(file)); // // HungrySingleton newInstance = (HungrySingleton) ois.readObject(); // EnumInstance newInstance = (EnumInstance) ois.readObject(); // // System.out.println(instance.getData()); // System.out.println(newInstance.getData()); // System.out.println(instance.getData() == newInstance.getData());// Class objectClass = HungrySingleton.class; // Class objectClass = StaticInnerClassSingleton.class;Class objectClass = LazySingleton.class; // Class objectClass = EnumInstance.class;Constructor constructor = objectClass.getDeclaredConstructor(); // Constructor constructor = objectClass.getDeclaredConstructor(String.class,int.class); //constructor.setAccessible(true); // EnumInstance instance = (EnumInstance) constructor.newInstance("Geely",666);//LazySingleton newInstance = (LazySingleton) constructor.newInstance();/*** 直接用LazySingleton去getInstance* 因為執(zhí)行到這里的時候單例已經(jīng)存在了* 所以這里拋出異常* 但是如果我們把這個順序調(diào)換一下* 我們再run一下* 可以看到結(jié)果* 完全不一樣* 兩個不同的對象都出來了* 并且是false* 但是如果多線程的情況下* 例如反射一個線程* 獲取單例一個線程* 那這個就不一定了* 看CPU的分配* 如果是反射的線程* 先實例化對象的話* 那這個單例對象可就不止一個對象了* 那有的人會說* 那像剛才餓漢式* 內(nèi)部類調(diào)換順序* 會不會也這樣呢* 但是不會的* 因為這兩個是在類加載的時候就把對象初始化好了* 那我們也簡單測試一下* * */LazySingleton instance = LazySingleton.getInstance();// StaticInnerClassSingleton instance = StaticInnerClassSingleton.getInstance(); // StaticInnerClassSingleton newInstance = (StaticInnerClassSingleton) constructor.newInstance();// HungrySingleton newInstance = (HungrySingleton) constructor.newInstance(); // HungrySingleton instance = HungrySingleton.getInstance();System.out.println(instance);System.out.println(newInstance);/*** 結(jié)果也能夠猜到不是同一個對象* * */System.out.println(instance == newInstance);// EnumInstance instance = EnumInstance.getInstance(); // instance.printTest();} } package com.learn.design.pattern.creational.singleton;import java.io.IOException; import java.lang.reflect.Constructor; import java.lang.reflect.InvocationTargetException;public class Test {public static void main(String[] args) throws IOException, ClassNotFoundException, NoSuchMethodException, IllegalAccessException, InvocationTargetException, InstantiationException { // LazySingleton lazySingleton = LazySingleton.getInstance();// System.out.println("main thread"+ThreadLocalInstance.getInstance()); // System.out.println("main thread"+ThreadLocalInstance.getInstance()); // System.out.println("main thread"+ThreadLocalInstance.getInstance()); // System.out.println("main thread"+ThreadLocalInstance.getInstance()); // System.out.println("main thread"+ThreadLocalInstance.getInstance()); // System.out.println("main thread"+ThreadLocalInstance.getInstance());// Thread t1 = new Thread(new T()); // Thread t2 = new Thread(new T()); // t1.start(); // t2.start(); // System.out.println("program end");// HungrySingleton instance = HungrySingleton.getInstance(); // EnumInstance instance = EnumInstance.getInstance(); // instance.setData(new Object()); // // ObjectOutputStream oos = new ObjectOutputStream(new FileOutputStream("singleton_file")); // oos.writeObject(instance); // // File file = new File("singleton_file"); // ObjectInputStream ois = new ObjectInputStream(new FileInputStream(file)); // // HungrySingleton newInstance = (HungrySingleton) ois.readObject(); // EnumInstance newInstance = (EnumInstance) ois.readObject(); // // System.out.println(instance.getData()); // System.out.println(newInstance.getData()); // System.out.println(instance.getData() == newInstance.getData());Class objectClass = HungrySingleton.class; // Class objectClass = StaticInnerClassSingleton.class;// Class objectClass = LazySingleton.class; // Class objectClass = EnumInstance.class;Constructor constructor = objectClass.getDeclaredConstructor(); // Constructor constructor = objectClass.getDeclaredConstructor(String.class,int.class); //constructor.setAccessible(true); // EnumInstance instance = (EnumInstance) constructor.newInstance("Geely",666);// // LazySingleton newInstance = (LazySingleton) constructor.newInstance(); // LazySingleton instance = LazySingleton.getInstance();// StaticInnerClassSingleton instance = StaticInnerClassSingleton.getInstance(); // StaticInnerClassSingleton newInstance = (StaticInnerClassSingleton) constructor.newInstance();/*** 先反射再獲取實例* 結(jié)果是一樣的* 他的原因前面也強調(diào)了* 因為在類加載的時候就把類初始化好了* 接著回到懶漢式來說* * */HungrySingleton newInstance = (HungrySingleton) constructor.newInstance();HungrySingleton instance = HungrySingleton.getInstance();System.out.println(instance);System.out.println(newInstance);System.out.println(instance == newInstance);// EnumInstance instance = EnumInstance.getInstance(); // instance.printTest();} } package com.learn.design.pattern.creational.singleton;import java.lang.reflect.Constructor; import java.lang.reflect.Field;/*** 我們在這里面取巧* 比如說* 我們聲明一個flag等于0* 調(diào)用構(gòu)造器就賦值flag等于1* 是一個非常復(fù)雜的邏輯* 例如flag的各種變化* 甚至取反* 加上各種邏輯判斷* 無論增加多么復(fù)雜的邏輯* 看上去是可以的* 但是不要忘了我們反射的威力* 反射既然可以拿構(gòu)造器的權(quán)限* 他很自然也可以修改flag* 所以這里創(chuàng)建計數(shù)或者信號量* 沒有什么意義* 那結(jié)論就是對于這種Lazy的情況* 反射就是無法避免的* 這個我們刪除* 因為一旦多線程就和順序有關(guān)* 反射先進(jìn)來那就拿兩個對象* 希望能理解這一塊* 因為一旦多線程就和順序有關(guān)* 反射如果先進(jìn)來* 那就拿兩個對象了* 那這里我們就較個真* 這里是后來補的* 因為我覺得有必要寫一遍反射* 首先我們寫一個flag* 直接用布爾類型* 比較簡單* * * * @author Leon.Sun**/ public class LazySingleton {private static LazySingleton lazySingleton = null;/*** 默認(rèn)值就是true* 如果為true這個類還可以進(jìn)行實例* 否則就拋出異常* 那很簡單* 我們在構(gòu)造器里面進(jìn)行一個判斷* * 現(xiàn)在這個字段并沒有g(shù)et/set方法* 也就是get/set方法在這里并沒有什么意義* 反射還是能修改它* 所以就不寫了* * * */private static boolean flag = true;private LazySingleton(){/*** 首先這個flag是為true的* * 第二次通過反射* 這個時候flag是false* 所以if進(jìn)不來* 直接進(jìn)入到拋出異常里* F8通過* 沒有任何問題* 阻止了反射攻擊* * 又會來到LazySingleton的構(gòu)造器* 咱們來看一下* flag為true* flag這個值在內(nèi)存中已經(jīng)為true了* 所以這個構(gòu)造器又調(diào)用成功* F8通過* 結(jié)果我就打出來了* 聲明了兩個對象* 并且他們不是同一個對象* 那有的人會說* 我改成final呢* final肯定不行啊* 如果是final的話* flag = false;* 這里是不能夠重新賦值的* 也就是說這個開關(guān)失效了* 所以說這里邏輯無論多復(fù)雜* 反射的時候* 直接修改這個值還是抵擋不住的* 所以有人認(rèn)為單例模式是最簡單的模式* 那看上去的確是最簡單的* 但是如果說復(fù)雜呢* 他也是最復(fù)雜的* 這一塊的知識點還是比較值得研究的* 那接下來我們還有一種單例的模式* 這種單例模式呢* 既能防御反射* 又能保證不被序列化破壞* 那他的原理是什么呢* * * */if(flag) {/*** 我們賦值成false* 那如果flag為true的話* 就把它賦值成false* * if進(jìn)來了把它賦值為false* 注意這里的變化* * 第一次為true* 把它置為false* 沒有任何問題* 直接通過F8通過* * */flag = false;}else {/*** else里面就不用判斷了* 就是這樣的* 所以當(dāng)?shù)谝淮握{(diào)用構(gòu)造器的時候* 走到if里面是OK的* 第二次他被置成false* 我們期望是走到else里邊* * * */throw new RuntimeException("單例構(gòu)造器禁止反射調(diào)用");} // if(lazySingleton != null){ // throw new RuntimeException("單例構(gòu)造器禁止反射調(diào)用"); // }}public synchronized static LazySingleton getInstance(){ // public static LazySingleton getInstance(){ // synchronized (LazySingleton.class) {if(lazySingleton == null){lazySingleton = new LazySingleton();} // }return lazySingleton;}/*** 那我們就在這類里邊寫一個主函數(shù)* 測試反射攻擊* 前面剛剛說用int* 我們這里采用簡單的布爾* 那我們所要說的關(guān)鍵點* 不在于flag是什么類型* 而是在于反射能不能搞定* * * @param args* @throws Exception*/public static void main(String[] args) throws Exception {Class objectClass = LazySingleton.class;/*** 獲取他的構(gòu)造器* 第一個反射包下的* 調(diào)用getDeclaredConstructor這個方法* 然后把異常拋出* * */Constructor c = objectClass.getDeclaredConstructor();/*** 設(shè)置他的權(quán)限為true* 也就是這一條修改了構(gòu)造器的權(quán)限* * */c.setAccessible(true);/*** 現(xiàn)在獲取這個對象* 正常的獲取這個對象LazySingleton.getInstance()* * */LazySingleton o1 = LazySingleton.getInstance();System.out.println("Program end.....");/*** 現(xiàn)在我們要破壞他* 我們在反射生成o2之前* 把這個字段改掉* 怎么改呢* 非常簡單* Field也是反射包下的* 我們從o1里面獲得class* 然后獲取聲明的字段* 這個字段就是flag* 這里面應(yīng)該有異常* 我們拋出一下* 那這個字段我們就獲取到了* 因為這個字段是private的* * 現(xiàn)在到這里邊* 我們看一下flag這個對象* 我們可以看到他是private的static的boolean類型的* * */Field flag = o1.getClass().getDeclaredField("flag");/*** 我們把它權(quán)限打開* * 現(xiàn)在設(shè)置他的權(quán)限為true* * */flag.setAccessible(true);/*** set方法就是要把某個對象的值設(shè)置成什么* 是哪個對象呢* o1這個對象* value是什么呢* 因為字段本身已經(jīng)是flag了* 把它呢置成true* 那我們看一下* LazySingleton o2 = (LazySingleton) c.newInstance();* 這個能不能成功呢* 我們先run一下* 看一下結(jié)果* 結(jié)果已經(jīng)出來了* 可以看到反射攻擊成功* 也就是說我們通過這個反射* 已經(jīng)把private的flag* 已經(jīng)置成true了* 所以剛剛我也說* 無論這里邊加多復(fù)雜的邏輯* 無論是布爾還是int* 都是沒有用的* 因為我們通過反射可以任意修改* 接著往下看* 所以我們在這里debug一下* 一起來學(xué)習(xí)一下反射* 斷點過來了* 第一次為true* 把它置為false* * 這個時候把他的值修改掉* o1是什么呢* o1就是這個對象* 也就是上面的getInstance對象* 這個對象是靜態(tài)的* 所以我修改這個對象的靜態(tài)屬性* 就直接修改掉了* 我們F6單步* 那現(xiàn)在我們再F8一下* * */flag.set(o1,true);/*** 然后我們獲取一個o2* 通過反射* c.newInstance()* 然后進(jìn)行強轉(zhuǎn)* 先拋出異常然后強轉(zhuǎn)* * */LazySingleton o2 = (LazySingleton) c.newInstance();/*** 輸出o1和o2* */System.out.println(o1);System.out.println(o2);/*** 然后輸出他們想不相等* 結(jié)果出來了* 和我們預(yù)期的是一樣的* 單例構(gòu)造器禁止調(diào)用* 也就是我們加的flag已經(jīng)生效了* 沒有問題* 現(xiàn)在是好使的* 那我們打一個斷點來看一下* 我們再debug看一下* * */System.out.println(o1==o2);} }
package com.learn.design.pattern.creational.singleton;/*** * @author Leon.Sun**/ public class LazySingleton {/*** 首先他在類加載的時候沒有生成* 只有調(diào)用getInstance方法的時候* 才會生成這個對象* * */private static LazySingleton lazySingleton = null;/*** */private LazySingleton(){/*** 如果把這段代碼加載這里邊* 這個就和我們創(chuàng)建實例的順序有關(guān)了* 為什么這么說呢* 回到Test里邊* 我們是先調(diào)用的getInstance* 而且這個方法是同步方法* * */if(lazySingleton != null){throw new RuntimeException("單例構(gòu)造器禁止反射調(diào)用");}}public synchronized static LazySingleton getInstance(){ // public static LazySingleton getInstance(){ // synchronized (LazySingleton.class) {if(lazySingleton == null){/*** 先調(diào)用之后這個對象就會new出來* 后調(diào)用的反射的時候* 也就走到異常里面了* 那run一下看一下現(xiàn)在的效果* 效果和哦我們預(yù)期的是一致的* */lazySingleton = new LazySingleton();} // }return lazySingleton;}// public static void main(String[] args) throws Exception { // Class objectClass = LazySingleton.class; // Constructor c = objectClass.getDeclaredConstructor(); // c.setAccessible(true); // // LazySingleton o1 = LazySingleton.getInstance(); // System.out.println("Program end....."); // // Field flag = o1.getClass().getDeclaredField("flag"); // flag.setAccessible(true); // flag.set(o1,true); // // // LazySingleton o2 = (LazySingleton) c.newInstance(); // // System.out.println(o1); // System.out.println(o2); // System.out.println(o1==o2); // }} package com.learn.design.pattern.creational.singleton;import java.io.IOException; import java.lang.reflect.Constructor; import java.lang.reflect.InvocationTargetException;public class Test {public static void main(String[] args) throws IOException, ClassNotFoundException, NoSuchMethodException, IllegalAccessException, InvocationTargetException, InstantiationException { // LazySingleton lazySingleton = LazySingleton.getInstance();// System.out.println("main thread"+ThreadLocalInstance.getInstance()); // System.out.println("main thread"+ThreadLocalInstance.getInstance()); // System.out.println("main thread"+ThreadLocalInstance.getInstance()); // System.out.println("main thread"+ThreadLocalInstance.getInstance()); // System.out.println("main thread"+ThreadLocalInstance.getInstance()); // System.out.println("main thread"+ThreadLocalInstance.getInstance());// Thread t1 = new Thread(new T()); // Thread t2 = new Thread(new T()); // t1.start(); // t2.start(); // System.out.println("program end");// HungrySingleton instance = HungrySingleton.getInstance(); // EnumInstance instance = EnumInstance.getInstance(); // instance.setData(new Object()); // // ObjectOutputStream oos = new ObjectOutputStream(new FileOutputStream("singleton_file")); // oos.writeObject(instance); // // File file = new File("singleton_file"); // ObjectInputStream ois = new ObjectInputStream(new FileInputStream(file)); // // HungrySingleton newInstance = (HungrySingleton) ois.readObject(); // EnumInstance newInstance = (EnumInstance) ois.readObject(); // // System.out.println(instance.getData()); // System.out.println(newInstance.getData()); // System.out.println(instance.getData() == newInstance.getData());// Class objectClass = HungrySingleton.class; // Class objectClass = StaticInnerClassSingleton.class;Class objectClass = LazySingleton.class; // Class objectClass = EnumInstance.class;Constructor constructor = objectClass.getDeclaredConstructor(); // Constructor constructor = objectClass.getDeclaredConstructor(String.class,int.class); //constructor.setAccessible(true); // EnumInstance instance = (EnumInstance) constructor.newInstance("Geely",666);//LazySingleton newInstance = (LazySingleton) constructor.newInstance();/*** 直接用LazySingleton去getInstance* 因為執(zhí)行到這里的時候單例已經(jīng)存在了* 所以這里拋出異常* 但是如果我們把這個順序調(diào)換一下* 我們再run一下* 可以看到結(jié)果* 完全不一樣* 兩個不同的對象都出來了* 并且是false* 但是如果多線程的情況下* 例如反射一個線程* 獲取單例一個線程* 那這個就不一定了* 看CPU的分配* 如果是反射的線程* 先實例化對象的話* 那這個單例對象可就不止一個對象了* 那有的人會說* 那像剛才餓漢式* 內(nèi)部類調(diào)換順序* 會不會也這樣呢* 但是不會的* 因為這兩個是在類加載的時候就把對象初始化好了* 那我們也簡單測試一下* * */LazySingleton instance = LazySingleton.getInstance();// StaticInnerClassSingleton instance = StaticInnerClassSingleton.getInstance(); // StaticInnerClassSingleton newInstance = (StaticInnerClassSingleton) constructor.newInstance();// HungrySingleton newInstance = (HungrySingleton) constructor.newInstance(); // HungrySingleton instance = HungrySingleton.getInstance();System.out.println(instance);System.out.println(newInstance);/*** 結(jié)果也能夠猜到不是同一個對象* * */System.out.println(instance == newInstance);// EnumInstance instance = EnumInstance.getInstance(); // instance.printTest();} } package com.learn.design.pattern.creational.singleton;import java.io.IOException; import java.lang.reflect.Constructor; import java.lang.reflect.InvocationTargetException;public class Test {public static void main(String[] args) throws IOException, ClassNotFoundException, NoSuchMethodException, IllegalAccessException, InvocationTargetException, InstantiationException { // LazySingleton lazySingleton = LazySingleton.getInstance();// System.out.println("main thread"+ThreadLocalInstance.getInstance()); // System.out.println("main thread"+ThreadLocalInstance.getInstance()); // System.out.println("main thread"+ThreadLocalInstance.getInstance()); // System.out.println("main thread"+ThreadLocalInstance.getInstance()); // System.out.println("main thread"+ThreadLocalInstance.getInstance()); // System.out.println("main thread"+ThreadLocalInstance.getInstance());// Thread t1 = new Thread(new T()); // Thread t2 = new Thread(new T()); // t1.start(); // t2.start(); // System.out.println("program end");// HungrySingleton instance = HungrySingleton.getInstance(); // EnumInstance instance = EnumInstance.getInstance(); // instance.setData(new Object()); // // ObjectOutputStream oos = new ObjectOutputStream(new FileOutputStream("singleton_file")); // oos.writeObject(instance); // // File file = new File("singleton_file"); // ObjectInputStream ois = new ObjectInputStream(new FileInputStream(file)); // // HungrySingleton newInstance = (HungrySingleton) ois.readObject(); // EnumInstance newInstance = (EnumInstance) ois.readObject(); // // System.out.println(instance.getData()); // System.out.println(newInstance.getData()); // System.out.println(instance.getData() == newInstance.getData());Class objectClass = HungrySingleton.class; // Class objectClass = StaticInnerClassSingleton.class;// Class objectClass = LazySingleton.class; // Class objectClass = EnumInstance.class;Constructor constructor = objectClass.getDeclaredConstructor(); // Constructor constructor = objectClass.getDeclaredConstructor(String.class,int.class); //constructor.setAccessible(true); // EnumInstance instance = (EnumInstance) constructor.newInstance("Geely",666);// // LazySingleton newInstance = (LazySingleton) constructor.newInstance(); // LazySingleton instance = LazySingleton.getInstance();// StaticInnerClassSingleton instance = StaticInnerClassSingleton.getInstance(); // StaticInnerClassSingleton newInstance = (StaticInnerClassSingleton) constructor.newInstance();/*** 先反射再獲取實例* 結(jié)果是一樣的* 他的原因前面也強調(diào)了* 因為在類加載的時候就把類初始化好了* 接著回到懶漢式來說* * */HungrySingleton newInstance = (HungrySingleton) constructor.newInstance();HungrySingleton instance = HungrySingleton.getInstance();System.out.println(instance);System.out.println(newInstance);System.out.println(instance == newInstance);// EnumInstance instance = EnumInstance.getInstance(); // instance.printTest();} } package com.learn.design.pattern.creational.singleton;import java.lang.reflect.Constructor; import java.lang.reflect.Field;/*** 我們在這里面取巧* 比如說* 我們聲明一個flag等于0* 調(diào)用構(gòu)造器就賦值flag等于1* 是一個非常復(fù)雜的邏輯* 例如flag的各種變化* 甚至取反* 加上各種邏輯判斷* 無論增加多么復(fù)雜的邏輯* 看上去是可以的* 但是不要忘了我們反射的威力* 反射既然可以拿構(gòu)造器的權(quán)限* 他很自然也可以修改flag* 所以這里創(chuàng)建計數(shù)或者信號量* 沒有什么意義* 那結(jié)論就是對于這種Lazy的情況* 反射就是無法避免的* 這個我們刪除* 因為一旦多線程就和順序有關(guān)* 反射先進(jìn)來那就拿兩個對象* 希望能理解這一塊* 因為一旦多線程就和順序有關(guān)* 反射如果先進(jìn)來* 那就拿兩個對象了* 那這里我們就較個真* 這里是后來補的* 因為我覺得有必要寫一遍反射* 首先我們寫一個flag* 直接用布爾類型* 比較簡單* * * * @author Leon.Sun**/ public class LazySingleton {private static LazySingleton lazySingleton = null;/*** 默認(rèn)值就是true* 如果為true這個類還可以進(jìn)行實例* 否則就拋出異常* 那很簡單* 我們在構(gòu)造器里面進(jìn)行一個判斷* * 現(xiàn)在這個字段并沒有g(shù)et/set方法* 也就是get/set方法在這里并沒有什么意義* 反射還是能修改它* 所以就不寫了* * * */private static boolean flag = true;private LazySingleton(){/*** 首先這個flag是為true的* * 第二次通過反射* 這個時候flag是false* 所以if進(jìn)不來* 直接進(jìn)入到拋出異常里* F8通過* 沒有任何問題* 阻止了反射攻擊* * 又會來到LazySingleton的構(gòu)造器* 咱們來看一下* flag為true* flag這個值在內(nèi)存中已經(jīng)為true了* 所以這個構(gòu)造器又調(diào)用成功* F8通過* 結(jié)果我就打出來了* 聲明了兩個對象* 并且他們不是同一個對象* 那有的人會說* 我改成final呢* final肯定不行啊* 如果是final的話* flag = false;* 這里是不能夠重新賦值的* 也就是說這個開關(guān)失效了* 所以說這里邏輯無論多復(fù)雜* 反射的時候* 直接修改這個值還是抵擋不住的* 所以有人認(rèn)為單例模式是最簡單的模式* 那看上去的確是最簡單的* 但是如果說復(fù)雜呢* 他也是最復(fù)雜的* 這一塊的知識點還是比較值得研究的* 那接下來我們還有一種單例的模式* 這種單例模式呢* 既能防御反射* 又能保證不被序列化破壞* 那他的原理是什么呢* * * */if(flag) {/*** 我們賦值成false* 那如果flag為true的話* 就把它賦值成false* * if進(jìn)來了把它賦值為false* 注意這里的變化* * 第一次為true* 把它置為false* 沒有任何問題* 直接通過F8通過* * */flag = false;}else {/*** else里面就不用判斷了* 就是這樣的* 所以當(dāng)?shù)谝淮握{(diào)用構(gòu)造器的時候* 走到if里面是OK的* 第二次他被置成false* 我們期望是走到else里邊* * * */throw new RuntimeException("單例構(gòu)造器禁止反射調(diào)用");} // if(lazySingleton != null){ // throw new RuntimeException("單例構(gòu)造器禁止反射調(diào)用"); // }}public synchronized static LazySingleton getInstance(){ // public static LazySingleton getInstance(){ // synchronized (LazySingleton.class) {if(lazySingleton == null){lazySingleton = new LazySingleton();} // }return lazySingleton;}/*** 那我們就在這類里邊寫一個主函數(shù)* 測試反射攻擊* 前面剛剛說用int* 我們這里采用簡單的布爾* 那我們所要說的關(guān)鍵點* 不在于flag是什么類型* 而是在于反射能不能搞定* * * @param args* @throws Exception*/public static void main(String[] args) throws Exception {Class objectClass = LazySingleton.class;/*** 獲取他的構(gòu)造器* 第一個反射包下的* 調(diào)用getDeclaredConstructor這個方法* 然后把異常拋出* * */Constructor c = objectClass.getDeclaredConstructor();/*** 設(shè)置他的權(quán)限為true* 也就是這一條修改了構(gòu)造器的權(quán)限* * */c.setAccessible(true);/*** 現(xiàn)在獲取這個對象* 正常的獲取這個對象LazySingleton.getInstance()* * */LazySingleton o1 = LazySingleton.getInstance();System.out.println("Program end.....");/*** 現(xiàn)在我們要破壞他* 我們在反射生成o2之前* 把這個字段改掉* 怎么改呢* 非常簡單* Field也是反射包下的* 我們從o1里面獲得class* 然后獲取聲明的字段* 這個字段就是flag* 這里面應(yīng)該有異常* 我們拋出一下* 那這個字段我們就獲取到了* 因為這個字段是private的* * 現(xiàn)在到這里邊* 我們看一下flag這個對象* 我們可以看到他是private的static的boolean類型的* * */Field flag = o1.getClass().getDeclaredField("flag");/*** 我們把它權(quán)限打開* * 現(xiàn)在設(shè)置他的權(quán)限為true* * */flag.setAccessible(true);/*** set方法就是要把某個對象的值設(shè)置成什么* 是哪個對象呢* o1這個對象* value是什么呢* 因為字段本身已經(jīng)是flag了* 把它呢置成true* 那我們看一下* LazySingleton o2 = (LazySingleton) c.newInstance();* 這個能不能成功呢* 我們先run一下* 看一下結(jié)果* 結(jié)果已經(jīng)出來了* 可以看到反射攻擊成功* 也就是說我們通過這個反射* 已經(jīng)把private的flag* 已經(jīng)置成true了* 所以剛剛我也說* 無論這里邊加多復(fù)雜的邏輯* 無論是布爾還是int* 都是沒有用的* 因為我們通過反射可以任意修改* 接著往下看* 所以我們在這里debug一下* 一起來學(xué)習(xí)一下反射* 斷點過來了* 第一次為true* 把它置為false* * 這個時候把他的值修改掉* o1是什么呢* o1就是這個對象* 也就是上面的getInstance對象* 這個對象是靜態(tài)的* 所以我修改這個對象的靜態(tài)屬性* 就直接修改掉了* 我們F6單步* 那現(xiàn)在我們再F8一下* * */flag.set(o1,true);/*** 然后我們獲取一個o2* 通過反射* c.newInstance()* 然后進(jìn)行強轉(zhuǎn)* 先拋出異常然后強轉(zhuǎn)* * */LazySingleton o2 = (LazySingleton) c.newInstance();/*** 輸出o1和o2* */System.out.println(o1);System.out.println(o2);/*** 然后輸出他們想不相等* 結(jié)果出來了* 和我們預(yù)期的是一樣的* 單例構(gòu)造器禁止調(diào)用* 也就是我們加的flag已經(jīng)生效了* 沒有問題* 現(xiàn)在是好使的* 那我們打一個斷點來看一下* 我們再debug看一下* * */System.out.println(o1==o2);} }
?
超強干貨來襲 云風(fēng)專訪:近40年碼齡,通宵達(dá)旦的技術(shù)人生總結(jié)
以上是生活随笔為你收集整理的单例设计模式-反射攻击解决方案及原理分析的全部內(nèi)容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: 单例设计模式-序列化破坏单例模式原理解析
- 下一篇: 单例设计模式-Enum枚举单例、原理源码