單例模式,一種防反射攻擊的寫法

單例模式,一種防反射攻擊的寫法

介紹

單例模式一般用於只需要一個對象的場景,必須http請求工具類,我們不需要多個,就可以用單例的寫法

代碼實現

public class UtilSync {
    private static UtilSync sync;
    private UtilSync(){
        System.out.println("util雙重鎖初始化成功");
    }

    public static UtilSync getInstance(){
        //如果爲空纔會進去
        if(sync==null){// 1
            //然後鎖定這個工具
            synchronized (UtilSync.class){ // 1
                //做一下判斷,因爲有這種可能,就是1步驟判斷爲true進來了,
                // 然後另一個線程也成功判斷了,並且都走完步驟,成功new了
                //然後我不判斷是否爲空的話,這裏就創建多份util了
                if(sync==null){
                    sync=new UtilSync();
                }
            }
        }
        return sync;
    }
    public void show(){
        System.out.println("show----------->");
    }
}

於是,我們這種雙重鎖,防止多線程重複創建對象的代碼就寫好了,我們測試一下

public class Content {
    public static void main(String[] args) {
        //這裏模擬多線程
        CountDownLatch latch=new CountDownLatch(1);
        for(int i=0;i<10000;i++){
            new Thread(new Runnable() {
                @Override
                public void run() {
                    try{
                        latch.await();
                    }catch (Exception e){
                        e.printStackTrace();
                    }
                    UtilSync instance1 = UtilSync.getInstance();
                }
            }).start();
        }
        latch.countDown();
    }


}

output:

util雙重鎖初始化成功

Process finished with exit code 0

執行結束,防止重複創建對象的功能我們實現了,接下來我們進行反射攻擊
於是我們就有了下面的代碼

try {
            Constructor<UtilSync> declaredConstructor = UtilSync.class.getDeclaredConstructor();
            declaredConstructor.setAccessible(true);
            UtilSync utilSync = declaredConstructor.newInstance();
            Method show = UtilSync.class.getMethod("show");
            show.invoke(utilSync);

        } catch (Exception e) {
            e.printStackTrace();
        }

output:

util雙重鎖初始化成功
show----------->

Process finished with exit code 0

可以看到,我們的雙重鎖單例在反射攻擊面前不堪一擊

我們能不能在代碼里加一個像開關一樣的東西呢,就是隻能實例化對象一次,以後實例化對象都直接報錯
於是,帶着我們的想法,下面的代碼就產生了

public class SingletonNotAttackByReflect
{
    private static boolean flag = false;
    private static final SingletonNotAttackByReflect INSTANCE = new SingletonNotAttackByReflect();
    
    //保證其不被java反射攻擊
    private SingletonNotAttackByReflect()
    {
        synchronized (SingletonNotAttackByReflect.class) 
        {
            if(false == flag)
            {
                flag = !flag;
                System.out.println("初始化完成------->");


            }
            else
            {
                throw new RuntimeException("單例模式正在被攻擊");
            }
            
        }
    }
    
    public static SingletonNotAttackByReflect getInstance()
    {
        return INSTANCE;
    }

    
}
```
我們來測試一下
```shell
java.lang.reflect.InvocationTargetException
	at sun.reflect.NativeConstructorAccessorImpl.newInstance0(Native Method)
	at sun.reflect.NativeConstructorAccessorImpl.newInstance(NativeConstructorAccessorImpl.java:62)
	at sun.reflect.DelegatingConstructorAccessorImpl.newInstance(DelegatingConstructorAccessorImpl.java:45)
	at java.lang.reflect.Constructor.newInstance(Constructor.java:423)
	at singleinstance.TestReflectionSingleton.test2(TestReflectionSingleton.java:36)
	at singleinstance.TestReflectionSingleton.main(TestReflectionSingleton.java:15)
Caused by: java.lang.RuntimeException: 單例模式正在被攻擊
	at singleinstance.UtilCantReflection.<init>(UtilCantReflection.java:27)
	... 6 more
