java是一種類型安全的語言,它有四類稱為安全沙箱機制的安全機制來保證語言的安全性,這四類安全沙箱分別是:
-
安全管理器及Java API
本篇博客主要介紹“類安全管理器及Java API”的基本原理,如需了解其它幾類安全機制可以通過上面的博客鏈接進入查看。
簡介
java安全沙箱的前三類保證了jvm所運行程序的完整性,使得jvm不會因為運行有漏洞或惡意的代碼而導致出現不可預期的狀態。而第四類沙箱模型是“類安全管理器及Java API”,它能保護jvm在運行有漏洞或惡意的代碼不會破壞外部資源。java通過稱為安全管理器的一類API來保證這類安全性。
安全策略文件
首先介紹下安全策略文件,如果啟用了安全管理器,默認會使用jre自帶的安全策略文件$JAVA_HOME/jre/lib/security/java.policy來指定訪問外部資源的權限,該策略文件也可以通過jvm參數-Djava.security.policy來指定。
Policy文件的主要格式如下:
keystore "some_keystore_url", "keystore_type"; grant [SignedBy "signer_names"] [, CodeBase "URL"] { Permission permission_class_name ["target_name"] [,"action"] [,SignedBy"signer_names"]; … … };
-
"keystore"記錄
一個keystore是一個私有密鑰(private keys)數據庫和相應的數字簽名,例如X.509證書。Policy文件中可能只有一條keystore記錄(也可能不含有該記錄),它可以出現在文件中grant記錄以外的任何地方。Policy配置文件中指定的keystores用於尋找grant記錄中指定的、簽名者的公共密鑰(public keys),如果任何grant記錄指定簽名者(signer_names),那么,keystore記錄必須出現在policy配置文件中。
"some_keystore_url"是指keystore的URL位置,"keystore_type"是指keystore的類型。第二個選項是可選項,如果沒有指定,該類型則假定由安全屬性文件(java.security)中的"keystore.type"屬性來確定。keystore類型定義了keystore信息的存儲和數據格式,用於保護keystore中的私有密鑰和keystore完整性的算法。Sun Microsystems支持的缺省類型為“JKS”。
-
"grant"記錄
在Policy文件中的每一個grant記錄含有一個CodeSource(指定代碼)及其permission(許可)。Policy文件中的每一條grant記錄遵循下面的格式,以保留字“grant”開頭,表示一條新的記錄的開始,“Permission”是另一個保留字,在記錄中用來標記一個新的許可的開始。每一個grant記錄授予一個指定的代碼(CodeBase)一套許可(Permissions)。permission_class_name必須是一個合格並存在的全限定性類名,例如java.io.FilePermission。
target_name用來指定目標類的位置,action用於指定目標類擁有的權限。 target_name可以直接指定類名(可以是絕對或相對路徑),目錄名,也可以使用通配符/、/*或着/-。
directory/ 表示directory目錄下的所有.class文件,不包括.jar文件
directory/* 表示directory目錄下的所有的.class及.jar文件
directory/- 表示directory目錄下的所有的.class及.jar文件,包括子目錄
下面是一個policy文件的demo:
grant codeBase "file:${{java.ext.dirs}}/*" { permission java.security.AllPermission; }; grant { permission java.lang.RuntimePermission "stopThread"; permission java.net.SocketPermission "localhost:1099", "listen"; permission java.util.PropertyPermission "java.version", "read"; ... ... };
例如:對於java.net.SocketPermission,action可以是:listen,accept,connect,read,write;對於java.io.FilePermission,action可以是:read, write, delete和execute。
安全管理器
java的安全管理器可以定制,也可以使用jdk的默認實現java.lang.SecurityManager,啟動安全管理器的話有兩種方式,一種是通過硬編碼的方式啟動,另外一種是通過jvm參數-Djava.security.manager啟動。
下面的測試用例都使用jre的默認policy文件配置:
grant { ... ... permission java.util.PropertyPermission "java.version", "read"; ... ... };
該策略文件指定了"java.version"的讀權限,然后並沒有指定寫權限。參考以下測試用例:
public static void main(String... args) { String javaVersion=System.getProperty("java.version"); System.err.println(javaVersion); System.setProperty("java.version","1.7.0_45"); String javaNewVersion=System.getProperty("java.version"); System.err.println(javaNewVersion); }
首先讀"java.version"屬性,然后把該屬性改寫為1.7.0_45,最后再讀取它並打印出來,輸出結果為:
1.8.0_45
1.7.0_45
可以看到默認的jdk版本為1.8.0_45(1.8.0是java的主版本號,45是次版本號)。
然后前面的policy文件只指定了read權限,為什么這里卻write成功了?那是因為默認情況下java並不啟動安全管理器,可以使用硬編碼System.setSecurityManager()來啟動安全管理器,參考以下測試用例:
public static void main(String... args) { // 啟用安全管理器 System.setSecurityManager(new SecurityManager()); String javaVersion=System.getProperty("java.version"); System.err.println(javaVersion); System.setProperty("java.version","1.7.0_45"); String javaNewVersion=System.getProperty("java.version"); System.err.println(javaNewVersion); }
此時的輸出結果為:
1.8.0_45 Exception in thread "main" java.security.AccessControlException: access denied ("java.util.PropertyPermission" "java.version" "write") at java.security.AccessControlContext.checkPermission(AccessControlContext.java:457) at java.security.AccessController.checkPermission(AccessController.java:884) at java.lang.SecurityManager.checkPermission(SecurityManager.java:549) at java.lang.System.setProperty(System.java:792) at test.Test.main(Test.java:9)
結果很明確:可以讀,但不能寫,我們可以來修改policy文件,讓它支持"java.version"的寫操作:
grant { ... ... permission java.util.PropertyPermission "java.version", "read"; permission java.util.PropertyPermission "java.version", "write"; ... ... };
此時再執行上面的用例,輸出結果為:
1.8.0_45
1.7.0_45
可以看到此時可以支持"java.version"的寫操作了。
另外使用jvm參數-Djava.security.manager也能啟用安全管理器,此時jvm啟動時會設置"java.security.manager"系統屬性為空字符串"",此時會在啟動sun.misc.Launcher時初始化安全管理器,查看sun.misc.Launcher的源碼:
public Launcher(){ ExtClassLoader extclassloader; try { extclassloader = ExtClassLoader.getExtClassLoader(); } catch(IOException ioexception) { throw new InternalError("Could not create extension class loader", ioexception); } try { loader = AppClassLoader.getAppClassLoader(extclassloader); } catch(IOException ioexception1) { throw new InternalError("Could not create application class loader", ioexception1); } Thread.currentThread().setContextClassLoader(loader); String s = System.getProperty("java.security.manager"); if(s != null) { SecurityManager securitymanager = null; if("".equals(s) || "default".equals(s)) securitymanager = new SecurityManager(); else try { securitymanager = (SecurityManager)loader.loadClass(s).newInstance(); } catch(IllegalAccessException illegalaccessexception) { } catch(InstantiationException instantiationexception) { } catch(ClassNotFoundException classnotfoundexception) { } catch(ClassCastException classcastexception) { } if(securitymanager != null) System.setSecurityManager(securitymanager); else throw new InternalError((new StringBuilder()).append("Could not create SecurityManager: ").append(s).toString()); } }
可以看到"java.security.manager"系統屬性為空字符串""時會啟用jdk的默認安全管理器SecurityManager。
Java API
java的安全機制api大部分都在java.security包下,因為源碼很多,就不貼出來了,大家感興趣的話可以研究下。以下是一些常用的類的api介紹:
-
java.security.AccessControlContext:基於它所封裝的上下文作出系統資源訪問決定,該類最常用於將代碼標記為享有“特權”。
-
java.security.AccessController:用於與訪問控制相關的操作和決定。java.security.SecureClassLoader此類擴展了 ClassLoader,支持使用相關的代碼源和權限定義類,這些代碼源和權限默認情況下可根據系統策略獲取到。
-
java.security.Provider:此類表示 Java 安全 API "provider",這里 provider 實現了 Java 安全性的一部分或者全部。
-
java.security.Permission:表示訪問系統資源的抽象類。所有權限都有一個名稱(對它們的解釋依賴於子類),以及用來定義特定 Permission 子類的語義的抽象方法。