1. 程式人生 > >java安全編碼指南之:基礎篇

java安全編碼指南之:基礎篇

[toc] # 簡介 作為一個程式設計師,只是寫出好用的程式碼是不夠的,我們還需要考慮到程式的安全性。在這個不能跟陌生人說話世界,扶老奶奶過馬路都是一件很困難的事情。那麼對於程式設計師來說,尤其是對於開發那種對外可以公開訪問的網站的程式設計師,要承受的壓力會大很多。 任何人都可以訪問我們的系統,也就意味著如果我們的系統不夠健壯,或者有些漏洞,惡意攻擊者就會破門而入,將我們辛辛苦苦寫的程式蹂躪的體無完膚。 所以,安全很重要,今天本文將會探討一下java中的安全編碼指南。 # java平臺本身的安全性 作為一個強型別語言,java平臺本身已經儘可能的考慮到了安全性的,為我們遮蔽了大多數安全性的細節。 比如可以為不同級別許可權的程式碼提供受限的執行環境。 java程式是型別安全的,並且在執行時提供了自動記憶體管理和陣列邊界檢查,Java會盡可能的及早發現程式中的問題,從而使Java程式具有很高的抵抗堆疊破壞的能力。 儘管Java安全體系結構在許多情況下可以幫助保護使用者和系統免受惡意程式碼或行為不當的攻擊,但它無法防禦可信任程式碼中發生的錯誤。也就說如果是使用者本身程式碼的漏洞,java安全體系是無法進行判斷的。 這些錯誤可能會繞過java本身的安全體系結構。在嚴重的情況下,可能會執行本地程式或禁用Java安全性。從而會被用來從計算機和Intranet竊取機密資料,濫用系統資源,阻止計算機的有用操作,協助進一步的攻擊以及許多其他惡意活動。 所以,最大的安全在程式設計師本身,不管外部機制如何強大,如果核心的程式設計師出了問題,那麼一切都將歸於虛無。 接下來,我們看下java程式設計師應該遵循一些什麼行為準則,來保證程式的安全性呢? # 安全第一,不要寫聰明的程式碼 我們可能會在很多教科書甚至是JDK的原始碼中,看到很多讓人驚歎的程式碼寫法,如果你真的真的明白你在做什麼,那麼這樣寫沒什麼問題。但是很多情況下我們並不是很瞭解這樣寫的原理,甚至不知道這樣寫會出現什麼樣的問題。 並且現代系統是一個多人協作的過程,如果你寫了這樣的聰明程式碼,很有可能別人看不懂,最後導致未知的系統問題。 給大家舉個例子: ~~~shell :(){:|:&};: ~~~ 上面是一個shell下面的fork炸彈,如果你在shell下面執行上面的程式碼,幾秒之後系統就會宕機或者執行出錯。 怎麼分析上面的程式碼呢?我們把程式碼展開: ~~~shell :() { :|:& }; : ~~~ 還是不明白? 我們把:替換成函式名: ~~~shell fork() { fork|fork& }; fork ~~~ 上面的程式碼就是無限的fork程序,通過幾何級數的增長,最後導致程式崩潰。 java設計的很多大神把他們跳躍般的思想寫到了JDK原始碼裡面,大神們的思想經過了千錘百煉,並且JDK是Java的核心,裡面的程式碼再優化也不為過。 但是現在硬體技術的發展,程式碼級別的優化可能作用已經比較少了。為了避免出現不可知的安全問題,還是建議大家編寫一眼就能看出邏輯的程式碼。雖然可能不是那麼快,但是安全性有了保證。除非你真的知道你在做什麼。 # 在程式碼設計之初就考慮安全性 安全性應該是一個在編寫程式碼過程中非常重要的標準,我們在設計程式碼的時候就應該考慮到相關的安全性問題,否則後面重構起來會非常費事。 舉個例子: ~~~java public final class SensitiveClass { private final Behavior behavior; // Hide constructor. private SensitiveClass(Behavior behavior) { this.behavior = behavior; } // Guarded construction. public static SensitiveClass newSensitiveClass(Behavior behavior) { // ... validate any arguments ... // ... perform security checks ... return new SensitiveClass(behavior); } } ~~~ 上面的例子中我們使用了final關鍵字來防止我們的某些關鍵類被繼承擴充套件。因為沒有擴充套件性,所以安全性判斷會更加容易。 同時,java提供了SecurityManager和一系列的Permission類,通過合理的配置,我們可以有效的控制java程式的訪問許可權。 # 避免重複的程式碼 和重複程式碼相關的一個關鍵詞就是重構。為什麼會出現重複程式碼呢? 很簡單,最開始我們在實現一個功能的時候寫了一段程式碼邏輯。結果後面還有一個方法要使用這段程式碼邏輯。然後我們為了圖方便,就把程式碼邏輯拷貝過去了。 看起來問題好像解決了。但是一旦這段業務邏輯要修改,那可就是非常麻煩的一件事情。因為我們需要找到程式中所有出現這段程式碼的地方,然後一個一個的修改。 為什麼不把這段程式碼提取出來,做成一個單獨的方法來供其他的方法呼叫呢?這樣即使後面需要修改,也只用修改一處地方即可。 在現實的工作中,我們經常會遇到這種問題,尤其是那種年久失修的程式碼,大家都不敢修改,因為牽一髮而動全身。往往是修改了這邊忘記了那邊,最後導致bug重重。 # 限制許可權 JDK專門提供了一個SecurityManager類,來顯示的對安全性進行控制,我們看下SecurityManager是怎麼使用的: ~~~java SecurityManager security = System.getSecurityManager(); if (security != null) { security.checkXXX(argument, ...); } ~~~ SecurityManager提供了一系列的check方法,來對許可權進行控制。 許可權分為以下類別:檔案、套接字、網路、安全性、執行時、屬性、AWT、反射和可序列化。管理各種許可權類別的類是 :   java.io.FilePermission、   java.net.SocketPermission、   java.net.NetPermission、   java.security.SecurityPermission、   java.lang.RuntimePermission、   java.util.PropertyPermission、   java.awt.AWTPermission、   java.lang.reflect.ReflectPermission   java.io.SerializablePermission JDK本身已經使用了很多這些許可權控制的程式碼。比如說我們最常用的File: ~~~java public boolean canRead() { SecurityManager security = System.getSecurityManager(); if (security != null) { security.checkRead(path); } if (isInvalid()) { return false; } return fs.checkAccess(this, FileSystem.ACCESS_READ); } ~~~ 上面是File類的canRead方法,我們會首先去判斷是否配置了SecurityManager,如果配置了,則去檢查是否可以read。 如果我們在寫程式碼中,遇到檔案、套接字、網路、安全性、執行時、屬性、AWT、反射和可序列化相關的操作時,也可以考慮使用SecurityManager來進行細粒度的許可權控制。 # 構建可信邊界 什麼是可信邊界呢?邊界主要起攔截作用,邊界裡邊的我們可以信任,邊界外邊的我們就不能信任了。 對於不能信任的外邊界請求,我們需要進行足夠的安全訪問控制。 比如說web客戶端來訪問web伺服器。web客戶端是在全球各地的,各種環境都有,並且是不可控的,所以web客戶端訪問web伺服器端的請求需要進行額外的安全控制。 而web伺服器訪問業務伺服器又是不同的,因為web伺服器是我們自己控制的,所以安全程度相對較高,我們需要針對不同的可信邊界做不同的控制。 # 封裝 封裝(Encapsulation)是指一種將抽象性函式介面的實現細節部份包裝、隱藏起來的方法。 封裝可以被認為是一個保護屏障,防止該類的程式碼和資料被外部類定義的程式碼隨機訪問。通過對介面進行訪問控制,可以嚴格的包含類中的資料和方法。 並且封裝可以減少耦合,並且隱藏實現細節。 # 寫文件 最後一項也是非常非常重要的一項就是寫文件。為什麼接別人的老專案那麼痛苦,為什麼讀原始碼那麼困難。根本的原因就是沒有寫文件。 如果不寫文件,可能你自己寫的程式碼過一段時間之後也不知道為什麼當時這樣寫了。 所以,寫文件很重要。 > 本文已收錄於 [http://www.flydean.com/java-security-code-line-base/](http://www.flydean.com/java-security-code-line-base/) > > 最通俗的解讀,最深刻的乾貨,最簡潔的教程,眾多你不知道的小技巧等你來發現! > > 歡迎關注我的公眾號:「程式那些事」,懂技術,更懂你!