java安全编程标准笔记(一)
一、表达式规范
1. 确保使用正确的类型来自动封装数值
简介
自动封装是java中基本类型与其封装类型的相关转换的操作,但有时如果不注意会产生意想不到的情况,如下代码,我们期望的结果应该是集合的大小应该为1,然而实际并非如此.
public static void main(String[] args) {
HashSet<Short> s = new HashSet<>();
for (short i = 0; i < 100; i++) {
s.add(i);
s.remove(i-1);
}
System.out.println(s.size()); //打印结果为100
}
问题说明
在操作 i-1
的时候,将short
和int
类型的值结合起来,它会产生自动类型转换,将其转换到一个int
类型,在存到集合时,转成Integer
类型,而不是转成short
类型,因为这个HashSet
集合只有Short
类型,所以这个移除Integer
类型的动作就没有发生。
修正办法
public static void main(String[] args) {
HashSet<Short> s = new HashSet<>();
for (short i = 0; i < 100; i++) {
s.add(i);
s.remove((short)(i-1));
}
System.out.println(s.size()); 打印结果为1
}
2、确保构造函数不会调用可覆写的方法
简介
在创建对象时用可覆写的方法可能造成使用未初始化的数据,进而导致执行异常或者预期外的结果。在构造函数中调用可覆写的方法也会在对象创建完成前泄露this
引用,这使得其他线程可能访问到未初始化或不一致的数据。
class SuperClass{
public SuperClass() {
doLogic();
}
public void doLogic() {
System.out.println("this is super class");
}
}
class SubClass extends SuperClass{
private String color = null;
SubClass(){
super();
color = "red";
}
public void doLogic() {
System.out.println("this is sub class,color: " + color);
}
}
public class Main {
public static void main(String[] args) {
SuperClass superClass = new SuperClass(); //this is super class
SubClass subClass = new SubClass();//this is sub class,color: null
subClass.doLogic();//this is sub class,color: red
superClass.doLogic();//this is super class
}
}
问题说明
方法doLogic()
是在基类的构造函数中调用的。当直接创建基类的实例时,将调用基类中的方法doLogic()
,从而正确的完成执行。然而,当子类发起基类的创建时,却调用子类的doLogic()
方法。这种情况下,因为子类的构造函数还有完成执行,所以color
的值为null
;
修正办法
符合规则的方案是把基类的doLogic()
声明为private
,从而确保此方法不会被覆写。总结为构造函数只能调用fina
l或者private
修饰的方法。
二、方法规范
1.确保比较等同的对象能得到相等的结果
简介
一个对象是同时以它的标识(内存中的位置)和它的状态(实际的数据)作为特征的。操作符 ==
只是比较两个对象的标识;java.lang.Object
默认的比较是实现比较对象标识,如果一个类定义了equals()
方法,则意味着覆写了java.lang.Objec
t中定义的方法,会用来比较对象的状态。当一个类中没有自定义的equals()
方法时,在比较时会调用Object
类中的方法。
java语言规范指定的equals()
的通常合约用法有5点要求:
1)方法是自反的:对于任何引用x
,x.equals(x)
必须返回true
。
2)方法是对称的:对于任何引用x
和y
,x.equals(y)
必须返回true,当且仅当y.equals(x)
返回true
。
3)方法是可传递的:对于任何引用x
,y
和z
,如果x.equals(y)
返回true
并且y.equals(z)
返回true
,则x.equals(z)
返回true
。
4)方法是一致的:只要用在equals()
中比较的对象信息没有改变,对于任何引用x
和y
,多次调用x.equals(y)
总是返回true
或者总是返回false
。
5)对于任何非空的引用,x.equals(null)
总是返回false。
错误示例一
以下代码违反了对称性
public class Sub {
private String s;
public Sub(String s) {
if(s == null) {
throw new NullPointerException();
}
this.s = s;
}
public boolean equals(Object o) {
if(o instanceof Sub) {
return s.equalsIgnoreCase(((Sub) o).s);
}
if(o instanceof String) {
return s.equalsIgnoreCase((String)o);
}
return false;
}
public static void main(String[] args) {
// TODO Auto-generated method stub
Sub sub = new Sub("Java");
String s = "java";
System.out.println(sub.equals(s)); //输出true
System.out.println(s.equals(sub)); //输出false
}
}
问题说明
覆写的equals()
方法违反了第二条合约要求(对称)。
修正办法
覆写的equals()
方法只允许传入该类的对象,保证比较的对象实例是同一个对象
public class Sub {
private String s;
public Sub(String s) {
if(s == null) {
throw new NullPointerException();
}
this.s = s;
}
public boolean equals(Object o) {
return (o instanceof Sub) && ((Sub)o.s.equalsIgnoreCase(s));
}
public static void main(String[] args) {
// TODO Auto-generated method stub
Sub sub = new Sub("Java");
String s = "java";
System.out.println(sub.equals(s)); //输出false
System.out.println(s.equals(sub)); //输出false
}
}
错误示例二
统一资源定位器(URL
)指定了资源的地址和访问资源的方法。根据Java API中URL
类的文档描述:
如果两个URL
对象有相同的协议、引用、等价的主机、主机端口、相同的文件及文件片段,则这两个URL
是相等的。
在以下的情况中,认为两个主机是等同的:当能够将两个主机的名字解析成同样的IP地址时(DNS
解析);或者当其中一个主机名字不能解析时,两个主机名在不考虑大小写时是相同的;或者两个主机名都等于null
。
1)对于HTTP的虚拟机来说,为equals()
方法定义的行为是不一致的。虚拟主机允许一个网络服务器可以在一台电脑上处理多个网站,并有时会共享相同的IP地址。遗憾的是,在设计URL
类的时候没有考虑到这种技术。因此,当两个不同的URL
被解析成相同的IP地址时,URL
类认为它们是相等的。
2)另一个对URL
对象使用equals()
方法带来的风险是该方法在有网络连接和没有网络连接时,使用的逻辑是不同的。当连接到网络时,该方法遵循java API中描述的步骤;当没有网络连接时,该方法对两个URL
进行字符串进行比较。从而,URL.equals()
方法违反了一致性约定的要求。
public class Filter{
public static main(String[] args) throws MalformedURLException {
final URL allow = new URL("http://mailwebsite.com");
if (!allow.equals(new URL(args[0]))) {
throw new SecurityException("Access Denied");
}
}
}
修正办法
方法一
这个解决方案还是存在问题。两个有着不同字符串表达的URL还是可能指向同一个资源的。然而,因为维持了equals()
的合约,所以这个方案在这种情况下会安全地终止任务,并且系统不允许错误地接受一个恶意的URL
public class Filter{
public static main(String[] args) throws MalformedURLException {
final URL allow = new URL("http://mailwebsite.com");
if (!allow.toString().equals(new URL(args[0]).toString())) {
throw new SecurityException("Access Denied");
}
}
}
方法二
统一资源定位符(URI
)包含一个字符串来定位资源;这是一个比URL
范围更广的概念。Java.net.URL
类提供了基于字符串的equals()
和hashCode()
方法来满足基类方法大体上的合约;它们不会调用主机名解析而且不会受网络连接的影响。并且,URL.toURI()
和URI.toURL()
方法方便的在这个两个类之间进行转换。程序应尽可能的使用URI
。
public class Filter{
public static main(String[] args) throws MalformedURLException,URISyntaxException {
final URI allow = new URI("http://mailwebsite.com");
if (!allow.toString().equals(new URI(args[0]).toString())) {
throw new SecurityException("Access Denied");
}
}
}