1. 程式人生 > >如何防止單例模式被 JAVA 反射攻擊

如何防止單例模式被 JAVA 反射攻擊

如何防止單例模式被 JAVA 反射攻擊

package sf.com.singleton;

public class Demo {
    
    private static boolean flag = true;
    
    private  Demo() {
        System.out.println("flag==" + flag);
    }
    
    private static class SingletonHolder{
        private static final Demo INSTANCE = new Demo();
    }
    
    public static Demo getInstance(){
        return SingletonHolder.INSTANCE;
    }
    
    public void deSomethingElse(){
        
    }
}



package sf.com.singleton;

import java.lang.reflect.Constructor;

public class DemoRelectAttack {

    public static void main(String[] args){
        try {
            Class<Demo> classType = Demo.class;
            Constructor<Demo> constructor = classType.getDeclaredConstructor(null);
            //取消java的許可權控制檢查
            constructor.setAccessible(true);
            //下面兩個都可以訪問私有構造器
            Demo demo1 = (Demo) constructor.newInstance(); 
            Demo demo2 = Demo.getInstance();
            System.out.println(demo1 == demo2);
        } catch (Exception e) {
            e.printStackTrace();
        }
        
    }
}

執行結果:

可以看到,通過反射獲取建構函式,然後呼叫setAccessible(true)就可以呼叫私有的建構函式,所有e1和e2是兩個不同的物件。

 

如果要抵禦這種攻擊,可以修改構造器,讓它在被要求建立第二個例項的時候丟擲異常。

修改後的程式碼:

package sf.com.singleton;

public class DemoModify {
    
    private static boolean flag = true;
    
    private  DemoModify() {
        synchronized (DemoModify.class) {
            if (flag == false) {
                flag = !flag;
            }else{
                throw new RuntimeException("單例模式被侵犯!");
            }
        }
    }
    
    private static class SingletonHolder{
        private static final DemoModify INSTANCE = new DemoModify();
    }
    
    public static DemoModify getInstance(){
        return SingletonHolder.INSTANCE;
    }
    
    public void deSomethingElse(){
        
    }
}

package sf.com.singleton;

import java.lang.reflect.Constructor;

public class DemoRelectAttack {

    public static void main(String[] args){
        try {
            Class<DemoModify> classType = DemoModify.class;
            Constructor<DemoModify> constructor = classType.getDeclaredConstructor(null);
            //取消java的許可權控制檢查
            constructor.setAccessible(true);
            //下面兩個都可以訪問私有構造器
            DemoModify demo1 = (DemoModify) constructor.newInstance(); 
            DemoModify demo2 = DemoModify.getInstance();
            System.out.println(demo1 == demo2);
        } catch (Exception e) {
            e.printStackTrace();
        }
        
    }
}
 

執行結果如下:

可以看到,成功的阻止了單例模式被破壞。

 從JDK1.5開始,實現Singleton還有新的寫法,只需編寫一個包含單個元素的列舉型別。推薦寫法:

package sf.com.singleton;

public enum SingletonClass {
    
    INSTANCE;
    
    public void test(){
        System.out.println("The Test!");
    }
}


package sf.com.singleton;

import java.lang.reflect.Constructor;
import java.lang.reflect.InvocationTargetException;

public class TestMain {

    public static void main(String[] args) throws NoSuchMethodException, SecurityException, InstantiationException, IllegalAccessException, IllegalArgumentException, InvocationTargetException {
        Class<SingletonClass> classType = SingletonClass.class;
        Constructor<SingletonClass> constructor =(Constructor<SingletonClass>)  classType.getDeclaredConstructor();
        constructor.setAccessible(true);
        constructor.newInstance();
    }
}

 

執行結果如下:

 
 

由此可見這種寫法也可以防止單例模式被“攻擊”。

2、第二種方法簡單直接

  private Singleton() {
        if(singleton!=null){
            throw new RuntimeException();
        }
    }

public class Demo {
    public static void main(String[] args) throws Exception {
        Singleton singletonOne = Singleton.getSingleton();
        Singleton singletonTwo = Singleton.getSingleton();
        System.out.println("One和Two是否是同一個例項? " + (singletonOne == singletonTwo));
        //利用反射來破壞單例模式
        Class clazz = Class.forName("com.jiagouedu.Singleton");
        Constructor constructor = clazz.getDeclaredConstructor(null);
        constructor.setAccessible(true);
        Singleton singletonThree = (Singleton) constructor.newInstance(null);
        System.out.println("One和three是否是同一個例項? "+(singletonOne==singletonThree));
    }
}
//雙重檢測鎖模式
class Singleton {
    private Singleton() {
        if(singleton!=null){
            throw new RuntimeException();
        }
    }

    static Singleton singleton;

    public static Singleton getSingleton() {
        if (singleton == null) {
            synchronized (Singleton.class) {
                if (singleton == null) {
                    singleton = new Singleton();
                }
            }
        }
        return singleton;
    }
}