深入理解Java虛擬機讀書筆記之:第3章 安全(3)

策略
    Java安全體系結構的真正好處在於,它可以對代碼授予不同層次的信任度來部分地訪問系統。
    Microsoft提供了ActiveX控件認證技術,它和Java的認證技術相類似,但是ActiveX控件並不在沙箱中運行。這樣,使用了ActiveX,一系列移動代碼要麼是被完全信任的,要麼是完全不被信任的。
    版本1.2的安全體系結構的主要目標之一就是使建立(以簽名代碼爲基礎的)細粒度的訪問控制策略的過程更爲簡單且更少出錯。
 
    在版本1.2的安全體系結構中,對應於整個Java應用程序的一個訪問控制策略是由抽象類java.security.Policy的一個子類的單個實例所表示的。
    安全策略是一個從描述運行代碼的屬性集合到這段代碼所擁有的權限的映射。在版本1.2的安全體系結構中,描述運行代碼的屬性被總稱爲代碼來源。一個代碼來源是由一個java.security.CodeSource對象表示的,這個對象中包含了一個java.net.URL,它表示代碼庫和代表了簽名者的零個或多個證書對象的數組。證書對象是抽象類java.security.cert.Certificate的子類的一個實例,一個Certificate對象抽象表示了從一個人到一個公鑰的綁定,以及另一個爲這個綁定作擔保的人(以前提過的證書機構)。CodeSource對象包含了一個Certificate對象的數組,因爲同一段代碼可以被多個團體簽名(擔保)。這個簽名通常是從JAR文件中獲得的。
    權限是用抽象類java.security.Permission的一個子類的實例表示的。一個Permission對象有三個屬性:類型、名字和可選的操作。
    在Policy對象中,每一個CodeSource是和一個或多個Permission對象相關聯的。和一個CodeSource相關聯的Permission對象被封裝在java.security.PermissionCollection的一個子類實例中。
 
 
策略文件
    java.security.Policy是一個抽象類,具體Policy子類的實現細節之一就是該子類的實例怎樣知道策略應該是什麼。子類可以採取多種方法,例如對一個已序列化的Policy對象進行並行化,從數據庫中抽取策略,或者從文件中讀取策略。由Sun提供的在Java 1.2平臺下的具體Policy子類採用了最後一種方法:在一個ASCII策略文件中用上下文無關方法描述安全策略。
    一個策略文件包括了一系列grant子句,每一個grant子句將一些權限授給一個代碼來源。
 
策略文件例子policyfile.txt:
keystore "ijvmkeys";

grant signedBy "friend" {
    permission java.io.FilePermission "question.txt", "read";
    permission java.io.FilePermission "answer.txt", "read";
};

grant signedBy "stranger" {
    permission java.io.FilePermission "question.txt", "read";
};

grant codeBase "file:${com.artima.ijvm.cdrom.home}/security/ex2/*" {
	permission java.io.FilePermission "question.txt", "read";
	permission java.io.FilePermission "answer.txt", "read";
};
 
說明:
    keystore "ijvmkeys"子句說明密鑰別名存儲在名爲ijvmkeys的keystore文件中。
 
    第1個grant子句將授予由別名爲“friend”的實體簽名的所有代碼兩個權限。被授予的權限是:讀取question.txt、answer.txt文件的權限。因爲這個grant子句中沒有提到代碼庫,所以由friend簽名的代碼可以來自任何代碼庫。
    
    第2個grant子句只授予了讀取question.txt文件的權限。
 
    第3個grant子句將兩個權限授予所有從一個特定目錄中裝載的代碼。這個grant子句沒有指明任何簽名者,所以,這個代碼可以是被任何人簽名的,或者是未被簽名的。
 
 
保護域
    當類裝載器將類型裝入Java虛擬機時,它們將爲每個類型指派一個保護域。保護域定義了授予一段特定代碼的所有權限。(一個保護域對應策略文件中的一個或多個grant子句。)裝載入Java虛擬機的每一個類型都屬於一個且僅屬於一個保護域。


 
 
