反射攻击演示(一) 饿汉式
- 通过Class对象获得HungrySingleton的Constructor对象constructor;
- 通过constructor修改HungrySingleton的私有构造器的访问权限;
- 通过constructor.newInstance()创建新的对象,得到HungrySinleton的两个实例,从而打破单例模式;
public class HungrySingleton {
private final static HungrySingleton hungrySingleton;
static {
hungrySingleton = new HungrySingleton();
}
private HungrySingleton() {}
public static HungrySingleton getInstance() {
return hungrySingleton;
}
}
public class Test {
public static void main(String[] args) throws Exception{
Class objectClass = HungrySingleton.class;
Constructor constructor = objectClass.getDeclaredConstructor();
constructor.setAccessible(true);
HungrySingleton instance = HungrySingleton.getInstance();
HungrySingleton newInstance = (HungrySingleton) constructor.newInstance();
System.out.println(instance);
System.out.println(newInstance);
System.out.println(instance == newInstance);
}
}
输出:
designpattern.creational.singleton.reflection.HungrySingleton@52cc8049
designpattern.creational.singleton.reflection.HungrySingleton@5b6f7412
false
解决方案
- 在私有构造器中做判断,如果 hungrySingleton 已经被实例化了,就抛出异常;
public class HungrySingleton {
private final static HungrySingleton hungrySingleton;
static {
hungrySingleton = new HungrySingleton();
}
private HungrySingleton() {
if (hungrySingleton != null) {
throw new RuntimeException("单例构造器禁止反射调用");
}
}
public static HungrySingleton getInstance() {
return hungrySingleton;
}
}
public class Test {
public static void main(String[] args) throws Exception{
Class objectClass = HungrySingleton.class;
Constructor constructor = objectClass.getDeclaredConstructor();
constructor.setAccessible(true);
HungrySingleton instance = HungrySingleton.getInstance();
HungrySingleton newInstance = (HungrySingleton) constructor.newInstance();
System.out.println(instance);
System.out.println(newInstance);
System.out.println(instance == newInstance);
}
}
输出:
Exception in thread "main" 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 designpattern.creational.singleton.reflection.Test.main(Test.java:12)
Caused by: java.lang.RuntimeException: 单例构造器禁止反射调用
at designpattern.creational.singleton.reflection.HungrySingleton.<init>(HungrySingleton.java:13)
... 5 more
反射攻击演示(二) 静态内部类
public class StaticInnerClassSingleton {
private static class InnerClass {
private static StaticInnerClassSingleton staticInnerClassSingleton =
new StaticInnerClassSingleton();
}
public static StaticInnerClassSingleton getInstance() {
return InnerClass.staticInnerClassSingleton;
}
private StaticInnerClassSingleton() {}
}
public class Test {
public static void main(String[] args) throws Exception{
staticInnerClass();
}
private static void staticInnerClass() throws Exception {
Class objectClass = StaticInnerClassSingleton.class;
Constructor constructor = objectClass.getDeclaredConstructor();
constructor.setAccessible(true);
StaticInnerClassSingleton instance = StaticInnerClassSingleton.getInstance();
StaticInnerClassSingleton newInstance = (StaticInnerClassSingleton) constructor.newInstance();
System.out.println(instance);
System.out.println(newInstance);
System.out.println(instance == newInstance);
}
}
输出:
designpattern.creational.singleton.reflection.StaticInnerClassSingleton@52cc8049
designpattern.creational.singleton.reflection.StaticInnerClassSingleton@5b6f7412
false
解决方案
- 在私有构造器中做判断,如果 staticInnerClassSingleton 已经被实例化了,就抛出异常;
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("单例构造器禁止反射调用");
}
}
}
public class Test {
public static void main(String[] args) throws Exception{
staticInnerClass();
}
private static void staticInnerClass() throws Exception {
Class objectClass = StaticInnerClassSingleton.class;
Constructor constructor = objectClass.getDeclaredConstructor();
constructor.setAccessible(true);
StaticInnerClassSingleton instance = StaticInnerClassSingleton.getInstance();
StaticInnerClassSingleton newInstance = (StaticInnerClassSingleton) constructor.newInstance();
System.out.println(instance);
System.out.println(newInstance);
System.out.println(instance == newInstance);
}
}
输出:
Exception in thread "main" 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 designpattern.creational.singleton.reflection.Test.staticInnerClass(Test.java:16)
at designpattern.creational.singleton.reflection.Test.main(Test.java:7)
Caused by: java.lang.RuntimeException: 单例构造器禁止反射调用
at designpattern.creational.singleton.reflection.StaticInnerClassSingleton.<init>(StaticInnerClassSingleton.java:16)
... 6 more
注意:
- 以上两种情况都属于单例在类被加载的时候单例对象就初始化好了,这这两种情况下,都可以使用在私有构造器中注入防御代码的方式防止反射攻击;
反射攻击演示(三) 懒汉式 无效的防御代码1
- 即使懒汉式的代码中注入防御代码也是不一定能防止单例类创建2个以上的实例;
- 如果反射攻击发生在正常调用之前,每次反射攻击都可以获取单例类的一个实例,因为即使私有构造器中使用了单例类(LazySingleton )的静态成员(lazySingleton )从而触发了单例类(LazySingleton)的初始化,但单例对象并没有在类的初始化阶段被实例化,所以防御代码不生效,从而可以通过构造器的反射调用创建单例类的多个实例;
- 如果反射攻击发生在正常调用之后,防御代码是可以生效的;
public class LazySingleton {
private static LazySingleton lazySingleton = null;
private LazySingleton(){
if (lazySingleton != null) {
throw new RuntimeException("单例构造器不允许反射调用");
}
}
public static LazySingleton getInstance() {
if (lazySingleton == null) {
lazySingleton = new LazySingleton();
}
return lazySingleton;
}
}
public class Test {
public static void main(String[] args) throws Exception{
Class objectClass = LazySingleton.class;
Constructor constructor = objectClass.getDeclaredConstructor();
constructor.setAccessible(true);
LazySingleton newInstance = (LazySingleton) constructor.newInstance();
LazySingleton instance = LazySingleton.getInstance();
System.out.println(instance);
System.out.println(newInstance);
System.out.println(instance == newInstance);
}
}
输出:
designpattern.creational.singleton.reflection.lazy.LazySingleton@52cc8049
designpattern.creational.singleton.reflection.lazy.LazySingleton@5b6f7412
false
反射攻击演示(三) 懒汉式 无效的防御代码2
- 增加了一个控制器flag,控制私有构造器只能被正常调用一次;
- 但是在创建新实例之前,通过反射控制flag的访问权限和值,还是能创建出新的实例;
public class LazySingleton {
private static LazySingleton lazySingleton = null;
private static boolean flag = true;
private LazySingleton(){
if (flag) {
flag = false;
} else {
throw new RuntimeException("单例构造器不允许反射调用");
}
}
public static LazySingleton getInstance() {
if (lazySingleton == null) {
lazySingleton = new LazySingleton();
}
return lazySingleton;
}
}
public class Test {
public static void main(String[] args) throws Exception{
Class objectClass = LazySingleton.class;
Constructor constructor = objectClass.getDeclaredConstructor();
constructor.setAccessible(true);
LazySingleton instance = LazySingleton.getInstance();
Field flag = objectClass.getDeclaredField("flag");
flag.setAccessible(true);
flag.set(instance, true);
LazySingleton newInstance = (LazySingleton) constructor.newInstance();
System.out.println(instance);
System.out.println(newInstance);
System.out.println(instance == newInstance);
}
}
输出:
designpattern.creational.singleton.reflection.lazy.defenseless2.LazySingleton@27973e9b
designpattern.creational.singleton.reflection.lazy.defenseless2.LazySingleton@312b1dae
false