```
我們看見,反射代碼直接報錯了,我們再來分析這個能解決反射攻擊的構造函數裏面的代碼
首先定義了一個靜態boolean變量,並且附初始值爲false
然後在給第二個變量賦值的時候,會走這個工具類的構造方法
這個構造方法會先對這個類進行加鎖,防止A線程滿足false==flag的條件後,正要對flag進行賦值,然後a線程被掛起,b線程進來條件,然後b線程對flag進行取反,這時a線程也對flag進行取反,然後我們的flag判斷就會失效。爲了避免這種類似情況,這裏直接把這個類作爲鎖,然後進行判斷操作
這個邏輯就相當於做了一個開關,執行第一次構造方法後,下次進來構造方法會直接拋異常,於是很巧妙的解決了反射攻擊問題

不過,僅僅這樣就可以了嗎?我並不覺得,接下來我們看看這種反射攻擊方法

這裏我們用了一個變量來實現避免反射實例化對象,我們的對象實例化控制在了開關上,那我們就可以對這個開關做點手腳,這裏反射修改下開關

```java
try {
            Field flag = SingletonNotAttackByReflect.class.getDeclaredField("flag");
            flag.setAccessible(true);
            flag.set(null, false);

            Constructor<SingletonNotAttackByReflect> declaredConstructor = SingletonNotAttackByReflect.class.getDeclaredConstructor();
            declaredConstructor.setAccessible(true);
            SingletonNotAttackByReflect utilSync = declaredConstructor.newInstance();
            System.out.println(utilSync);
            System.out.println(UtilCantReflection.GetInstance());
        } catch (Exception e) {
            e.printStackTrace();
        }
```
output:
```shell
初始化完成------->
初始化完成------->
singleinstance.SingletonNotAttackByReflect@74a14482
false
singleinstance.UtilCantReflection@1540e19d

Process finished with exit code 0
```
這種開關控制對象實例化的方法還是有問題的

其實我們根本沒必要弄個開關來判斷對象能否初始化,我們直接判斷實例是否爲空就行了
```java
public class SingletonNotAttackByReflect
{
    private static final SingletonNotAttackByReflect INSTANCE;
    static {
         INSTANCE= new SingletonNotAttackByReflect();
    }

    //保證其不被java反射攻擊
    private SingletonNotAttackByReflect()
    {
        synchronized (SingletonNotAttackByReflect.class) 
        {
           if(INSTANCE!=null){
               throw new IllegalStateException("已經被實例化了");
           }
            
        }
    }
    
    public static SingletonNotAttackByReflect getInstance()
    {
        return INSTANCE;
    }

}
```
我們來測試一下
```java
 try {
            CountDownLatch latch = new CountDownLatch(1);
            for (int i = 0; i < 10000; i++) {
                new Thread(new Runnable() {
                    @Override
                    public void run() {
                        try {
                            latch.await();
                            synchronized (SingletonNotAttackByReflect.class) {

//                                    SingletonNotAttackByReflect.getInstance().pringFlag();
                                    Constructor<SingletonNotAttackByReflect> declaredConstructor = SingletonNotAttackByReflect.class.getDeclaredConstructor();
                                    declaredConstructor.setAccessible(true);
                                    SingletonNotAttackByReflect utilSync = declaredConstructor.newInstance();
                                    System.out.println(utilSync);
                                    System.out.println(UtilCantReflection.GetInstance());

                            }
                        } catch (Exception e) {
                            e.printStackTrace();
                        }
                    }
                }).start();
            }
            latch.countDown();
        } catch (Exception e) {
            e.printStackTrace();
        }
```
otput
```shell
at singleinstance.TestReflectionSingleton$1.run(TestReflectionSingleton.java:58)
	at java.lang.Thread.run(Thread.java:748)
Caused by: java.lang.IllegalStateException: 已經被實例化了
	at singleinstance.SingletonNotAttackByReflect.<init>(SingletonNotAttackByReflect.java:16)
	... 5 more
java.lang.reflect.InvocationTargetException
	at sun.reflect.GeneratedConstructorAccessor1.newInstance(Unknown Source)
	at sun.reflect.DelegatingConstructorAccessorImpl.newInstance(DelegatingConstructorAccessorImpl.java:45)
	at java.lang.reflect.Constructor.newInstance(Constructor.java:423)
	at singleinstance.TestReflectionSingleton$1.run(TestReflectionSingleton.java:58)
	at java.lang.Thread.run(Thread.java:748)
Caused by: java.lang.IllegalStateException: 已經被實例化了
	at singleinstance.SingletonNotAttackByReflect.<init>(SingletonNotAttackByReflect.java:16)
	... 5 more
```
我們可以看到,反射失敗,因爲已經有一個實例了
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章