訪問控制器
    類java.security.AccessController提供了一個默認的安全策略執行機制,它使用棧檢查來決定潛在不安全的操作是否被允許。這個訪問控制器不能被實例化,它不是一個對象,而是集合在單個類中的多個靜態方法。AccessController的最核心方法是它的靜態方法checkPermission(),這個方法決定一個特定的操作能否被允許。
    如果你安裝了具體安全管理器,其實最終是由這個AccessController來決定一個潛在不安全的方法是否否被允許。
    每一個棧幀代表了由當前線程調用的某個方法,每一個方法是在某個類中定義的,每一個類又屬於某個保護域,每個保護域包含一些權限。因此,每個棧幀間接地和一些權限相關。
 
1)implies()方法
    爲了決定由傳遞給AccessController的checkPermission()方法的Permission對象所代表的操作,是否包含在(或隱含在)和調用棧中的代碼相關聯的權限中,AccessController利用了一個名爲implies()的重要方法。這個implies()方法是在Permission類以及PermissionCollection類和ProtectionDomain類中聲明的。
import java.security.Permission;
import java.io.FilePermission;
import java.io.File;
// On CD-ROM in file security/ex1/Example1.java
class Example1 {
    public static void main(String[] args) {
        char sep = File.separatorChar;
        // Read permission for "/tmp/f"
        Permission file = new FilePermission(sep + "tmp" + sep + "f", "read");
        // Read permission for "/tmp/*", which
        // means all files in the /tmp directory 
        // (but not any files in subdirectories
        // of /tmp)
        Permission star = new FilePermission(sep + "tmp" + sep + "*", "read");
        boolean starImpliesFile = star.implies(file);
        boolean fileImpliesStar = file.implies(star);
        // Prints "Star implies file = true"
        System.out.println("Star implies file = " + starImpliesFile);
        // Prints "File implies star = false"
        System.out.println("File implies star = " + fileImpliesStar);
    }
}
 
2)棧檢查示例
    下面幾節將給出幾個示例,說明AccessController執行棧檢查的方法。
    基於深入理解Java虛擬機讀書筆記之:第3章 安全(2)的代碼示例,增加一個類:
import com.artima.security.doer.Doer;
import java.io.FileReader;
import java.io.CharArrayWriter;
import java.io.IOException;
public class TextFileDisplayer implements Doer {
    private String fileName;
    public TextFileDisplayer(String fileName) {
        this.fileName = fileName;
    }
    public void doYourThing() {
        try {
            FileReader fr = new FileReader(fileName);
            try {
                CharArrayWriter caw = new CharArrayWriter();
                int c;
                while ((c = fr.read()) != -1) {
                    caw.write(c);
                }
                System.out.println(caw.toString());
            }
            catch (IOException e) {
            }
            finally {
                try {
                    fr.close();
                }
                catch (IOException e) {
                }
            }
        }
        catch (IOException e) {
        }
    }
}
這個類的doYourThing()方法作用是顯示一個文本文件的內容。
 
3)一個回答“是”的棧檢查
    在第一個棧檢查示例中,先來看一個Example2a應用程序:
import com.artima.security.friend.Friend;
import com.artima.security.stranger.Stranger;
// This succeeds because everyone has permission to
// read answer.txt
class Example2a {
    public static void main(String[] args) {
        TextFileDisplayer tfd = new TextFileDisplayer("question.txt");
        Friend friend = new Friend(tfd, true);
        Stranger stranger = new Stranger(friend, true);
        stranger.doYourThing();
    }
}
    當TextFileDisplayer的doYourThing()方法創建了一個新的FileReader對象時,FileReader的構造器創建了一個新的FileInputStream,FileInputStream的構造器檢查是否已經安裝了一個安全管理器。在現在這個例子中,已經安裝了具體安全管理器,因此,FileInputStream的構造器調用了具體安全管理器的checkRead()方法。這個checkRead()方法實例化了一個新的、代表讀文件question.txt權限的FilePermission對象,並將這個對象傳給具體安全管理器的checkPermission()方法,這個checkPermission()方法又把這個對象傳遞給AccessController的checkPermission()方法。AccessController()的checkPermission()方法執行了棧檢查,確定這個線程是否有權打開並讀取文件question.txt。


 
                   Example2a中的棧檢查:所有棧幀都有權限
 
    調用棧的每一個棧幀用由多個元素組成的一行表示。保護域表示了這個棧幀所關聯的保護域。最右邊的箭頭,說明了當一個AccessController的checkPermission()方法檢查每一個棧幀是否有權執行被請求的操作時,它的行進方向。在箭頭的左邊是數字,每個棧幀對應一個。棧的頂顯示在圖的最底端。
    FRIEND、STRANGER和CDROM三個保護域和policyfile.txt文件中的grant子句相對應,BOOTSTRAP保護域代表賦予所有由啓動類裝載器裝載的代碼的權限,在該保護域中的代碼被賦予了java.lang.AllPermission,該權限允許做任何事。
 
    運行示例代碼(注:附件cdrom.zip的security/ex2目錄下的ex2a.bat文件):
