你写的单例一定安全吗?

简介: 前言:在并发环境中,我们可以用各种锁来保持单例的线程安全,当然这是从业务角度来考虑的;但是,从一个攻击者的角度来看,你的单例也许只保证了线程安全,当攻击者通过反射new出单例的实例时候(反射的可以改变你的私有构造函数)...

前言:

在并发环境中,我们可以用各种锁来保持单例的线程安全,当然这是从业务角度来考虑的;但是,从一个攻击者的角度来看,你的单例也许只保证了线程安全,当攻击者通过反射new出单例的实例时候(反射的可以改变你的私有构造函数),那攻击者就可以控制你的单例干很多坏事

举例:

单例代码:

/**
 * 懒汉式单例(简单点),不考虑多线程并发的模型
 */
public class MyInstance {
    private static MyInstance instance;

    private MyInstance(){

    }

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

攻击代码:

import java.lang.reflect.Constructor;

public class Main {

    public static void main(String[] args){

        System.out.println(MyInstance.getInstance());
        try
        {
            Class<MyInstance> classType = MyInstance.class;

            Constructor<MyInstance> constructor = classType.getDeclaredConstructor(null);
            constructor.setAccessible(true);//关键代码,将这个构造函数的权限设置为可进入的
            MyInstance myInstanceHack = constructor.newInstance();
            System.out.println(myInstanceHack);
        }
        catch (Exception e)
        {
            e.printStackTrace();
        }

    }
}

攻击结果:

从结果来看,两个对象的地址是不一样的,那么攻击者就可以利用这个方式,干很多越权的事情。

如何防范呢?

其实,问题所在就是那个构造函数被攻击了,因此,我们只要保护好构造函数,就能基本防御这种攻击

方式一:

利用一个静态变量,保证构造函数只被掉一次

/**
 * 懒汉式单例(简单点),不考虑多线程并发的模型
 */
public class MyInstance {
    private static MyInstance instance;
    private static boolean isUsed;

    private MyInstance(){

        if (isUsed){
            //抛出异常
            throw new RuntimeException();
        }else {
            isUsed = true;
        }



    }

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

方式二:

利用枚举的特性:每个枚举量只会实例化一次,也就是枚举类的单例:

public enum MyInstanceEnum {
    INSTANCE;
    private MyInstance2 myInstance;

    MyInstanceEnum(){
        myInstance = new MyInstance2();
    }

    public MyInstance2 getMyInstance() {
        return myInstance;
    }
}

攻击时报错

总结:

当然,反射被设计出来,肯定不是用来攻击的,它的作用可以很多依赖注入的框架中,比如Spring各种框架,JSON解析等很多方面。(不晓得从哪里看到一句话:人的血液循环是复杂但是有规律的,打针(反射)在中间介入这个循环如果没摸清楚循环规律而乱戳表面看似达到效果,实际会引发其他问题)

目录
相关文章
|
5月前
|
设计模式 存储
static应用之 单例设计模式(饿汉单例&懒汉单例)
本章我们来学习单例模式中的饿汉单例和懒汉单例,那么什么是单例模式呢?应用该模式的这个类永远只有一个实列,即一个类只能创建一个对象例如电脑上的任务管理器对象只需要一个就能解决问题,可以节省内存空间先定义一个类,把构造器私有如下图,先来看一下没有把构造器私有化的SingleInstance类,此时Test类中可以随意创建多个SingleInstance的实例化。 在SingleInstance类中用private修饰无参构造器,此时左边new方法报错了。我们在右边创建一个静态变量来存储对象,变量名为instan
24 0
|
2月前
|
设计模式 安全 Java
单例模式:饿汉模式、懒汉模式
单例模式:饿汉模式、懒汉模式
24 0
|
8月前
|
安全
线程安全的单例懒汉式
线程安全的单例懒汉式
26 0
|
8月前
|
安全 Java
饿汉式单例
饿汉式单例
|
8月前
|
安全 Java
懒汉式单例
懒汉式单例
|
8月前
|
缓存 安全 Java
双重检查锁单例
双重检查锁单例
|
8月前
|
设计模式 安全 编译器
单例模式实现的四种方法详解
单例模式实现的四种方法详解
58 0
|
设计模式 缓存 JavaScript
什么是单例模式?怎么生成单例类? - 1/14
什么是单例模式?怎么生成单例类? - 1/14
55 0
|
安全 Java
单例模式和多例模式(懒汉式和饿汉式)
单例模式和多例模式(懒汉式和饿汉式)
111 0
|
SQL 设计模式 安全
一个单例还能写出花来吗?
单例可以说是最简单的一个设计模式了,单例模式要求只能创建一个对象实例。通常的写法是声明私有的构造函数,提供静态方法获取单例的对象实例。 常见的单例写法就是饿汉式、懒汉式、双重加锁验证、静态内部类和枚举的方式,写法可能大家都知道,不过针对不同的写法还是有可以继续深挖一下的地方,让我们从最简单的几种写法开始回顾单例,不想看前面的话直接往后翻好了。
一个单例还能写出花来吗?