反射和序列化破解单例

介绍

主要介绍通过反射的方式获取单例对象, 验证单例模式的安全性.
主要从以下几个角度来介绍反射下的单例
饿汉式
双重锁检查
枚举单例

饿汉式

饿汉式直接使用反射即可破解单例模式

public class ReflectTest {

    public static void main(String[] args) {

        try {
            HungerPattern hungerPattern = HungerPattern.getHungerPattern();

            Class<HungerPattern> hungerPatternClass = HungerPattern.class;

            Constructor<HungerPattern> conA = hungerPatternClass.getDeclaredConstructor();
            Constructor<HungerPattern> conB = hungerPatternClass.getDeclaredConstructor();

            conA.setAccessible(true);
            conB.setAccessible(true);

            HungerPattern instanceA = conA.newInstance();
            HungerPattern instanceB = conB.newInstance();
            // instanceA 和 instanceB 不是同一对象
            System.out.println(hungerPattern.hashCode());
            System.out.println(instanceA.hashCode());
            System.out.println(instanceB.hashCode());

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

}

输出结果

D:\jdk1.8\bin\java.exe . . .
713338599
168423058
821270929

Process finished with exit code 0

双重锁检查

双重锁检查同样存在相同的情况

  1. 直接使用
    `java
    public class ReflectTest {
public static void main(String[] args) {

    try {
        DoubleCheckLockLazyPattern pattern = DoubleCheckLockLazyPattern.getDoubleCheckLockLazyPattern();

        Class<DoubleCheckLockLazyPattern> patternClass = DoubleCheckLockLazyPattern.class;

        Constructor<DoubleCheckLockLazyPattern> conA = patternClass.getDeclaredConstructor();
        Constructor<DoubleCheckLockLazyPattern> conB = patternClass.getDeclaredConstructor();

        conA.setAccessible(true);
        conB.setAccessible(true);

        DoubleCheckLockLazyPattern patternA = conA.newInstance();
        DoubleCheckLockLazyPattern patternB = conA.newInstance();

        System.out.println(pattern.hashCode());
        System.out.println(patternA.hashCode());
        System.out.println(patternB.hashCode());

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

}


输出结果
```text
D:\jdk1.8\bin\java.exe . . .
713338599
168423058
821270929

Process finished with exit code 0
  1. 在双重锁检查私有构造内加入异常
public class DoubleCheckLockLazyPattern {

    private DoubleCheckLockLazyPattern() {

        // 加入异常判断, 防止反射
        if (doubleCheckLockLazyPattern != null) {
            throw new RuntimeException();
        }

    }

    private static volatile DoubleCheckLockLazyPattern doubleCheckLockLazyPattern = null;

    public static DoubleCheckLockLazyPattern getDoubleCheckLockLazyPattern() {

        try {
            if (doubleCheckLockLazyPattern == null) {
                // 一系列操作
                Thread.sleep(100);
                synchronized (DoubleCheckLockLazyPattern.class) {
                    // 二次检查
                    if (doubleCheckLockLazyPattern == null) {
                        doubleCheckLockLazyPattern = new DoubleCheckLockLazyPattern();
                    }
                }

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

        return doubleCheckLockLazyPattern;
    }
}

输出结果

D:\jdk1.8\bin\java.exe . . .
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 com.liuzhihang.demo.singleton.ReflectTest.main(ReflectTest.java:24)
Caused by: java.lang.RuntimeException
    at com.liuzhihang.demo.singleton.DoubleCheckLockLazyPattern.(DoubleCheckLockLazyPattern.java:15)
    ... 5 more

  1. 通过序列化反序列化获取对象

DoubleCheckLockLazyPattern 实现序列化


public class ReflectTest {

    public static void main(String[] args) {

        try {
            DoubleCheckLockLazyPattern pattern = DoubleCheckLockLazyPattern.getDoubleCheckLockLazyPattern();

            FileOutputStream fos= new FileOutputStream("C:/Users/liuzhihang/desktop/pattern.txt");
            ObjectOutputStream oos = new ObjectOutputStream(fos);
            oos.writeObject(pattern);
            oos.close();
            fos.close();
            ObjectInputStream oisA = new ObjectInputStream(new FileInputStream("C:/Users/liuzhihang/desktop/pattern.txt"));
            DoubleCheckLockLazyPattern patternA= (DoubleCheckLockLazyPattern) oisA.readObject();

            ObjectInputStream oisB = new ObjectInputStream(new FileInputStream("C:/Users/liuzhihang/desktop/pattern.txt"));
            DoubleCheckLockLazyPattern patternB= (DoubleCheckLockLazyPattern) oisB.readObject();

            System.out.println(pattern.hashCode());
            System.out.println(patternA.hashCode());
            System.out.println(patternB.hashCode());

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

}

输出结果

D:\jdk1.8\bin\java.exe . . .
258952499
1702297201
1996181658

Process finished with exit code 0
  1. 修改反序列化方法, 可以防止反序列化

添加以下方法

private Object readResolve() {
    return doubleCheckLockLazyPattern;
}

输出结果

D:\jdk1.8\bin\java.exe . . .
258952499
258952499
258952499

Process finished with exit code 0

枚举单例

public enum SingletonEnum {

    /**
     * 单例
     */
    INSTANCE;

    private Resource resource;


    SingletonEnum() {
        this.resource = new Resource();
    }

    public Resource getResource() {
        return resource;
    }
}

class Resource {
}

枚举单例分析

在枚举反射获取对象时抛出异常, 通过 Constructor类 源码可以看出, 在反射创建对象时会判断是否是枚举修饰, 是则抛出异常


  @CallerSensitive
    public T newInstance(Object ... initargs)
        throws InstantiationException, IllegalAccessException,
               IllegalArgumentException, InvocationTargetException
    {
        if (!override) {
            if (!Reflection.quickCheckMemberAccess(clazz, modifiers)) {
                Class caller = Reflection.getCallerClass();
                checkAccess(caller, clazz, null, modifiers);
            }
        }
        if ((clazz.getModifiers() & Modifier.ENUM) != 0)
            throw new IllegalArgumentException("Cannot reflectively create enum objects");
        ConstructorAccessor ca = constructorAccessor;   // read volatile
        if (ca == null) {
            ca = acquireConstructorAccessor();
        }
        @SuppressWarnings("unchecked")
        T inst = (T) ca.newInstance(initargs);
        return inst;
    }

同时在父类 Enum类 中重写了 readObject方法, 所以枚举也可以避免反序列化

    /**
     * prevent default deserialization
     */
    private void readObject(ObjectInputStream in) throws IOException,
        ClassNotFoundException {
        throw new InvalidObjectException("can't deserialize enum");
    }


   转载规则


《反射和序列化破解单例》 liuzhihang 采用 知识共享署名 4.0 国际许可协议 进行许可。
  目录