java -Djava.security.manager -Djava.security.policy=policyfile.txt -Dcom.artima.ijvm.cdrom.home=D:\cdrom -cp .;jars/friend.jar;jars/stranger.jar Example2a
 
    運行結果:
Too what extent does complexity threaten security?
 
4)一個回答“不”的棧檢查
    下面一個棧檢查的例子,是沒有權限情況下的棧檢查。
import com.artima.security.friend.Friend;
import com.artima.security.stranger.Stranger;
// This fails because the Stranger code doesn't have
// permission to read file question.txt
class Example2b {
    public static void main(String[] args) {
        TextFileDisplayer tfd = new TextFileDisplayer("answer.txt");
        Friend friend = new Friend(tfd, true);
        Stranger stranger = new Stranger(friend, true);
        stranger.doYourThing();
    }
}
 
    本例子與上個例子的區別僅僅是將TextFileDisplayer的參數question.txt替換爲answer.txt。
    在這個例子中,檢查過程並沒有真正到達棧幀1,當AccessController到達棧幀2時,它發現棧幀2的doYourThing()方法屬於Stranger類的代碼,而這個類沒有讀取answer.txt文件的權限。AccessController的checkPermission()方法拋出了一個AccessControllerException異常。


 
                   Example2b中的棧檢查:棧幀2沒有權限
 
    運行示例代碼(注:附件cdrom.zip的security/ex2目錄下的ex2b.bat文件):
java -Djava.security.manager -Djava.security.policy=policyfile.txt -Dcom.artima.ijvm.cdrom.home=D:\cdrom -cp .;jars/friend.jar;jars/stranger.jar Example2b
 
    運行結果:
Exception in thread "main" java.security.AccessControlException: access denied (
java.io.FilePermission answer.txt read)
        at java.security.AccessControlContext.checkPermission(Unknown Source)
        at java.security.AccessController.checkPermission(Unknown Source)
        at java.lang.SecurityManager.checkPermission(Unknown Source)
        at java.lang.SecurityManager.checkRead(Unknown Source)
        at java.io.FileInputStream.<init>(Unknown Source)
        at java.io.FileInputStream.<init>(Unknown Source)
        at java.io.FileReader.<init>(Unknown Source)
        at TextFileDisplayer.doYourThing(TextFileDisplayer.java:19)
        at com.artima.security.friend.Friend.doYourThing(Friend.java:45)
        at com.artima.security.stranger.Stranger.doYourThing(Stranger.java:45)
        at Example2b.main(Example2b.java:18)
 
5)doPrivileged()方法
    有的時候,調用棧較上層(更靠近棧頂)的代碼可能希望執行一段代碼,而這段代碼在調用棧的較下層是不允許執行的。
    爲了使可信的代碼執行較不可靠的代碼操作(這段不可靠的代碼位於調用棧的較下層且沒有執行這個操作的權限),AccessController類重載了四個名爲doPrivileged()的靜態方法。
    當調用doPrivileged()方法時,就像調用其他任何方法一樣,都會將一個新的棧幀壓入棧。在由AccessController執行的棧檢查中,一個doPrivileged()方法調用的棧幀標識了檢查過程的提前終止點。如果和調用doPrivileged()的方法相關聯的保護域擁有執行被請求操作的權限,AccessController將立即返回。這樣這個操作就被允許,即使在棧下層的代碼可能沒有執行這個操作的權限。
 
    看一看Friend的doPrivileged()的調用過程例子:
