单例模式的攻击之反射攻击

上篇文章,我们讨论了序列化与反序列化对单例的破坏原理以及相应的抵御措施,本篇文章我们就讨论一下,反射对单例模式的破坏。
单例模式的实现按照加载时机的不同可分为懒加载和预加载两种方式。我们从这两种角度来讨论反射攻击分别对他们的影响。
下面咱们贴上测试类,完整版(包含之前的序列化与反序列化对于单例的破坏)

package com.wangming.pattern.creational.singleton;

import java.io.*;
import java.lang.reflect.Constructor;
import java.lang.reflect.Field;
import java.lang.reflect.InvocationTargetException;

/**
 * @Author: ming.wang
 * @Date: 2019/2/21 16:05
 * @Description: 使用反射或反序列化来破坏单例
 */
public class DestroySingletonTest {


    public static void main(String[] args) throws Exception {
        //序列化方式破坏单例   测试
//        serializeDestroyMethod();


        //反射方式破坏单例模式 测试
        reflectMethod();

    }

    private static void reflectMethod() throws  Exception {

//        reflectHungryMethod();
//        reflectLazyMethod();
        reflectLazyMethod2();

    }

    private static void reflectHungryMethod() throws Exception {
        //同理StaticInnerClassSingleton

        HungrySingleton hungrySingleton = null;
        HungrySingleton hungrySingleton_new = null;

        Class singletonClass = HungrySingleton.class;
        Constructor declaredConstructor = singletonClass.getDeclaredConstructor();
        declaredConstructor.setAccessible(true);

        hungrySingleton = HungrySingleton.getInstance();
        hungrySingleton_new = (HungrySingleton) declaredConstructor.newInstance();

        System.out.println(hungrySingleton == hungrySingleton_new);
    }

    /**
     * 验证使用对象空判断是否可抵御反射攻击
     * @throws Exception
     */
    private static void reflectLazyMethod() throws Exception {
        LazySingleton lazySingleton = null;
        LazySingleton lazySingleton_new = null;

        Class singletonClass = LazySingleton.class;
        Constructor declaredConstructor = singletonClass.getDeclaredConstructor();
        declaredConstructor.setAccessible(true);

        lazySingleton = LazySingleton.getInstance();
        lazySingleton_new = (LazySingleton) declaredConstructor.newInstance();

        System.out.println(lazySingleton == lazySingleton_new);
    }
    
    /**
     * 验证使用标志位是否可抵御反射攻击
     * @throws Exception
     */
    private static void reflectLazyMethod2() throws Exception {
        LazySingleton lazySingleton = null;
        LazySingleton lazySingleton_new = null;

        Class singletonClass = LazySingleton.class;
        Constructor declaredConstructor = singletonClass.getDeclaredConstructor();
        declaredConstructor.setAccessible(true);

        lazySingleton_new = (LazySingleton) declaredConstructor.newInstance();
        Field flag = singletonClass.getDeclaredField("flag");
        flag.setAccessible(true);
        flag.set(lazySingleton_new,true);
        lazySingleton = LazySingleton.getInstance();

        System.out.println(lazySingleton == lazySingleton_new);
    }
    private static void serializeDestroyMethod() throws IOException, ClassNotFoundException {
//        HungrySingleton hungrySingleton=null;
//        HungrySingleton hungrySingleton_new=null;

        StaticInnerClassSingleton hungrySingleton = null;
        StaticInnerClassSingleton hungrySingleton_new = null;

//        hungrySingleton=HungrySingleton.getInstance();
        hungrySingleton = StaticInnerClassSingleton.getInstance();

        ByteArrayOutputStream bos = new ByteArrayOutputStream();
        ObjectOutputStream oos = new ObjectOutputStream(bos);
        oos.writeObject(hungrySingleton);

        ByteArrayInputStream bis = new ByteArrayInputStream(bos.toByteArray());
        ObjectInputStream ois = new ObjectInputStream(bis);
//        hungrySingleton_new= (HungrySingleton) ois.readObject();
        hungrySingleton_new = (StaticInnerClassSingleton) ois.readObject();

        System.out.println(hungrySingleton == hungrySingleton_new);
    }
}

由于代码里面注释已经很清楚了,咱们就直接说结论吧

结论

预加载模式

