单例模式,一种防反射攻击的写法

单例模式,一种防反射攻击的写法

介绍

单例模式一般用于只需要一个对象的场景,必须http请求工具类,我们不需要多个,就可以用单例的写法

代码实现
public class UtilSync {
    private static UtilSync sync;
    private UtilSync(){
        System.out.println("util双重锁初始化成功");
    }

    public static UtilSync getInstance(){
        //如果为空才会进去
        if(sync==null){// 1
            //然后锁定这个工具
            synchronized (UtilSync.class){ // 1
                //做一下判断,因为有这种可能,就是1步骤判断为true进来了,
                // 然后另一个线程也成功判断了,并且都走完步骤,成功new了
                //然后我不判断是否为空的话,这里就创建多份util了
                if(sync==null){
                    sync=new UtilSync();
                }
            }
        }
        return sync;
    }
    public void show(){
        System.out.println("show----------->");
    }
}

于是,我们这种双重锁,防止多线程重复创建对象的代码就写好了,我们测试一下

public class Content {
    public static void main(String[] args) {
        //这里模拟多线程
        CountDownLatch latch=new CountDownLatch(1);
        for(int i=0;i<10000;i++){
            new Thread(new Runnable() {
                @Override
                public void run() {
                    try{
                        latch.await();
                    }catch (Exception e){
                        e.printStackTrace();
                    }
                    UtilSync instance1 = UtilSync.getInstance();
                }
            }).start();
        }
        latch.countDown();
    }


}

output:

util双重锁初始化成功

Process finished with exit code 0

执行结束,防止重复创建对象的功能我们实现了,接下来我们进行反射攻击
于是我们就有了下面的代码