import com.artima.security.friend.Friend;
import com.artima.security.stranger.Stranger;
// This succeeds because Friend code executes a
// doPrivileged() call. (Passing false as
// the second arg to Friend constructor causes
// it to do a doPrivileged().)
class Example2c {
    public static void main(String[] args) {
        TextFileDisplayer tfd = new TextFileDisplayer("answer.txt");
        Friend friend = new Friend(tfd, false);
        Stranger stranger = new Stranger(friend, true);
        stranger.doYourThing();
    }
}
 

 
                   Example2c中的棧檢查:在棧幀3停止
 
    與前兩個例子的調用棧區別在於Example2c的調用棧有兩個另外的棧幀:棧幀4代表了doPrivileged()調用,棧幀5代表了PrivilegedAction對象的run()調用。
    當AccessController到達棧幀4時,它發現了一個doPrivileged()調用。因此,AccessController又進行了一個檢查:它檢查由棧幀3代表的代碼,也就是調用了doPrivileged()的代碼,是否有讀取文件answer.txt的權限。因爲棧幀3是與FRIEND保護域相關聯的,而FRIEND保護域擁有讀取文件question.txt的權限,所以AccessController的checkPermission()方法正常返回。
 
    運行示例代碼(注:附件cdrom.zip的security/ex2目錄下的ex2c.bat文件):
java -Djava.security.manager -Djava.security.policy=policyfile.txt -Dcom.artima.ijvm.cdrom.home=D:\cdrom -cp .;jars/friend.jar;jars/stranger.jar Example2c
 
    運行結果:
Complexity threatens security to a significant extent. The more
complicated a security infrastructure becomes, the more likely
parties responsible for configuring security will either make
mistakes that open up security holes or avoid using the
security infrastructure altogether.
 
5)doPrivileged()的一個無效使用
    有一點很重要,必須理解,那就是一個方法不能授予它自己比它現在已經用doPrivileged()調用所得到的權限更多的權限。通過調用doPrivileged(),一個方法僅僅能使用它現在已經被授予的權限。
 
    作爲一個doPrivileged()的無效使用的例子:
import com.artima.security.friend.Friend;
import com.artima.security.stranger.Stranger;
// This fails because even though Stranger does
// a doPrivileged() call, Stranger doesn't have
// permission to read question.txt. (Passing
// false as second arg to Stranger constructor
// causes it to do a doPrivileged().)
class Example2d {
    public static void main(String[] args) {
        TextFileDisplayer tfd = new TextFileDisplayer("answer.txt");
        Stranger stranger = new Stranger(tfd, false);
        Friend friend = new Friend(stranger, true);
        friend.doYourThing();
    }
}
 

 
                   Example2d中的棧檢查:棧幀5沒有權限
 
    當AccessController到達棧幀5,它發現這個棧幀和STRANGER保護域相關聯,而STRANGER保護域沒有讀取文件answer.txt的權限。在這種情況下,AccessController拋出一個AccessControlException異常,說明請求讀取answer.txt的操作不能被執行。
 
    運行示例代碼(注:附件cdrom.zip的security/ex2目錄下的ex2d.bat文件):
java -Djava.security.manager -Djava.security.policy=policyfile.txt -Dcom.artima.ijvm.cdrom.home=D:\cdrom -cp .;jars/friend.jar;jars/stranger.jar Example2d
 
    運行結果:
Exception in thread "main" java.security.AccessControlException: access denied (
java.io.FilePermission answer.txt read)
        at java.security.AccessControlContext.checkPermission(Unknown Source)
        at java.security.AccessController.checkPermission(Unknown Source)
        at java.lang.SecurityManager.checkPermission(Unknown Source)
        at java.lang.SecurityManager.checkRead(Unknown Source)
        at java.io.FileInputStream.<init>(Unknown Source)
        at java.io.FileInputStream.<init>(Unknown Source)
        at java.io.FileReader.<init>(Unknown Source)
        at TextFileDisplayer.doYourThing(TextFileDisplayer.java:19)
        at com.artima.security.stranger.Stranger$1.run(Stranger.java:51)
        at java.security.AccessController.doPrivileged(Native Method)
        at com.artima.security.stranger.Stranger.doYourThing(Stranger.java:48)
        at com.artima.security.friend.Friend.doYourThing(Friend.java:45)
        at Example2d.main(Example2d.java:21)
 
 
    在此說明,本系列文章的內容均出自《深入理解Java虛擬機》一書,除了極少數的“注”或對內容的裁剪整理外,內容原則上與原書保持一致。由於這是一本原理性的書籍,本人不想由於自己能力與理解的問題對大家造成誤解,所以除了對原書內容的裁剪整理之外,不做任何內容的延伸思考與擴展。
    另外,如果您對本系列文章的內容感興趣,建議您去閱讀原版書籍,謝謝!
 
(轉載請註明來源:http://zhanjia.iteye.com/blog/1842733)
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章