预加载模式典型的实现方式就是饿汉式与静态内部类的实现方式。抵御反射的攻击的主要代码if (null != instance) { throw new RuntimeException("禁止反射调用默认构造器"); }

  • 静态内部类的实现方式
/**
 * @Author: ming.wang
 * @Date: 2019/2/20 15:46
 * @Description:
 */
public class StaticInnerClassSingleton implements Serializable {

    private StaticInnerClassSingleton() {
         if (null != instance) {
            throw new RuntimeException("禁止反射调用默认构造器");
        }
}

    private static class InnerClass{
        private static StaticInnerClassSingleton instance=new StaticInnerClassSingleton();
    }

    public static StaticInnerClassSingleton getInstance(){
       return InnerClass.instance;
    }
    private Object readResolve()
    {
        return InnerClass.instance;
    }
}
  • 饿汉式
/**
 * @Author: ming.wang
 * @Date: 2019/2/20 17:04
 * @Description:
 */
public class HungrySingleton implements Serializable {
    private final static HungrySingleton instance;
    static {
        instance = new HungrySingleton();
    }
    private HungrySingleton() {
         if (null != instance) {
            throw new RuntimeException("禁止反射调用默认构造器");
        }
}

    public static HungrySingleton getInstance() {
        return instance;
    }

    /**
     * 为解决序列化和反序列化对单例的破坏
     * @return
     */
    private Object readResolve() {
        return instance;
    }
}

懒加载模式(懒汉式和双重检查)

  • 懒汉式
package com.wangming.pattern.creational.singleton;

import java.io.Serializable;

/**
 * @Auther: ming.wang
 * @Date: 2019/1/6 19:25
 * @Description:
 */

public class LazySingleton implements Serializable {
    private static LazySingleton instance = null;
    private static boolean flag = true;

    private LazySingleton() {
        //对于懒加载模式,此种方式是不能抵御反射攻击的
       /* if (null != instance) {
            throw new RuntimeException("禁止反射调用默认构造器");
        }*/
       
       
       //对于懒加载方式,使用标志位的方式也并不能保证抵御反射攻击,因为利用反射可以修改标志位
      /*  if (flag) {
            flag = false;
        } else {
            throw new RuntimeException("禁止反射调用默认构造器");
        }*/
    }

    public synchronized static LazySingleton getInstance() {
        if (null == instance) {
            instance = new LazySingleton();
        }
        return instance;
    }

    /**
     * 为解决序列化和反序列化对单例的破坏
     * @return
     */
    private Object readResolve() {
        return instance;
    }

}

  • 双重检查
package com.wangming.pattern.creational.singleton;

import java.io.Serializable;

/**
 * @Author: ming.wang
 * @Date: 2019/2/20 14:45
 * @Description:
 */
public class LazyDoubleCheckSingleton implements Serializable {

    private volatile static LazyDoubleCheckSingleton instance=null;

    private static boolean flag = true;

    private LazyDoubleCheckSingleton() {
        //对于懒加载模式,此种方式是不能抵御反射攻击的
       /* if (null != instance) {
            throw new RuntimeException("禁止反射调用默认构造器");
        }*/


        //对于懒加载方式,使用标志位的方式也并不能保证抵御反射攻击,因为利用反射可以修改标志位
      /*  if (flag) {
            flag = false;
        } else {
            throw new RuntimeException("禁止反射调用默认构造器");
        }*/
    }

    public static LazyDoubleCheckSingleton getInstance(){
        if (null==instance) {//第一个 if(instance==null),其实是为了解决代码中的效率问题,只有instance为null的时候,才进入synchronized的代码段,大大减少了几率。
            synchronized (LazyDoubleCheckSingleton.class) {
                if (null==instance) {//第二个if(instance==null),则是为了防止可能出现多个实例的情况。
                    instance=new LazyDoubleCheckSingleton();
                    /*
                    * 1.分配内存给这对象
                    * 2.初始化对象
                    * 3.设置instance指向刚刚分配的内存空间(执行完这步 instance才是非 null了)
                    * 其中2和3会指令重排序,执行顺序可能为123或132
                    * */
                }
            }
        }
        return instance;

    }
    private Object readResolve()
    {
        return instance;
    }
}

疑问

既然懒加载的模式抵御不了反射,那么有没有一种解决方案,既能解决线程安全问题,又能抵御序列化和反射的攻击呢?答案下篇文章揭晓

推荐阅读更多精彩内容