乐趣区

关于java:java安全编码指南之对象构建

简介

程序员必定是不缺对象的,因为随时都能够构建一个,对象多了必定会呈现点平安问题,一起来看看在 java 的对象构建中怎么保障对象的安全性吧。

构造函数的异样

思考上面的一个例子:

public class SensitiveOperation {public SensitiveOperation(){if(!doSecurityCheck()){throw new SecurityException("Security check failed!");
        }
    }

    //Security check return false
    private boolean doSecurityCheck(){return false;}

    public void storeMoney(){System.out.println("Store 1000000 RMB!");
    }
}

下面的例子中,咱们在构造函数中做了一个 securityCheck,因为这个 securityCheck 返回的值是 false,所以会抛出 SecurityException。

看下调用的例子:

    public static void main(String[] args) {SensitiveOperation sensitiveOperation = new SensitiveOperation();
        sensitiveOperation.storeMoney();}

这个调用会抛出上面的异样:

Exception in thread "main" java.lang.SecurityException: Security check failed!
    at com.flydean.SensitiveOperation.<init>(SensitiveOperation.java:11)
    at com.flydean.SensitiveUsage.main(SensitiveUsage.java:10)

那么问题来了,下面的这个 class 是不是平安的呢?

Finalizer Attack

下面的 class 不是 final 的,所以咱们能够结构一个 class 去继承它。而后思考这样一个问题,当构造函数抛出异样之后,会执行什么操作呢?

如果该对象曾经被构建了,那么这个对象在 GC 的时候须要执行 finalize 办法。那么咱们是不是能够在 finalize 办法中绕过安全检查呢?

看上面的例子:

public class SensitiveOperationFinalizer extends  SensitiveOperation{public SensitiveOperationFinalizer(){ }

    @Override
    protected void finalize() {System.out.println("We can still do store Money action!");
        this.storeMoney();
        System.exit(0);
    }
}

上的例子中,咱们继承了 SensitiveOperation,并且实现了 finalize 办法,在 finalize 中,咱们调用了 storeMoney。看下运行的代码:

    public void testFinalizer() throws InterruptedException {
        try {SensitiveOperation sensitiveOperation = new SensitiveOperationFinalizer();
            sensitiveOperation.storeMoney();}catch (Exception e){System.out.println(e.getMessage());
        }
        System.gc();
        Thread.sleep(10000);
    }

运行后果:

Security check failed!
We can still do store Money action!
Store 1000000 RMB!

能够看到,尽管咱们构造函数抛出了异样,然而 storeMoney 的操作还是被执行了!

这个操作就叫做 Finalizer Attack。

解决 Finalizer Attack

怎么解决这个构造函数抛出异样的问题呢?这里给大家介绍几种解决办法。

应用 final class

如果应用 final class,那么类是不可能被继承的,问题天然就解决了。

public final class SensitiveOperationFinal {public SensitiveOperationFinal(){if(!doSecurityCheck()){throw new SecurityException("Security check failed!");
        }
    }

    //Security check return false
    private boolean doSecurityCheck(){return false;}

    public void storeMoney(){System.out.println("Store 1000000 RMB!");
    }
}

应用 final finalize 办法

因为子类想要重写 finalize 办法,如果咱们的父类中 finalize 办法定义为 final,也能够解决这个问题。

public final class SensitiveOperationFinal {public SensitiveOperationFinal(){if(!doSecurityCheck()){throw new SecurityException("Security check failed!");
        }
    }

    //Security check return false
    private boolean doSecurityCheck(){return false;}

    public void storeMoney(){System.out.println("Store 1000000 RMB!");
    }
    
    final protected void finalize() {}
}

应用 flag 变量

咱们能够在对象构建结束的时候设置一个 flag 变量,而后在每次平安操作的时候都去判断一下这个 flag 变量,这样也能够防止之前提到的问题:

public class SensitiveOperationFlag {

    private volatile boolean flag= false;

    public SensitiveOperationFlag(){if(!doSecurityCheck()){throw new SecurityException("Security check failed!");
        }
        flag=true;
    }

    //Security check return false
    private boolean doSecurityCheck(){return false;}

    public void storeMoney(){if(!flag){System.out.println("Object is not initiated yet!");
            return;
        }
        System.out.println("Store 1000000 RMB!");
    }
}

留神,这里 flag 须要设置为 volatile,只有这样能力保障构造函数在 flag 设置之前执行。也就是说须要保障 happens-before 个性。

应用 this 或者 super

在 JDK6 或者更高版本中,如果对象的构造函数在 java.lang.Object 构造函数退出之前引发异样,则 JVM 将不会执行该对象的 finalize 办法。

因为 Java 确保 java.lang.Object 构造函数在任何构造函数的第一条语句之上或之前执行。如果构造函数中的第一个语句是对超类的构造函数或同一个类中的另一个构造函数的调用,则 java.lang.Object 构造函数将在该调用中的某个地位执行。否则,Java 将在该构造函数的代码中的任何一个执行之前执行超类的默认构造函数,并且将通过隐式调用执行 java.lang.Object 构造函数。

也就是说如果异样产生在构造函数中的第一条 this 或者 super 中的时候,JVM 将不会调用对象的 finalize 办法:

public class SensitiveOperationThis {public SensitiveOperationThis(){this(doSecurityCheck());
    }

    private SensitiveOperationThis(boolean secure) { }

    //Security check return false
    private static boolean doSecurityCheck(){throw new SecurityException("Security check failed!");
    }

    public void storeMoney(){System.out.println("Store 1000000 RMB!");
    }
}

本文的例子:

learn-java-base-9-to-20/tree/master/security

本文已收录于 http://www.flydean.com/java-security-code-line-object/

最艰深的解读,最粗浅的干货,最简洁的教程,泛滥你不晓得的小技巧等你来发现!

欢送关注我的公众号:「程序那些事」, 懂技术,更懂你!

退出移动版