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
以上這篇java安全編碼指南之:物件構建操作就是小編分享給大家的全部內容了,希望能給大家一個參考,也希望大家多多支援我們。