程序员面试系列之Java单例模式的攻击与防护

我写的程序员面试系列

Java面试系列-webapp文件夹和WebContent文件夹的区别?java

程序员面试系列:Spring MVC能响应HTTP请求的缘由?程序员

Java程序员面试系列-什么是Java Marker Interface(标记接口)web

使用JDK自带的工具jstack找出形成运行程序死锁的缘由面试

编程面试题:编写一个会形成数据库死锁的应用数据库

JavaScript面试系列:JavaScript设计模式之桥接模式和懒加载编程

使用JavaScript ES6的新特性计算Fibonacci(非波拉契数列)json

单例模式在不少Java程序员的眼中,应该是设计模式里最简单的一种了。那么单例模式可能会被攻击,您据说过么?设计模式

说到“单例模式被攻击”这个话题,你们最容易想到的可能就是经过序列化/反序列化来攻击单例模式,由于一个对象实例序列化再反序列化后,获得的新的对象虽然各字段内容和原字段一致,然而对象地址和原始对象地址相比已经发生了变化,所以它们是两个不一样的对象。app

上面的结论彻底正确,然而除了序列化/反序列化,单例模式还可能遭受另外一种方式的攻击,即反射攻击(Reflection attack)。webapp

看一个具体例子:

public class JerrySingleton {

   private String name;

   private JerrySingleton(){

   name = "Jerry";

}

private static class SingletonHolder{

      private static final JerrySingleton INSTANCE = new JerrySingleton();

}

public static JerrySingleton getInstance() {

      return SingletonHolder.INSTANCE;

      }

}

上面是一个饿汉式单例。

然而我只须要将这个单例类JerrySingleton的构造函数经过反射设置成能够访问Accessible,而后就能经过反射调用该构造函数,进而生成新的对象实例。这样就破坏了单例模式。

Class<?> classType = JerrySingleton.class;

Constructor<?> c = classType.getDeclaredConstructor(null);

c.setAccessible(true);

JerrySingleton e1 = (JerrySingleton)c.newInstance();

JerrySingleton e2 = JerrySingleton.getInstance();

System.out.println(e1 == e2);

第6行代码会打印false。

针对这种攻击,一种可行的防护措施是在单例类的构造函数内定义一个布尔变量,初始化为false。当构造函数执行后,该变量被置为true。若是接下来构造函数再次被执行,则人为抛出异常,避免构造函数重复执行。

public class JerrySingletonImproved {

    private static boolean flag = false;

    private JerrySingletonImproved(){

         synchronized(JerrySingletonImproved.class) {

            if(flag == false) {

                  flag = !flag;

            }

      else {

              throw new RuntimeException("Singleton violated");

      }

  }

}

}

这种防护措施没法从根本上杜绝Singleton被攻击,由于攻击者仍旧能够经过反射来修改布尔变量flag的值,从而绕过这个检查。

最理想的不会受到攻击的单例模式实现是借助Java里枚举类Enumeration的特性:

这种实现类型的单例模式的消费代码:

System.out.println("Name:" + JerrySingletonAnotherApproach.INSTANCE.getName());

若是攻击者经过前面介绍的反射代码对这种实现方式的单例进行攻击,JDK会抛出NoSuchMethodException异常:

Exception in thread "main" java.lang.NoSuchMethodException: singleton.JerrySingletonAnotherApproach.<init>()

at java.lang.Class.getConstructor0(Class.java:3082)

at java.lang.Class.getDeclaredConstructor(Class.java:2178)

at singleton.SingletonAttack.test3(SingletonAttack.java:31)

at singleton.SingletonAttack.main(SingletonAttack.java:43)

究其缘由,是由于如今咱们是经过Java枚举方式实现的单例,枚举类没有传统意义上的构造函数,所以对这种反射攻击免疫。

要获取更多Jerry的原创技术文章,请关注公众号"汪子熙"或者扫描下面二维码:

相关文章
相关标签/搜索