【Java 安全技術探索之路系列:J2SE安全架構】之二:安全管理器
一 安全管理器的功能
安全管理器是一個允許程式實現安全策略的類,它會在執行階段檢查需要保護的資源的訪問許可權及其它規定的操作許可權,保護系統免受惡意操作攻擊,以達到系統的安全策略。
安全管理器負責檢查的操作主要包括以下幾個:
- 建立一個新的類載入器
- 退出虛擬機器
- 使用反射訪問另一個類的成員
- 訪問本地連線
- 開啟socket連線
- 啟動列印作業
- 訪問系統剪貼簿
- 訪問AWT事件佇列
- 開啟一個頂層視窗
注意:在執行Java應用程式時,預設的設定是不安裝安全管理器的,這樣所有的操作都是允許的,
安全管理器的工作流程如下圖所示:
一 安全管理器的使用
1.1 獲取安全管理器
Security security = System.getSecurityManager();
1.2 啟動安全管理器
1.2.1 命令列啟動
java -Djava.security.manager class_name
1.2.2 程式啟動
在啟動安全管理器時可以通過-Djava.security.policy選項來指定安全策略檔案。如果沒有指定策略檔案的路徑,那麼安全管理器將使用預設的安全策略檔案,它位於%JAVA_HOME%/jre/lib/security目錄下面的java.policy。
注意:
- =表示這個策略檔案將和預設的策略檔案一同發揮作用。
- ==表示只使用這個策略檔案。
policy檔案包含了多個grant語句,每一個grant描述某些程式碼擁有某些操作的許可權。在啟動安全管理器時會根據policy檔案生成一個Policy物件,任何時候一個應用程式只能有一個Policy物件
SecurityManager sm=new SecurityManager();
System.setSecurityManager(sm);
預設的%JAVA_HOME%/jre/lib/security/java.policy檔案內容如下所示:
// Standard extensions get all permissions by default
grant codeBase "file:${{java.ext.dirs}}/*" {
permission java.security.AllPermission;
};
// default permissions granted to all domains
grant {
// Allows any thread to stop itself using the java.lang .Thread.stop()
// method that takes no argument.
// Note that this permission is granted by default only to remain
// backwards compatible.
// It is strongly recommended that you either remove this permission
// from this policy file or further restrict it to code sources
// that you specify, because Thread.stop() is potentially unsafe.
// See the API specification of java.lang.Thread.stop() for more
// information.
permission java.lang.RuntimePermission "stopThread";
// allows anyone to listen on dynamic ports
permission java.net.SocketPermission "localhost:0", "listen";
// "standard" properies that can be read by anyone
permission java.util.PropertyPermission "java.version", "read";
permission java.util.PropertyPermission "java.vendor", "read";
permission java.util.PropertyPermission "java.vendor.url", "read";
permission java.util.PropertyPermission "java.class.version", "read";
permission java.util.PropertyPermission "os.name", "read";
permission java.util.PropertyPermission "os.version", "read";
permission java.util.PropertyPermission "os.arch", "read";
permission java.util.PropertyPermission "file.separator", "read";
permission java.util.PropertyPermission "path.separator", "read";
permission java.util.PropertyPermission "line.separator", "read";
permission java.util.PropertyPermission "java.specification.version", "read";
permission java.util.PropertyPermission "java.specification.vendor", "read";
permission java.util.PropertyPermission "java.specification.name", "read";
permission java.util.PropertyPermission "java.vm.specification.version", "read";
permission java.util.PropertyPermission "java.vm.specification.vendor", "read";
permission java.util.PropertyPermission "java.vm.specification.name", "read";
permission java.util.PropertyPermission "java.vm.version", "read";
permission java.util.PropertyPermission "java.vm.vendor", "read";
permission java.util.PropertyPermission "java.vm.name", "read";
};
1.3 關閉安全管理器
SecurityManager sm=System.getSecurityManager();
if(sm!=null)
{
System.setSecurityManager(null);
}
以上程式碼只有在位於{JDK_HOME}/jre/lib/security目錄下或者其他指定目錄下的java.policy檔案中指定了一個許可權才會生效。
該許可權是:
permission java.lang.RuntimePermission"setSecurityManager";
1.4 安全管理器檢查
security.checkXXX(...);
檢查完成後,成功則安全管理器返回,失敗則安全管理器丟擲SecurityException,注意該約定唯一的例外是checkTopLevelWindow,它返回boolean值。
1.5 安全管理器許可權檢查
安全管理器中所有其他check()方法的預設實現都是呼叫SecurityManager.checkPermission()方法來確定執行緒是否具有執行所請求的操作的許可權。
只帶有單個許可權引數的checkPermission()方法總是在當前執行的執行緒上下文中執行安全檢查。
如果在給定的上下文進行檢查需要在不同的上下文中進行,可以使用Java提供的包含上下文引數的getSecurityContext()方法和checkPermission()方法,如下所示:
Object context = null;
SecurityManager sm = System.getSecurityManager();
if(sm != null)
{
context = sm.getSecurityContext();//該方法返回當前呼叫上下文的一個快照
sm.checkPermission(permission, context);//該方法使用一個上下文物件,以及根據該上下文(不是當前執行執行緒的上下文)作出訪問決策的許可權。
}
許可權分為以下幾個類別:
- 檔案
- 套接字
- 網路
- 安全性
- 執行時
- 屬性
- 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
整個許可權類的層次結構如下圖所示:
下面寫一個例子來演示一下自定義安全管理器的使用。
import java.io.FileInputStream;
import java.io.FileNotFoundException;
public class SecurityManagerDemo
{
public static void main(String[] args) throws FileNotFoundException
{
System.out.println("SecurityManager: " + System.getSecurityManager());
FileInputStream fis = new FileInputStream("C:\\Users\\Administrator\\my.txt");
System.out.println(System.getProperty("file.encoding"));
}
}
注意:my.txt是已經存在的檔案,需要在你的目錄建立,這裡的目錄是C:\Users\Administrator。
直接執行
直接執行SecurityManagerDemo,相當於沒有啟動安全管理器,SecurityManager打印出來為null,且能正確讀取protect.txt檔案跟file.encoding屬性。如下圖所示:
新增啟動引數執行
新增啟動引數
-Djava.security.manager -Djava.security.policy=C:\\Users\\Administrator\\my.policy//自定義策略檔案
指定-Djava.security.manager引數,此時SecurityManager打印出來為不為null,my.policy裡面並沒有做任何授權,所以在讀取檔案的時就丟擲AccessControlException異常,如下圖所示:
建立my.policy,並寫入以下grant:
grant {
permission java.io.FilePermission "C:\\Users\\Administrator\\my.txt", "read";
permission java.util.PropertyPermission "file.encoding", "read";
};
此時可以正確讀取,如下圖所示:
三 實現自定義的安全管理器
實現自定義的安全管理器一般分為兩步:
- 建立一個SecurityManager子類,根據需要重寫一些方法。
- 根據應用程式程式碼的許可權需要配置策略檔案。
下面寫一個例子來演示一下自定義安全管理器的使用:
自定義類MySecurityManager繼承於SecurityManager,重寫了checkRead()方法。
public class MySecurityManager extends SecurityManager
{
@Override
public void checkRead(String file)
{
//super.checkRead(file, context);
if (file.endsWith("not"))
{
throw new SecurityException("你沒有讀取的本檔案的許可權");
}
}
}
寫個測試類MySecurityManagerDemo觀察MySecurityManager是否有用。
import java.io.FileInputStream;
import java.io.IOException;
public class MySecurityManagerDemo
{
public static void main(String[] args)
{
System.setSecurityManager(new MySecurityManager());
try
{
FileInputStream fis = new FileInputStream("not");
System.out.println(fis.read());
}
catch (IOException e)
{
e.printStackTrace();
}
}
}
執行完成後,輸出列印“你沒有讀取的本檔案的許可權”,說明MySecurityManager可以使用,結果如下圖所示: