1. 程式人生 > >java安全編碼指南之:Mutability可變性

java安全編碼指南之:Mutability可變性

[toc] # 簡介 mutable(可變)和immutable(不可變)物件是我們在java程式編寫的過程中經常會使用到的。 可變型別物件就是說,物件在建立之後,其內部的資料可能會被修改。所以它的安全性沒有保證。 而不可變型別物件就是說,物件一旦建立之後,其內部的資料就不能夠被修改,我們可以完全相信這個物件。 雖然mutable物件安全性不夠,但是因為其可以被修改,所以會有效的減少對該物件的拷貝。 而immutable物件因為不可改變,所以嘗試對該物件的修改都會導致物件的拷貝,從而生成新的物件。 我們最常使用的String就是一個immutable物件。 那麼可變性在java的安全編碼中的最佳實踐是怎麼樣的呢? 一起來看看吧。 # 可變物件和不可變物件 知道了可變物件和不可變物件的不同之處之後,我們看一下怎麼才能判斷這個物件是可變物件還是不可變物件呢? 首先,最簡單的一點就是,不可變物件建立之後就不能夠被修改,所以不可變物件裡面基本上沒有setXXX之類的方法,而可變物件提供了setXXX這些可以修改內部變數狀態的方法。 看一個例子java.util.Date是一個可變物件,而java.time.LocalTime是不可變物件。 看下他們的方法定義有什麼區別呢? ![](https://img-blog.csdnimg.cn/20200716175742739.png?x-oss-process=image/watermark,type_ZmFuZ3poZW5naGVpdGk,shadow_0,text_aHR0cDovL3d3dy5mbHlkZWFuLmNvbQ==,size_30,color_8F8F8F,t_70) 首先是Date,我們可以看到在其中定義了很多setXXX方法。 ![](https://img-blog.csdnimg.cn/20200716175759787.png?x-oss-process=image/watermark,type_ZmFuZ3poZW5naGVpdGk,shadow_0,text_aHR0cDovL3d3dy5mbHlkZWFuLmNvbQ==,size_30,color_8F8F8F,t_70) 而在LocalTime中,我們基本上看不到setXXX方法。 同時不可變物件的欄位基本上都是final的,防止被二次修改。 第二,不可變物件一般來說是不可繼承的,在java中就是以final關鍵字做限定的: ~~~java public class Date public final class LocalTime ~~~ 第三,不可變物件一般會隱藏建構函式,而是使用類似工廠模式的方法來建立物件,這樣為例項的建立提供了更多的機動性。 # 建立mutable物件的拷貝 那麼如果我們想使用mutable物件,又不想被別人修改怎麼辦呢? 簡單的辦法就是拷貝一份要使用的物件: ~~~java public class CopyOutput { private final java.util.Date date; ... public java.util.Date getDate() { return (java.util.Date)date.clone(); } } ~~~ 這裡大家還要注意深拷貝和淺拷貝的問題。 # 為mutable類建立copy方法 既然要為mutable物件建立拷貝,那麼相應的mutable類也需要提供一個copy方法來協助拷貝。 這裡需要考慮一個深拷貝和淺拷貝的問題。 # 不要相信equals 我們知道在HashMap中怎麼去查詢一個key呢?先去找這個key的hash值,然後去判斷key.equals方法是否相等,考慮下面這種情況: ~~~java private final Map extras = new HashMap<>(); public void op(Window window) { Extra extra = extras.get(window); } ~~~ op方法接收一個Window物件,然後將其當成key從HashMap中取出對應的value。 如果,這個時候,我們有一個類A繼承了Window,並且hash值和equals都和另外一個Window物件B相同,那麼使用A這個key可以獲取到B這個key儲存的資料! 怎麼解決這個問題呢? Java中有一個特別的HashMap:IdentityHashMap,這個Map的key和value比較是用==而不是equals方法,所以可以有效的避免上面出現的問題。 ~~~java private final Map extras = new IdentityHashMap<>(); public void op(Window window) { Extra extra = extras.get(window); } ~~~ 如果沒有這樣的Map可用,那麼可以使用不可變物件作為key或者使用Window的私有變數,從而惡意攻擊者無法獲得這個變數。 ~~~java public class Window { /* pp */ class PrivateKey { Window getWindow() { return Window.this; } } final PrivateKey privateKey = new PrivateKey(); private final Map extras = new WeakHashMap<>(); ... } public class WindowOps { public void op(Window window) { // Window.equals may be overridden, // but safe as we don't use it. Extra extra = extras.get(window.privateKey); ... } } ~~~ # 不要直接暴露可修改的屬性 如果一個可變類中的某個屬性確實需要暴露被外部使用,那麼一定要將這個屬性定義為private,並且使用wrapper方法將其包裝起來。 如果直接暴露出去,那麼基本上就沒有許可權控制可言,任何程式只要能夠拿到你這個物件,就可以對屬性進行修改。考慮下下面的應用方式,我們在修改state的方法中加入了一個引數校驗和許可權控制。 ~~~java public final class WrappedState { // private immutable object private String state; // wrapper method public String getState() { return state; } // wrapper method public void setState(final String newState) { this.state = requireValidation(newState); } private static String requireValidation(final String state) { if (...) { throw new IllegalArgumentException("..."); } return state; } } ~~~ # public static fields應該被置位final 同樣的,如果你是一個類變數,當然不希望這個變數會被任何人修改,那麼需要將其置位final。 ~~~java public class Files { public static final String separator = "/"; public static final String pathSeparator = ":"; } ~~~ # public static final field 應該是不可變的 如果類變數是public static final的,那麼這個變數一定要是不可變的。 有人會問了,都定義成了final了,是不是就已經不可變了? 其實不然,比如我們定義了一個final的List,雖然這個list不能變化,但是list裡面的值是可以變化的。我們需要將可變變數修改為不可變變數,如下所示: ~~~java import static java.util.Arrays.asList; import static java.util.Collections.unmodifiableList; ... public static final List names = unmodifiableList(asList( "Fred", "Jim", "Sheila" )); ~~~ 如果使用JDK9中引入的of()或者ofEntries()方法,可以直接建立不可修改的集合: ~~~java public static final List names = List.of("Fred", "Jim", "Sheila"); ~~~ > 本文已收錄於 [http://www.flydean.com/java-security-code-line-mutability/](http://www.flydean.com/java-security-code-line-mutability/) > > 最通俗的解讀,最深刻的乾貨,最簡潔的教程,眾多你不知道的小技巧等你來發現! > > 歡迎關注我的公眾號:「程式那些事」,懂技術,更