IT数码 购物 网址 头条 软件 日历 阅读 图书馆
TxT小说阅读器
↓语音阅读,小说下载,古典文学↓
图片批量下载器
↓批量下载图片,美女图库↓
图片自动播放器
↓图片自动播放器↓
一键清除垃圾
↓轻轻一点,清除系统垃圾↓
开发: C++知识库 Java知识库 JavaScript Python PHP知识库 人工智能 区块链 大数据 移动开发 嵌入式 开发工具 数据结构与算法 开发测试 游戏开发 网络协议 系统运维
教程: HTML教程 CSS教程 JavaScript教程 Go语言教程 JQuery教程 VUE教程 VUE3教程 Bootstrap教程 SQL数据库教程 C语言教程 C++教程 Java教程 Python教程 Python3教程 C#教程
数码: 电脑 笔记本 显卡 显示器 固态硬盘 硬盘 耳机 手机 iphone vivo oppo 小米 华为 单反 装机 图拉丁
 
   -> Java知识库 -> 反射破坏单例模式以及怎样防御 -> 正文阅读

[Java知识库]反射破坏单例模式以及怎样防御

先贴几个比较全的java反射博客

  1. Java反射:用最直接的大白话来聊一聊Java中的反射机制
  2. Java基础之—反射(非常重要)
  3. Java反射技术详解

前面介绍的单例模式的实现方式: 设计模式之单例模式

单例模式的设计在于只保留一个公有静态函数来获取唯一的实例,其他方法(构造函数)或字段为私有,外界不能访问。

而java反射则突破了构造函数私有的限制,可以获取单例类的私有构造函数并使用。

//得到该类在内存中的字节码对象
Class objectClass = Class.forName("com.xt.designmode.creational.singleton.hungryBoyClass.Singleton");
//获取构造器
Constructor constructor = objectClass.getDeclaredConstructor();
//暴力反射,解除私有限定
constructor.setAccessible(true);
Singleton reflectInstance = (Singleton) constructor.newInstance();

我们针对所有实现单例模式的方法使用如下测试

public class ReflectTest {
    
    public static void main(String[] args) throws InvocationTargetException, InstantiationException, IllegalAccessException, NoSuchMethodException, ClassNotFoundException {
        Class objectClass = Class.forName("com.xt.designmode.creational.singleton.hungryBoyClass.Singleton");

        Constructor constructor = objectClass.getDeclaredConstructor();
        constructor.setAccessible(true);

        Singleton instance = Singleton.getInstance();
        Singleton reflectInstance = (Singleton) constructor.newInstance();

        System.out.println(instance);
        System.out.println(reflectInstance);

        //对象类型, == 比较的是地址
        if(instance != reflectInstance){
            System.out.printf("创建了两个实例\n");
        }else{
            System.out.printf("只创建了一个实例\n");
        }

    }
}

除开枚举式实现的单例模式,其余的方式都可以被反射直接获取到私有的构造器来创建新实例。

