指南3:为敏感可变类提供不可修改的包装器
字段的不变性可以防止其被意外修改以及恶意篡改,因此在接受输入或返回值时,防御性复制不可变字段是不必要的。然而,部分敏感类由于某些原因必须要被改变。幸运的是,可以通过不可修改的包装器,将可变类的只读访问权限授予不可信代码。例如,Collection(集合)类包括一组包装器,允许客户端观察一个不可修改的集合对象视图。
违规代码示例
在下面的违规代码示例中,Mutable这个类允许内部数组对象被修改:
class Mutable {
private int[] array = new int[10];
public int[] getArray() {
return array;
}
public void setArray(int[] i) {
array = i;
}
}
// ...```
private Mutable mutable = new Mutable();
public Mutable getMutable() {return mutable;}
不可信的调用程序能调用setArray()方法,这违反了对象的不变性属性。调用getArray()方法还允许修改该类的私有内部状态。此外,这个类还违反了《The CERT® Oracle® Secure Coding Standard for Java™》[Long 2012]的“OBJ05-J. Defensively copy private mutable class members before returning their references”。
####违规代码示例
在下面的违规代码示例中,MutableProtector继承了Mutable类。
class MutableProtector extends Mutable {
@Override
public int[] getArray() {
return super.getArray().clone();
}
}
// ...`
private Mutable mutable = new MutableProtector();
// May be safely invoked by untrusted caller having read ability
public Mutable getMutable() {return mutable;}
在这个类中,调用getArray()方法不允许修改该类的私有内部状态,符合“OBJ05-J. Defensively copy private mutable class members before returning their references”[Long 2012]。然而,不可信的调用程序能调用setArray()方法修改Mutable对象。
合规解决方案
一般来说,通过对核心接口定义的所有方法(包括赋值方法),提供合适的包装器,可以将敏感类转化为安全视图(safe-view)对象。赋值方法的包装器必须抛出UnsupportedOperationException异常,这样调用者就不太可能做出影响属性不变性的操作。
在下面的解决方案中,setArray()方法覆盖了Mutable.setArray()方法,防止了对Mutable对象的改变。
class MutableProtector extends Mutable {
@Override
public int[] getArray() {
return super.getArray().clone();
}
@Override
public void setArray(int[] i) {
throw new UnsupportedOperationException();
}
}
// ...```
private Mutable mutable = new MutableProtector();
// May be safely invoked by untrusted caller having read ability
public Mutable getMutable() {return mutable; }
MutableProtector类覆盖了Mutable类的getArray()方法,该方法克隆了Mutable类的原始数组。因此,尽管调用代码能够得到Mutable对象中的数组字段的数据,但原始数组是保持不变的,并且调用代码不能访问到原始数组。当调用者试图调用MutableProtector对象的setArray()方法时,覆盖后的setArray()方法会抛出一个异常。这样保证了MutableProtector对象可以被传递给不可信代码,因为该对象只允许读操作。
####适用性