try {
            Constructor<UtilSync> declaredConstructor = UtilSync.class.getDeclaredConstructor();
            declaredConstructor.setAccessible(true);
            UtilSync utilSync = declaredConstructor.newInstance();
            Method show = UtilSync.class.getMethod("show");
            show.invoke(utilSync);

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

output:

util双重锁初始化成功
show----------->

Process finished with exit code 0

可以看到,我们的双重锁单例在反射攻击面前不堪一击

我们能不能在代码里加一个像开关一样的东西呢,就是只能实例化对象一次,以后实例化对象都直接报错
于是,带着我们的想法,下面的代码就产生了

public class SingletonNotAttackByReflect
{
    private static boolean flag = false;
    private static final SingletonNotAttackByReflect INSTANCE = new SingletonNotAttackByReflect();
    
    //保证其不被java反射攻击
    private SingletonNotAttackByReflect()
    {
        synchronized (SingletonNotAttackByReflect.class) 
        {
            if(false == flag)
            {
                flag = !flag;
                System.out.println("初始化完成------->");


            }
            else
            {
                throw new RuntimeException("单例模式正在被攻击");
            }
            
        }
    }
    
    public static SingletonNotAttackByReflect getInstance()
    {
        return INSTANCE;
    }

    
}
```
我们来测试一下
```shell
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 singleinstance.TestReflectionSingleton.test2(TestReflectionSingleton.java:36)
	at singleinstance.TestReflectionSingleton.main(TestReflectionSingleton.java:15)
Caused by: java.lang.RuntimeException: 单例模式正在被攻击
	at singleinstance.UtilCantReflection.<init>(UtilCantReflection.java:27)
	... 6 more
```
我们看见,反射代码直接报错了,我们再来分析这个能解决反射攻击的构造函数里面的代码
首先定义了一个静态boolean变量,并且附初始值为false
然后在给第二个变量赋值的时候,会走这个工具类的构造方法
这个构造方法会先对这个类进行加锁,防止A线程满足false==flag的条件后,正要对flag进行赋值,然后a线程被挂起,b线程进来条件,然后b线程对flag进行取反,这时a线程也对flag进行取反,然后我们的flag判断就会失效。为了避免这种类似情况,这里直接把这个类作为锁,然后进行判断操作
这个逻辑就相当于做了一个开关,执行第一次构造方法后,下次进来构造方法会直接抛异常,于是很巧妙的解决了反射攻击问题

不过,仅仅这样就可以了吗?我并不觉得,接下来我们看看这种反射攻击方法

这里我们用了一个变量来实现避免反射实例化对象,我们的对象实例化控制在了开关上,那我们就可以对这个开关做点手脚,这里反射修改下开关

```java
try {
            Field flag = SingletonNotAttackByReflect.class.getDeclaredField("flag");
            flag.setAccessible(true);
            flag.set(null, false);

            Constructor<SingletonNotAttackByReflect> declaredConstructor = SingletonNotAttackByReflect.class.getDeclaredConstructor();
            declaredConstructor.setAccessible(true);
            SingletonNotAttackByReflect utilSync = declaredConstructor.newInstance();
            System.out.println(utilSync);
            System.out.println(UtilCantReflection.GetInstance());
        } catch (Exception e) {
            e.printStackTrace();
        }
```
output:
```shell
初始化完成------->
初始化完成------->
singleinstance.SingletonNotAttackByReflect@74a14482
false
singleinstance.UtilCantReflection@1540e19d

Process finished with exit code 0
```
这种开关控制对象实例化的方法还是有问题的

其实我们根本没必要弄个开关来判断对象能否初始化,我们直接判断实例是否为空就行了
```java
public class SingletonNotAttackByReflect
{
    private static final SingletonNotAttackByReflect INSTANCE;
    static {
         INSTANCE= new SingletonNotAttackByReflect();
    }

    //保证其不被java反射攻击
    private SingletonNotAttackByReflect()
    {
        synchronized (SingletonNotAttackByReflect.class) 
        {
           if(INSTANCE!=null){
               throw new IllegalStateException("已经被实例化了");
           }
            
        }
    }
    
    public static SingletonNotAttackByReflect getInstance()
    {
        return INSTANCE;
    }

}
```
我们来测试一下
```java
 try {
            CountDownLatch latch = new CountDownLatch(1);
            for (int i = 0; i < 10000; i++) {
                new Thread(new Runnable() {
                    @Override
                    public void run() {
                        try {
                            latch.await();
                            synchronized (SingletonNotAttackByReflect.class) {

//                                    SingletonNotAttackByReflect.getInstance().pringFlag();
                                    Constructor<SingletonNotAttackByReflect> declaredConstructor = SingletonNotAttackByReflect.class.getDeclaredConstructor();
                                    declaredConstructor.setAccessible(true);
                                    SingletonNotAttackByReflect utilSync = declaredConstructor.newInstance();
                                    System.out.println(utilSync);
                                    System.out.println(UtilCantReflection.GetInstance());

                            }
                        } catch (Exception e) {
                            e.printStackTrace();
                        }
                    }
                }).start();
            }
            latch.countDown();
        } catch (Exception e) {
            e.printStackTrace();
        }
```
otput
```shell
at singleinstance.TestReflectionSingleton$1.run(TestReflectionSingleton.java:58)
	at java.lang.Thread.run(Thread.java:748)
Caused by: java.lang.IllegalStateException: 已经被实例化了
	at singleinstance.SingletonNotAttackByReflect.<init>(SingletonNotAttackByReflect.java:16)
	... 5 more
java.lang.reflect.InvocationTargetException
	at sun.reflect.GeneratedConstructorAccessor1.newInstance(Unknown Source)
	at sun.reflect.DelegatingConstructorAccessorImpl.newInstance(DelegatingConstructorAccessorImpl.java:45)
	at java.lang.reflect.Constructor.newInstance(Constructor.java:423)
	at singleinstance.TestReflectionSingleton$1.run(TestReflectionSingleton.java:58)
	at java.lang.Thread.run(Thread.java:748)
Caused by: java.lang.IllegalStateException: 已经被实例化了
	at singleinstance.SingletonNotAttackByReflect.<init>(SingletonNotAttackByReflect.java:16)
	... 5 more
```
我们可以看到,反射失败,因为已经有一个实例了
  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值