單例模式安全之反射攻擊

單例模式安全之反射攻擊

源碼

單例模式這裏就不談了,什麼是單例模式可參考七種Java單例模式詳解,這裏是關於單例模式安全方面的,當然了這裏說的安全不是線程安全。

什麼是反射攻擊呢

  1. 在Java中,由於反射的功能實在是太強了,通過動態訪問類並設置Access使得可以訪問對象的私有屬性方法等。
  2. 在單例模式中,我們使用private 修飾構造方法對外隱藏,防止外部new 對象,但是在反射的存在下,private的存在形同虛設,通過反射設置Access即可訪問構造方法,這時的單例就不是單例了。

反射攻擊重現

這裏使用volatile 雙重檢驗鎖實現線程安全的單例

package com.fine.reflect;

/**
 * 單例
 * volatile 雙重校驗
 *
 * @author finefine at: 2019-05-02 22:22
 */
public class Singleton {
    private volatile static Singleton INSTANCE;

    private Singleton() {

    }

    public static Singleton getInstance() {

        if (INSTANCE==null){

            //同步代碼塊
            synchronized (Singleton.class){
                if (INSTANCE == null) {
                    INSTANCE = new Singleton();
                }
            }

        }
        return INSTANCE;
    }

}

反射攻擊實現

package com.fine.reflect;

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

/**
 * @author finefine at: 2019-05-02 22:26
 */
public class ReflectAttackTest {

    public static void main(String[] args) {

        Singleton singleton = Singleton.getInstance();

        Class clazz = singleton.getClass();
        try {
            Constructor<Singleton> constructor = clazz.getDeclaredConstructors()[0];

            //設置允許訪問私有的構造器
            constructor.setAccessible(true);
            Singleton singleton1 = constructor.newInstance();

            if (singleton1 != null&&singleton1.getClass().equals(singleton.getClass())) {
                System.out.println("通過反射構造除了對象");
                return;
            }

            if (singleton == singleton1) {
                System.out.println("是同一個對象");
            } else {
                System.out.println("是兩個不同的對象");
            }
        } catch (IllegalAccessException e) {
            e.printStackTrace();
        } catch (InstantiationException e) {
            e.printStackTrace();
        } catch (InvocationTargetException e) {
            e.printStackTrace();
        }

    }
}

運行結果

debug 分析

可以看到singleton 和 singleton1 都是Singleton 的對象,一個hashcode是467一個是469。constructor=private com.fine.reflect.Singleton() 說明了的確是Singleton的私有構造方法。

通過反射,我們創造出了不止一個對象,因此該單例模式配破壞了,是不安全的,這就是反射攻擊。

預防反射攻擊

要避免通過反射來調用私有構造器這是行不通的,那麼該如何做呢,這裏有兩種做法。

  • 當嘗試使用構造方法new 對象時,直接拋出異常
  • 使用枚舉,枚舉類jvm底層保證了不可new。

第一種

單例寫法:

package com.fine.reflect.enhance;

/**
 * 單例
 * volatile 雙重校驗
 *
 * @author finefine at: 2019-05-02 22:22
 */
public class Singleton {
    private volatile static Singleton INSTANCE;

    private Singleton() {
        //如果已存在,直接拋出異常,保證只會被new 一次
        if (INSTANCE != null) {
            throw new RuntimeException("對象已存在不可重複創建");
        }
    }

    public static Singleton getInstance() {

        if (INSTANCE==null){

            //同步代碼塊
            synchronized (Singleton.class){
                if (INSTANCE == null) {
                    INSTANCE = new Singleton();
                }
            }

        }
        return INSTANCE;
    }

}

測試代碼:

package com.fine.reflect;

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

import com.fine.reflect.enhance.Singleton;

/**
 * @author finefine at: 2019-05-02 22:26
 */
public class ReflectAttackTest {

    public static void main(String[] args) {

        Singleton singleton = Singleton.getInstance();


        Class clazz = singleton.getClass();
        try {
            Constructor<Singleton> constructor = clazz.getDeclaredConstructors()[0];

            //設置允許訪問私有的構造器
            constructor.setAccessible(true);
            Singleton singleton1 = constructor.newInstance();

            if (singleton1 != null && singleton1.getClass().equals(singleton.getClass())) {
                System.out.println("通過反射構造除了對象");
            } else {
                return;
            }

            if (singleton == singleton1) {
                System.out.println("是同一個對象");
            } else {
                System.out.println("是兩個不同的對象");
            }
        } catch (IllegalAccessException e) {
            e.printStackTrace();
        } catch (InstantiationException e) {
            e.printStackTrace();
        } catch (InvocationTargetException e) {
            e.printStackTrace();
        }

    }
}

運行結果:

直接拋出了異常。

第二種

單例代碼:

package com.fine.reflect.enhance;

/**
 * 單例
 * 枚舉
 *
 * @author finefine at: 2019-05-02 22:22
 */
public enum  SingletonEnum {
    INSTANCE;
}

測試代碼:

package com.fine.reflect.enhance;

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

/**
 * @author finefine at: 2019-05-03 00:50
 */
public class SingletonEnumTest {

    public static void main(String[] args) {
        SingletonEnum singletonEnum = SingletonEnum.INSTANCE;

        Class clazz = singletonEnum.getClass();
        try {
            Constructor<Singleton> constructor = clazz.getDeclaredConstructors()[0];

            //設置允許訪問私有的構造器
            constructor.setAccessible(true);
            Singleton singleton1 = constructor.newInstance();

            if (singleton1 != null && singleton1.getClass().equals(singletonEnum.getClass())) {
                System.out.println("通過反射構造除了對象");
            } else {
                return;
            }

        } catch (IllegalAccessException e) {
            e.printStackTrace();
        } catch (InstantiationException e) {
            e.printStackTrace();
        } catch (InvocationTargetException e) {
            e.printStackTrace();
        }

    }
}

運行結果:

拋出了java.lang.IllegalArgumentException: Cannot reflectively create enum objects at java.lang.reflect.Constructor.newInstance(Constructor.java:417)

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章