单例模式安全问题--反射攻击

2019-11-24  本文已影响0人  wbpailxt

利用反射将私有构造器的权限打开

package com.geely.design.pattern.creational.singleton;

import java.io.Serializable;

/**
 * Created by geely
 */
public class HungrySingleton implements Serializable,Cloneable{
    // 准备阶段会被分配内存,但不会被赋予null值,在初始化阶段被初始化。
    private final static HungrySingleton hungrySingleton;

    static{
        hungrySingleton = new HungrySingleton();
    }

    public static HungrySingleton getInstance(){
        return hungrySingleton;
    }
    private HungrySingleton(){
        
    }
    private Object readResolve(){
        return hungrySingleton;
    }

    @Override
    protected Object clone() throws CloneNotSupportedException {
        return getInstance();
    }
}
package com.geely.design.pattern.creational.singleton;

import java.io.File;
import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.ObjectInputStream;
import java.io.ObjectOutputStream;
import java.lang.reflect.Constructor;
import java.lang.reflect.InvocationTargetException;

/**
 * Created by geely
 */
public class Test {
    public static void main(String[] args) throws IOException, ClassNotFoundException, NoSuchMethodException, IllegalAccessException, InvocationTargetException, InstantiationException {
        HungrySingleton instance = HungrySingleton.getInstance();
        Class objectClass = HungrySingleton.class;
        Constructor constructor = objectClass.getDeclaredConstructor();
        constructor.setAccessible(true);
        //先进行HungrySingleton的初始化,再调用私有构造函数
        HungrySingleton newInstance = (HungrySingleton) constructor.newInstance();

        System.out.println(instance);
        System.out.println(newInstance);
        System.out.println(instance == newInstance);
    }
}

结果:

图片.png
由于HungrySingleton在类的初始化阶段就已经实例化了,当它调用构造函数constructor.newInstance()时,常量静态变量必定不为null。所以可以这样。
private HungrySingleton(){
        if(hungrySingleton != null){
            throw new RuntimeException("单例构造器禁止反射调用");
        }
}

Test类

package com.geely.design.pattern.creational.singleton;
/**
 * Created by geely
 */
public class Test {
    public static void main(String[] args) throws IOException, ClassNotFoundException, NoSuchMethodException, IllegalAccessException, InvocationTargetException, InstantiationException {
        Class objectClass = HungrySingleton.class;
        Constructor constructor = objectClass.getDeclaredConstructor();
        constructor.setAccessible(true);
        //先进行HungrySingleton的初始化,再调用私有构造函数
        HungrySingleton newInstance = (HungrySingleton) constructor.newInstance();
        System.out.println(newInstance);
 }
}

结果


图片.png

对于由静态内部类实现的单例模式也能够用同样的方式抵挡反射攻击。因为其在静态内部类初始化阶段就已经实例化好外部类。

package com.geely.design.pattern.creational.singleton;

/**
 * Created by geely
 */
public class StaticInnerClassSingleton {
    private static class InnerClass{
        private static StaticInnerClassSingleton staticInnerClassSingleton = new StaticInnerClassSingleton();
    }
    public static StaticInnerClassSingleton getInstance(){
        return InnerClass.staticInnerClassSingleton;
    }
    private StaticInnerClassSingleton(){
        if(InnerClass.staticInnerClassSingleton != null){
            throw new RuntimeException("单例构造器禁止反射调用");
        }
    }
}

那对于不是在类加载的时候创建单例对象的情况该怎么处理?
先来看看像前面几个例子在构造函数判断单例对象是否为null会出现怎么样的情况。
单例类

package com.geely.design.pattern.creational.singleton;

/**
 * Created by geely
 */
public class LazySingleton {
    public static LazySingleton lazySingleton = null;
    private LazySingleton(){
        if(lazySingleton != null){
            throw new RuntimeException("单例构造器禁止反射调用");
        }
    }
    public  static LazySingleton getInstance(){
        synchronized(LazySingleton.class){
            if(lazySingleton == null){
                lazySingleton = new LazySingleton();
            }
            return lazySingleton;
        }
    }
}

测试类:

package com.geely.design.pattern.creational.singleton;
/**
 * Created by geely
 */
public class Test {
    public static void main(String[] args) throws IOException, ClassNotFoundException, NoSuchMethodException, IllegalAccessException, InvocationTargetException, InstantiationException {
        LazySingleton newInstance = LazySingleton.getInstance();

        Class clazz = LazySingleton.class;
        Constructor constructor = clazz.getDeclaredConstructor();
        constructor.setAccessible(true);
        LazySingleton instance = (LazySingleton) constructor.newInstance();
        System.out.println(instance);
        System.out.println(newInstance);
        System.out.println(instance == newInstance);

    }
}

结果:

图片.png
似乎是能抵御反射攻击,但这是因为在getInstance()方法中对静态变量lazySingleton进行了赋值操作,然后再在构造函数中对静态变量lazySingleton进行了判null操作。所以会抛出错误。
如果先调用私有构造函数,在调用静态方法getInstance()呢?
测试类:
package com.geely.design.pattern.creational.singleton;
/**
 * Created by geely
 */
public class Test {
    public static void main(String[] args) throws IOException, ClassNotFoundException, NoSuchMethodException, IllegalAccessException, InvocationTargetException, InstantiationException {
        Class clazz = LazySingleton.class;
        Constructor constructor = clazz.getDeclaredConstructor();
        constructor.setAccessible(true);
        LazySingleton instance = (LazySingleton) constructor.newInstance();

        LazySingleton newInstance = LazySingleton.getInstance();
        System.out.println(instance);
        System.out.println(newInstance);
        System.out.println(instance == newInstance);
    }
}

结果:


图片.png

这样就没有抵挡住反射攻击了。

上一篇下一篇

猜你喜欢

热点阅读