我们先看看枚举类是怎样防御反射攻击的。
像我 设计模式之单例模式这篇博客实现的枚举类实现单例是不能抵抗反射攻击的,因为我们不反射枚举类Singleton,直接反射Resource类就可以了,并且因为Resource类的构造函数是public的,所以真想攻击的话,直接去new Resource实例就好了。(逃

public enum Singleton {

    INSTANCE;
    private Resource resource;

    private Singleton(){
        resource = new Resource();
    }

    public Resource getInstance(){
        return resource;
    }
    
}

我们采用如下形式enum 枚举类实现单例模式

public enum EnumSingleton {

    INSTANCE;
    private EnumSingleton(){
    }

    public static EnumSingleton getInstance() {
        return INSTANCE;
    }
}

测试

public class ReflectTest {

    public static void main(String[] args) throws InvocationTargetException, InstantiationException, IllegalAccessException, NoSuchMethodException, ClassNotFoundException {
        Class objectClass = Class.forName("com.xt.designmode.creational.singleton.destroy.EnumSingleton");
        
        Constructor constructor = objectClass.getDeclaredConstructor();
        constructor.setAccessible(true);

        //枚举类式的单例模式
        EnumSingleton instance = EnumSingleton.INSTANCE;
        EnumSingleton reflectInstance = (EnumSingleton) constructor.newInstance();
        if(instance != reflectInstance){
            System.out.printf("创建了两个实例\n");
        }else{
            System.out.printf("只创建了一个实例\n");
        }
    }
}

在这里插入图片描述
会报出找不到无参的构造方法的异常。这个问题多个博主也遇到过,通过反编译的方式找到了问题所在。

我们使用javac来对代码进行编译,使用jad工具对class代码进行反编译

javac工具读由java语言编写的类和接口的定义,并将它们编译成字节代码的class文件
运用cmd终端或者IDEA终端 cd 到要编译文件的所在文件夹下面,运行以下命令将EnumSingleton.java编译成EnumSingleton.class文件

javac EnumSingleton.java

然后使用jad 工具来对class文件进行反编译
jad下载地址
我这里下载的是windows版本,然后放到一个文件夹下面,最后将文件夹位置设置到系统的环境变量。如下所示,是我的jad.exe所在的文件夹路径
在这里插入图片描述
将EnumSingleton.class文件复制到一个指定的文件夹下面,不然在源代码文件夹下,使用jad反编译生成的.java文件会覆盖原来的文件

jad -sjava EnumSingleton.class

生成的反编译代码如下所示:

// Decompiled by Jad v1.5.8g. Copyright 2001 Pavel Kouznetsov.
// Jad home page: http://www.kpdus.com/jad.html
// Decompiler options: packimports(3) 
// Source File Name:   EnumSingleton.java

package com.xt.designmode.creational.singleton.destroy;
public final class EnumSingleton extends Enum
{

    public static EnumSingleton[] values()
    {
        return (EnumSingleton[])$VALUES.clone();
    }

    public static EnumSingleton valueOf(String s)
    {
        return (EnumSingleton)Enum.valueOf(com/xt/designmode/creational/singleton/destroy/EnumSingleton, s);
    }

    private EnumSingleton(String s, int i)
    {
        super(s, i);
    }

    public static EnumSingleton getInstance()
    {
        return INSTANCE;
    }

    public static final EnumSingleton INSTANCE;
    private static final EnumSingleton $VALUES[];

    static 
    {
        INSTANCE = new EnumSingleton("INSTANCE", 0);
        $VALUES = (new EnumSingleton[] {
            INSTANCE
        });
    }
}

会发现枚举类内部其实是一个有参的构造函数。

也可以通过反射获取所有的构造方法,打印出来看一看,瞧一瞧

  Constructor<?>[] cons=objectClass.getDeclaredConstructors();
  for(Constructor<?>con:cons) {
      System.out.println("构造方法:"+con);
  }

如下所示,和上面反编译的代码一样。只有个String和int参数的有参构造器
在这里插入图片描述
所以我们获取其带有String和int参数的有参构造

Constructor constructor = objectClass.getDeclaredConstructor(String.class,int.class);

然后再通过这个构造器new一个实例
在这里插入图片描述

抛出异常,不能通过反射创建枚举类对象

debug进去看是那个地方抛出的异常,可以看见在反射的newInstance()方法里面,会检查该类是否ENUM修饰,如果是则抛出异常,反射失败
有如下方法,对于枚举类来说,通过反射创建枚举类实例的路是堵死了,所以枚举类实现单例模式不用怕反射破坏。

public T newInstance(Object ... initargs)
        throws InstantiationException, IllegalAccessException,
               IllegalArgumentException, InvocationTargetException
    {
         //其他代码已删除
        if ((clazz.getModifiers() & Modifier.ENUM) != 0)
            throw new IllegalArgumentException("Cannot reflectively create enum objects");
         //其他代码已删除
       
    }

那么其他单例模式的实现方式可以怎么防御反射攻击呢?

既然反射是通过暴力获取单例类的私有构造器来构造新实例,那就从构造器入手

饿汉式

    private Singleton(){
        if(singleton!=null){
            throw new RuntimeException("单例模式下,禁止使用反射创建新实例");
        }
    }

静态内部类式

    private Singleton(){
    
        if(InnerClass.singleton!=null){
            throw new RuntimeException("单例模式下,禁止使用反射创建新实例");
        }
    }

这两种方式通过更改构造器的代码都可以有效防止反射创建新实例

我们接下来对用双重检查锁式实现的单例模式进行测试。为了展示懒汉式使用(改动构造函数来防止反射攻击)方法的漏洞。我将Singleton类引用变量singleton设置成了public,方便测试代码获取打印到控制台

public class Singleton {

    public static volatile Singleton singleton;

    private Singleton() {
        if(singleton!=null){
            throw new RuntimeException("单例模式下,禁止使用反射创建新实例");
        }
    }

    public static Singleton getInstance() {
            if (singleton == null) {
                synchronized (Singleton.class){
                    if(singleton==null){
                        singleton = new Singleton();
                    }
                }
            }
        return singleton;
    }
}

测试代码如下,有详细的注释

public class ReflectTest {

    public static void main(String[] args) throws InvocationTargetException, InstantiationException, IllegalAccessException, NoSuchMethodException, ClassNotFoundException {
        //得到该类在内存中的字节码对象
        Class objectClass = Class.forName("com.xt.designmode.creational.singleton.doubleCheckClass.Singleton");
        //获取这个对象的构造器
        Constructor constructor = objectClass.getDeclaredConstructor();
        //暴力反射,解除私有限定
        constructor.setAccessible(true);
		//先使用反射获取的构造器创建一个实例
        Singleton reflectInstance = (Singleton) constructor.newInstance();
        //打印反射机制创造的实例
        System.out.println(reflectInstance);
		//打印此时单例类中的Singleton类引用变量singleton
        System.out.println(Singleton.singleton);
		//使用常规方式获取实例
        Singleton instance = Singleton.getInstance();
        //打印常规方式生成的实例
        System.out.println(instance);

        //对象类型, == 比较的是地址
        if(instance != reflectInstance){
            System.out.printf("创建了两个实例\n");
        }else{
            System.out.printf("只创建了一个实例\n");
        }
    }
}

如下图所示,创建了两个实例,是因为正常的双重检查锁式只是想实例化单例类中的一个引用变量,如果在单例类还没有实例化这个引用变量singleton之前,反射就可以通过使用构造器创建无数个实例,直到有人使用常规的方式来将单例类中的引用变量实例化。
本来懒汉式只是为了节约资源,等到真正有人使用他的时候才创建,但是没想到给反射钻了空子。
在这里插入图片描述
既然他钻这个空子,我们就想别的办法,因为我们只需要一个实例,而懒汉式的实例都是通过构造函数来创建的,那我们只需要保证构造函数只会被调用一次就好了。我们加个标记flag

public class Singleton {
    private static volatile Singleton singleton;
    private static boolean flag = true;

    private Singleton() {
       
        if(flag == true){
            flag = false;
        }else{
            throw new RuntimeException("单例模式下,禁止使用反射创建新实例");
        }
    }

    public static Singleton getInstance() {
    
            if (singleton == null) {
                synchronized (Singleton.class){
                    if(singleton==null){
                        singleton = new Singleton();
                    }
                }
            }
        return singleton;
    }
}

这样就限制了构造函数的使用次数,可以有效防止反射通过获取构造器来创建新实例。
但是反射是可以获取指定类的一切属性和方法,所以也可以通过反射不断将flag置为true来破解这一限制

public class ReflectTest {

    public static void main(String[] args) throws InvocationTargetException, InstantiationException, IllegalAccessException, NoSuchMethodException, ClassNotFoundException, NoSuchFieldException {
        //得到该类在内存中的字节码对象
        Class objectClass = Class.forName("com.xt.designmode.creational.singleton.doubleCheckClass.Singleton");
        //获取这个对象的构造器
        Constructor constructor = objectClass.getDeclaredConstructor();
        //暴力反射,解除私有限定
        constructor.setAccessible(true);

        for(int i=0;i<10;i++){
            Singleton reflectInstance = (Singleton) constructor.newInstance();
            Field flag = objectClass.getDeclaredField("flag");
            flag.setAccessible(true);
            flag.set(reflectInstance, true);
            System.out.println(reflectInstance);
        }

    }
}

如下图所示,我们创建了10个实例
在这里插入图片描述
所以这些方法都不能防范反射破坏,只有从反射的newInstance()方法入手,让这个方法不能运行成功,也就是使用上面所说的枚举式单例模式。

有错误以及不足的地方,请各位大佬斧正,哈利嘎多。

References:

  1. https://www.cnblogs.com/henuliulei/p/14870622.html
  2. https://blog.csdn.net/qq_37960603/article/details/104076323
  Java知识库 最新文章
计算距离春节还有多长时间
系统开发系列 之WebService(spring框架+ma
springBoot+Cache(自定义有效时间配置)
SpringBoot整合mybatis实现增删改查、分页查
spring教程
SpringBoot+Vue实现美食交流网站的设计与实
虚拟机内存结构以及虚拟机中销毁和新建对象
SpringMVC---原理
小李同学: Java如何按多个字段分组
打印票据--java
上一篇文章      下一篇文章      查看所有文章
加:2021-12-06 15:07:02  更:2021-12-06 15:08:44 
 
开发: C++知识库 Java知识库 JavaScript Python PHP知识库 人工智能 区块链 大数据 移动开发 嵌入式 开发工具 数据结构与算法 开发测试 游戏开发 网络协议 系统运维
教程: HTML教程 CSS教程 JavaScript教程 Go语言教程 JQuery教程 VUE教程 VUE3教程 Bootstrap教程 SQL数据库教程 C语言教程 C++教程 Java教程 Python教程 Python3教程 C#教程
数码: 电脑 笔记本 显卡 显示器 固态硬盘 硬盘 耳机 手机 iphone vivo oppo 小米 华为 单反 装机 图拉丁

360图书馆 购物 三丰科技 阅读网 日历 万年历 2024年11日历 -2024/11/24 5:00:37-

图片自动播放器
↓图片自动播放器↓
TxT小说阅读器
↓语音阅读,小说下载,古典文学↓
一键清除垃圾
↓轻轻一点,清除系统垃圾↓
图片批量下载器
↓批量下载图片,美女图库↓
  网站联系: qq:121756557 email:121756557@qq.com  IT数码