實例分析JVM安全體系:雙親委派、命名空間、保護域、策略

jopen 9年前發布 | 15K 次閱讀 JVM Java開發

原文出處: iceAeterna

在了解雙親委派模型之前,先了解一下類加載器的概念:

類加載器的作用就是將真實的class文件根據位置將該Java類的字節碼裝入內存,并生成對應的Class對象。用戶可以通過繼承ClassLoader和重寫findClass方法來定義自己的類加載器進行加載,系統類加載器按照層次,分為:
(1).啟動類加載器(Bootstrap ClassLoader):將加載 /JAVAHOME/lib以及為-Xbootclasspath所指定的目錄下的類庫,是核心Java API的class文件,用于啟動Java虛擬機
(2).擴展類加載器(Extension ClassLoader):將加載/JAVAHOME/lib/ext以及為java.ext.dirs所指定的目錄下的類庫
(3).應用程序類加載器(Application/System ClassLoader):將加載ClassPath下所指定的類庫,或者稱為類路徑加載器

1.雙親委派
類的加載將使用雙親委派的方式,注意這里的雙親關系并非通過繼承來實現,而是加載器之間指定或默認的委托加載關系,可以看到在 /java/lang/ClassLoader.java中,通過ClassLoader的構造方法顯式指定了其父加載器,而若沒有指定父加載器,那么將 會把系統類加載器AppClassLoader作為默認的父加載器

private ClassLoader(Void unused, ClassLoader parent) {
        this.parent = parent;
        //...
}

protected ClassLoader() {
        //getSystemClassLoader()
        this(checkCreateClassLoader(), getSystemClassLoader());
}

加載器對類的加載調用loadClass()方法實現:

protected Class<?> loadClass(String name, boolean resolve)
        throws ClassNotFoundException
    {
        synchronized (getClassLoadingLock(name)) {
            // First, check if the class has already been loaded
            Class c = findLoadedClass(name);
            // 該類沒有被加載
            if (c == null) { 
                long t0 = System.nanoTime();
                try {
                    if (parent != null) {
                    // 先交由父加載器嘗試加載
                        c = parent.loadClass(name, false);
                    } else {
                    // 父加載器為空,即為BootstrapClassLoader,那么查看啟動類中是否有該類
                        c = findBootstrapClassOrNull(name);
                    }
                } catch (ClassNotFoundException e) {
                    // ClassNotFoundException thrown if class not found
                    // from the non-null parent class loader
                }
                //父類無法加載該類,則由自己嘗試加載
                if (c == null) {
                    // If still not found, then invoke findClass in order
                    // to find the class.
                    long t1 = System.nanoTime();
                    c = findClass(name);

                    // this is the defining class loader; record the stats
                    sun.misc.PerfCounter.getParentDelegationTime().addTime(t1 - t0);
                    sun.misc.PerfCounter.getFindClassTime().addElapsedTimeFrom(t1);
                    sun.misc.PerfCounter.getFindClasses().increment();
                }
            }
            if (resolve) {
                resolveClass(c);
            }
            return c;
        }
    }

可見首先會檢查該類是否已經被加載,若沒有被加載,則會委托父加載器進行裝載,只有當父加載器無法加載時,才會調用自身的findClass()方法進行加載。這樣避免了子加載器加載一些試圖冒名頂替可信任類的不可靠類,也不會讓子加載器去實現父加載器實現的加載工作。
比如某個用戶自定義的類加載器試圖加載一個叫做“java.lang.String”的類,那么,該類會最終委派給啟動類加載器 BootstrapClassLoader嘗試加載,那么啟動類加載器將加載Java API中的”java.lang.String”類而不會通過用戶自定義的類加載器去獲得和加載這個看上去不懷好意的冒名類。

但是僅僅依賴雙親委派是遠遠不夠的,假設這個用戶自定義的類加載器試圖加載一個叫做“java.lang.Bomb”的危險類,而父類加載器無 法加載該類,那么加載工作將由用戶定義的加載器負責實現。由于一個類的同一包內的類(和其子類)可以訪問其protected成員,這個 “java.lang.Bomb”則可能訪問可信任類的一些敏感信息,所以就必須將這個類與可信任類的訪問域隔離,Java虛擬機只把這樣彼此訪問的特殊 權限授予由同一個類加載器加載的同一包內的類型,這樣一個由同一個類加載器加載的、屬于同一個包的多個類型集合稱為運行時包。

2.命名空間
類加載體系為不同類加載器加載的類提供不同的命名空間,同一命名空間內的類可以互相訪問,不同命名空間的類不知道彼此的存在(除非顯式提供訪問機制)。同一類可以再不同的命名空間內,但無法在同一命名空間內重復出現。

命名空間是這樣定義的:實際完成加載類的工作的加載器為定義類加載器,而加載的雙親委托路徑上的所有加載器為初始類加載器,某個加載器的命名空間就是所有以該加載器為初始類加載器的類所組成。

可以預見,子加載器的命名空間包括其父/祖先加載器的命名空間和只有自己才可以加載的類所組成。根據加載體系結構的安全機制,同一命名空間內的類可 以互相訪問,所以父加載器所加載的類不一定可以訪問子加載器所加載的類,但子加載器所加載的類必然可以訪問父加載器加載的類。父加載器加載的類就好像小箱 子,子加載器加載的類可能用到父加載器加載的類,就像一個大箱子,只能把小箱子放進大箱子,而不能反過來做(當然顯式的訪問機制除外)

以自己實現的類加載器為例:

  package com.ice.classloader;


import java.io.ByteArrayOutputStream;
import java.io.File;
import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.io.IOException;
import java.io.InputStream;

public class MyClassLoader extends ClassLoader {

    private String name;    //加載器名稱
    private String path = "E:\\WorkSpace\\ClassLoaderTest\\";  //加載路徑
    private static final String HOME = "E:\\WorkSpace\\ClassLoaderTest\\";
    private final String classFileType = ".class"; 

    public MyClassLoader(String name) {
        super(); 
        this.name = name;
    }

    public MyClassLoader(ClassLoader parent, String name) {
        super(parent); 
        this.name = name;
    }

    @Override
    public String toString() {
        return this.name;
    }

    public String getPath() {
        return path;
    }

    public void setPath(String path) {
        this.path = path;
    }

    @Override
    public Class<?> findClass(String name) throws ClassNotFoundException {
        byte[] data = this.loadClassData(name);
        if(data == null)
            throw new ClassNotFoundException();
        return this.defineClass(name, data, 0, data.length);

    }

    private byte[] loadClassData(String name) {

        InputStream is = null;
        byte[] data = null;
        ByteArrayOutputStream baos = null;
//        System.out.println("  classloader:" + this.name + " try to load");
        try {
            //類名轉化為路徑
            name = name.replace(".", "\\");
            is = new FileInputStream(new File(path + name + classFileType));

            baos = new ByteArrayOutputStream();
            int ch = 0;
            while (-1 != (ch = is.read())) {

                baos.write(ch);
            }

            data = baos.toByteArray();
        }
        catch (FileNotFoundException e) {
//            e.printStackTrace();
            return null;
        }
        catch (IOException ioe) {
            ioe.printStackTrace();
      }
        finally {
            try {
                is.close();
                baos.close();
            }
            catch (Exception e2) {
            }
        }
        return data;
    }


    public static void main(String[] args) throws Exception {
        //假定的系統加載器
        MyClassLoader father = new MyClassLoader("father");
        father.setPath(HOME + "syslib\\");

        MyClassLoader child = new MyClassLoader(father, "child");
        child.setPath(HOME + "ext\\");

        MyClassLoader user = new MyClassLoader("user");
        user.setPath(HOME + "usr\\");
        System.out.println("-------------test parent--------------");
        //測試父加載器關系
        traverseParent(child);
        System.out.println("-------------test load begin from child--------------");
        //測試加載
        test(child);
        //測試命名空間
        System.out.println("-------------test namespace--------------");
        testNameSpace(user);

    }

    public static void traverseParent(ClassLoader loader) throws Exception{
        if(loader == null) return;
        System.out.println("travase classloader:" + loader.toString());
        while(loader.getParent() != null){
            System.out.println(loader.getParent());
            loader = loader.getParent();
        }
    }

    public static void test(ClassLoader loader) throws Exception {
        Class<?> clazz = loader.loadClass("com.ice.classloader.LoadedClass");
        Object object = clazz.newInstance();
    }

    public static void testNameSpace(ClassLoader loader) throws Exception {
        Class<?> clazz = loader.loadClass("com.ice.classloader.LoadedClass");
        Object object = clazz.newInstance();
        try{
            LoadedClass lc = (LoadedClass) object;
        }catch(Exception e){
            e.printStackTrace();
        }
    }
}

被加載類LoadedClass的定義如下:

//被加載類
package com.ice.classloader;

public class LoadedClass {

    public LoadedClass() {
        System.out.println("LoadedClass is loaded by: "
                + this.getClass().getClassLoader());

    }

}

(1).雙親委派結果
child加載器會委托father進行加載,若father的加載目錄下存在著對應的class文件,則會由 父加載器father進行對應的加載工作(father也會交由AppClassLoader和ExtClassLoader嘗試進行加載,但這兩個加載 器并不知道如何加載,故而最后會自己嘗試進行加載)

 實例分析JVM安全體系:雙親委派、命名空間、保護域、策略

當father的加載目錄下沒有對應的class文件,則會交由child進行加載

 實例分析JVM安全體系:雙親委派、命名空間、保護域、策略

(2).命名空間隔離
由于MyClassLoader是通過系統的(應用程序類加載器/類路徑加載器加載的),而 LoadedClass是由user加載器所加載的,AppClassLoader加載器是user加載器的父加載器,故由父加載器加載的類 MyClassLoader無法看見子加載器user所加載的LoadedClass類,在MyClassLoader中嘗試實例化 LoadedClass類時就會出現如下錯誤:

 實例分析JVM安全體系:雙親委派、命名空間、保護域、策略

對應出錯的正是嘗試實例化LoadedClass類的那一行

try{
    LoadedClass lc = (LoadedClass) object;
}catch(Exception e){

(3).運行時包
當請求加載一個com.ice.classloader.virus類時,AppClassLoader路徑下沒有 該類的class文件,那么attaker加載器將會加載這個virus類,并暗示其為com.ice.classloader的一部分,該類想要獲取 com.ice.classloader包下被信任類的訪問權限。但由于權限檢查時,由于該Virus類由attacker加載而非 AppClassLoader加載,故對MyClassLoader受保護成員的訪問將會被阻止。

package com.ice.classloader;

public class Virus {

    public Virus() {
        System.out.println("Virus is loaded by: "
                + this.getClass().getClassLoader());
        MyClassLoader cl = (MyClassLoader) this.getClass().getClassLoader();
        System.out.println("secret is:" + cl.secret);
    }

}

MyClassLoader 由AppClassLoader所加載,而Virus由用戶自定義的加載器attacker所加載,雖然AppClassLoader是attacker 的父加載器,即MyClassLoader對Virus可見,但由于兩者不是由同一個加載器所加載,即不屬于同一個運行時包,那么Virus對 MyClassLoader的受保護成員訪問受限

public class MyClassLoader extends ClassLoader {
    protected int secret = -1;
//...
    public static void main(String[] args) throws Exception {
            //其父加載器為Bootstrap ClassLoader
            MyClassLoader loader = new MyClassLoader(null, "loader");
            loader.setPath(HOME + "usr\\");

            MyClassLoader attacker = new MyClassLoader("attacker");
            attacker.setPath(HOME + "attacker\\");

            System.out.println("MyClassLoader's classloader:" + MyClassLoader.class.getClassLoader());

            System.out.println("-------------test parent--------------");
            //測試父加載器關系
            traverseParent(attacker);

            System.out.println("-------------test in-package access--------------");
            testVirus(attacker);

        }

        public static void traverseParent(ClassLoader loader) throws Exception{
            if(loader == null) return;
            System.out.println("travase classloader:" + loader.toString());
            while(loader.getParent() != null){
                System.out.println(loader.getParent());
                loader = loader.getParent();
            }
        }


        public static void testVirus(ClassLoader loader) throws Exception {
            Class<?> clazz = loader.loadClass("com.ice.classloader.Virus");
            Object object = clazz.newInstance();
        }
    }

結果如下:

 實例分析JVM安全體系:雙親委派、命名空間、保護域、策略

注意命名空間的隔離與運行時包隔離的區別,不同命名空間的類之間不可見,而同一命名空間內的類可能由不同的加載器進行加載,如啟動類加載器加載 的核心JavaAPI和用戶自定義加載器加載的類,這些類及時聲明定義為同一個包,但是由于不是由同一個加載器加載的,即不屬于同一個運行時包,那么不同 運行時包內的類之間就存在對包可見成員的訪問限制。

3.策略與保護域
除了命名空間的訪問隔離和雙親委派的受信類保護,類加載器體系還是用保護域來定義代碼在運行時可以獲得的權限。同樣在分析保護域之前,先了解類Java虛擬機的安全訪問控制及策略。

Java的沙箱模型可以由用戶自定義,這是通過用戶定制沙箱的安全管理器(SecurityManager)來定義沙箱的安全邊界,以為程序運 行指定用戶自定義的安全策略和訪問控制。應用程序通過 System.setSecurityManager()/“-Djava.security.manager”來指定/啟動安全管理器,每當 JavaAPI執行一些可能不安全的操作時,如對文件的讀寫和刪除等,就會向安全管理器進行權限檢查,若權限檢查不通過,將會拋出一個安全異常,若權限檢 查通過,則允許該操作的執行。

比如創建一個FileInputStream時,會調用SecurityManager的checkRead()進行讀取權限的檢查:

public FileInputStream(File file) throws FileNotFoundException {
        String name = (file != null ? file.getPath() : null);
        SecurityManager security = System.getSecurityManager();
        if (security != null) {
            security.checkRead(name);
        }
        if (name == null) {
            throw new NullPointerException();
        }
        fd = new FileDescriptor();
        fd.incrementAndGetUseCount();
        open(name);
    }

checkRead()即以讀動作的FilePermission為參數調用checkPermission()

public void checkRead(String file) {
        checkPermission(new FilePermission(file,
            SecurityConstants.FILE_READ_ACTION));
    }

jdk1.2版本后,可以使用checkPermission(Permission perm)和checkPermission(Permission perm, Object context)來進行權限檢查,其中perm為請求執行操作所需要的權限,如java.io.FilePermission對“/usr /indata.txt”請求“read”操作。checkPermission()實際上在對當前線程的方法棧進行優化后,獲得一個訪問控制環境 AccessControlContext,并調用其checkPermission()方法

public static void checkPermission(Permission perm)
                 throws AccessControlException
    {
        //System.err.println("checkPermission "+perm);
        //Thread.currentThread().dumpStack();

        if (perm == null) {
            throw new NullPointerException("permission can't be null");
        }

        AccessControlContext stack = getStackAccessControlContext();
        // if context is null, we had privileged system code on the stack.
        if (stack == null) {
           //...debug相關
            return;
        }

        AccessControlContext acc = stack.optimize();
        acc.checkPermission(perm);
    }

checkPermission()會從方法的棧頂向棧底遍歷(檢查方法所在類的保護域權限,context是一個 ProtectionDomain數組),當遇到一個沒有權限的棧幀就會拋出一個AccessControlException。即對于一次需要進行權限 檢查的訪問,對于該訪問的方法的每一個調用層次都必須具有對應的訪問權限。
對權限的判定是通過implies()來進行的,implies()在Permission類、PermissionCollection、ProtectionDomain類中聲明。在Permission類(具體實現的子類)中,該方法將確定由該Permission所代表的對象,是否隱含了將要判斷的Permission對象的權限中, 如對”/test/*”目錄的讀寫權限testAllPermission,隱含了對”/test/test.txt”文件的讀寫權限 testFilePermission,即testAllPermission.implies(testFilePermission) 的值為true,反之為false。 在ProtectionDomain(其PermissionCollection)中,將進行權限集合內 implies()的判定,實際上就是在PermissionCollection中遍歷保護域所擁有的權限,調用implies()判定其是否具有對應的訪問權限。

public void checkPermission(Permission perm)
        throws AccessControlException
    {
       //...
        if (context == null)
            return;

        for (int i=0; i< context.length; i++) {
            if (context[i] != null &&  !context[i].implies(perm)) {
                //...
                throw new AccessControlException("access denied "+perm, perm);
            }
        }

        // ...

        return;
    }

那么,類的訪問權限(保護域)是如何指定的?
(1).類與訪問權限是什么?
每個class文件均和一個代碼來源相關聯,這個代碼來源(java.security.CodeSource)通過URL類成員location指向代碼庫和對該class文件進行簽名的零個或多個證書對象的數組(class文件在進行代碼認證的過程中可能經過多個證書簽名,也可能沒有進行簽名) 。
訪問控制策略Policy對權限的授予是以CodeSource為基礎進行的,每個CodeSource擁有若干個Permission,這些Permission對象會被具體地以其子類,如FilePermission、SocketPermission等描述,并且和CodeSource相關聯的Permission對象將被封裝在java.security.PermissionCollection(抽象類)的一個子類實例中,以描述該CodeSource所獲取的權限。
(2).從類的加載到保護域探尋類訪問權限的指定:
加載器會調用defineClass解析和加載類的Class實例:

protected final Class<?> defineClass(String name, byte[] b, int off, int len,
                                         ProtectionDomain protectionDomain)
        throws ClassFormatError
    {
        protectionDomain = preDefineClass(name, protectionDomain);

        Class c = null;
        String source = defineClassSourceLocation(protectionDomain);

        try {
            c = defineClass1(name, b, off, len, protectionDomain, source);
        } catch (ClassFormatError cfe) {
            c = defineTransformedClass(name, b, off, len, protectionDomain, cfe,
                                       source);
        }

        postDefineClass(c, protectionDomain);
        return c;
    }

在defineClass()中,會調用preDefineClass()獲取ProtectionDomain:

private ProtectionDomain preDefineClass(String name,
                                            ProtectionDomain pd)
    {
        if (!checkName(name))
            throw new NoClassDefFoundError("IllegalName: " + name);

        if ((name != null) && name.startsWith("java.")) {
            throw new SecurityException
                ("Prohibited package name: " +
                 name.substring(0, name.lastIndexOf('.')));
        }
        if (pd == null) {
            pd = defaultDomain;
        }

        if (name != null) checkCerts(name, pd.getCodeSource());

        return pd;
    }

當沒有指定保護域時,就會為其指定一個空的保護域,若指定了保護域則使用加載器所指定的保護域。

類加載器的實現可以通過將代碼來源(CodeSource),即代碼庫和該class文件的所有簽名者信息,傳遞給當前的 Policy對象的getPermissions()方法,來查詢該代碼來源所擁有的權限集合PermissionCollection(在策略初始化時 生成),并以此構造一個保護域傳遞給defineClass(),以此指定類的保護域。

(3).Java應用程序訪問控制策略是由抽象類java.security.Policy的子類實例所描述的,通過設置 policy.provider屬性值來指定Policy的實現類,該屬性值定義在/jre/lib/security/java.security文件 中

#
# Class to instantiate as the system Policy. This is the name of the class
# that will be used as the Policy object.
#
policy.provider=sun.security.provider.PolicyFile

可見默認是使用PolicyFile類來實現訪問控制策略,該類將使用從策略文件中讀取并解析訪問控制策略的方式形成策略。
也可以通過實現自己的Policy并調用Policy的setPolicy()方法來替換當前Policy對象。

對Java應用程序的訪問控制策略是由抽象類java.security.Policy的子類實例實現的,其實現方式可以采用很多種方法,如從一個 結構化ASCII文件中讀取,從一個Policy的二進制class文件中讀取,從一個數據庫中讀取,PolicyFile就是使用了從ASCII策略文 件中讀取的方法,策略文件定義在/jre/lib/security/java.security中:

1 # The default is to have a single system-wide policy file,
2 # and a policy file in the user's home directory.
3 policy.url.1=file:${java.home}/lib/security/java.policy
4 policy.url.2=file:${user.home}/.java.policy

可以在java.security文件中修改或添加policy.url.x來指定用戶自己想要的策略,也可以在運行時使用”-Djava.security.policy”命令行參數進行設置,如:
-Djava.security.manager -Djava.security.policy = mypolicy.txt
其中如果沒有指定java.security.manager,那么應用程序就不會安裝任何的安全管理器,而代碼也就沒有任何權限限制。mypolicy.txt就是用戶自頂一個策略文件,這里使用的是相對路徑,將使用程序的啟動目錄

以/jre/lib/security/java.policy為例說明策略文件,在該文件中使用上下文無關文法描述安全策略
如:

grant codeBase "file:${{java.ext.dirs}}/*" {
    permission java.security.AllPermission;
};

policy文件的基本語法如下:

keystore "keystore_url",
"keystore_type";

grant [SignedBy "signer_names"] [, CodeBase "URL"] [,principal principal_class_name "principal_name",]{
Permission permission_class_name
[ "target_name" ]
[, "action"] [, SignedBy "signer_names"];
Permission ...
};
  • keystore:
    keystore_url指定了簽名者的公鑰的證書文件所在位置,可以使相對URL,如keystore “mykey”,這個相對路徑指向了程序使用該策略文件的啟動目錄,比如,該策略文件由policy.url.x指定在”e://security /policy/mypolicy.txt”,那么公鑰證書就在”e://security/policy/mykey”文件中。當然也可以使用絕對路徑 指定公鑰路徑。
    keystore_type指定了密鑰倉庫信息的存儲和數據格式,也定義了保護密鑰倉庫中私鑰和密鑰倉庫完整性的算法,默認將使”JKS”類型
  • grant子句:
    授予 指定類型(代碼) 指定權限
    其中對代碼類型的描述有兩種:
    signedBy表示簽名者別名,可以是由”,”分隔的若干個簽名者
    codeBase表示一個特定的加載位置,從該目錄下加載的代碼都將被賦予特定的權限
  • permission:
    permission由權限類型、操作目標、操作動作三部分組成,如
    permission java.io.FilePermission “note.txt” “read”即為對程序啟動目錄下(相對路徑)的note.txt的讀取權限

最后以深入jvm(第二版)一書中的例子來介紹策略文件的使用以及保護域的作用:

Doer接口:

// /com/ice/security/doer/Doer.java
package com.ice.security.doer;

public interface Doer {
    void doYourThing();
}

Doer的實現類Friend,由Friend所簽名,將作為受信認類訪問“friend.txt”和“stranger.txt”

// /com/ice/security/friend/Friend.java
package com.ice.security.friend;

import java.security.AccessController;
import java.security.PrivilegedAction;

import com.ice.security.doer.Doer;

public class Friend implements Doer{
    private Doer next;
    private boolean direct;

    public Friend(Doer next, boolean direct){
        this.next = next;
        this.direct = direct;
    }

    @Override
    public void doYourThing() {
        if(direct){
            next.doYourThing();
        }else{
            AccessController.doPrivileged(
                    new PrivilegedAction() {
                        public Object run(){
                            next.doYourThing();
                            return null;
                        }
                    }
            );
        }
    }

}

Doer的實現類Stranger,由Stranger所簽名,作為不受信認類,僅能訪問“stranger.txt”

//com/ice/security/stranger/Stranger.java
package com.ice.security.stranger;

import java.security.AccessController;
import java.security.PrivilegedAction;

import com.ice.security.doer.Doer;

public class Stranger implements Doer{
    private Doer next;
    private boolean direct;

    public Stranger(Doer next, boolean direct){
        this.next = next;
        this.direct = direct;
    }

    @Override
    public void doYourThing() {
        if(direct){
            next.doYourThing();
        }else{
            AccessController.doPrivileged(
                    new PrivilegedAction() {
                        public Object run(){
                            next.doYourThing();
                            return null;
                        }
                    }
            );
        }
    }

}

txt文件的顯示輸出類TextFileDisplayer:

//TextFileDisplayer.java
import java.io.CharArrayWriter;
import java.io.FileReader;
import java.io.IOException;

import com.ice.security.doer.Doer;


public class TextFileDisplayer implements Doer{
    private String fileName;
    public TextFileDisplayer(String fileName){
        this.fileName = fileName;
    }

    @Override
    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) {

            }
    }

}

1.將Friend和Stranger分別導出為jar文件,放在指定目錄(這里放在”E:\java\security”目錄下)以待不同的機構 進行簽名,Friend所在包假定為比較有信用的機構”friend”進行簽名,而Stranger所在包假定為一個不受信任的機構”stranger” 進行簽名。
(1).調用命令 jar cvf xxx.jar <_ClassPath> 進行打包
(注意打包后,若沒有把jar包放在單獨的目錄下,需要刪除原java文件編譯產生的class文件,以免程序運行直接加載目錄下class文件而非包內的class文件)
這里分別調用
jar cvf friend.jar com/ice/security/friend/*.class 將friend包內的class文件打包

 實例分析JVM安全體系:雙親委派、命名空間、保護域、策略

jar cvf stranger.jar com/ice/security/stranger/*.class 將friend包內的class文件打包

 實例分析JVM安全體系:雙親委派、命名空間、保護域、策略

(2).使用keytool可以用來生成新的密鑰對,并與一個別名關聯,用密碼加以保護存放在keystore文件中
使用keytool -genkey -alias friend -keypass 123456 -validity 10000 -keystore mykey 命令:

 實例分析JVM安全體系:雙親委派、命名空間、保護域、策略

該密鑰的別名是friend,別名密碼是123456(至少6位),有效期是10000天,存放在一個mykey的keystore文件中,keystore密碼為myfriendkey

類似地,生成一個別名stranger的密鑰對

 實例分析JVM安全體系:雙親委派、命名空間、保護域、策略

為了方便起見,兩個不同的簽名者stranger和friend的密鑰均存放在mykey中,mykey的訪問密碼是myfriendkey,密鑰的訪問密碼都是123456

 實例分析JVM安全體系:雙親委派、命名空間、保護域、策略

可以看到在目錄下生成了一個mykey文件
(3).使用jarsigner -keystore -storepass -keypass 命令進行簽名
這里:
jarsigner -keystore mykey -storepass myfriendkey -keypass 123456 friend.jar friend
jarsigner -keystore mykey -storepass myfriendkey -keypass 123456 stranger.jar stranger
使用friend密鑰對friend.jar進行簽名,使用stranger密鑰對stranger.jar進行簽名

(4).最后可以使用
keytool -export -alias -storepass -file -keystore
這里分別用:
keytool -export -alias friend -storepass myfriendkey -file friend.cer -keystore mykey
keytool -export -alias stranger -storepass myfriendkey -file stranger.cer -keystore mykey
導出friend和stranger的發行證書

 實例分析JVM安全體系:雙親委派、命名空間、保護域、策略

 

2.編寫自己的策略文件,放在當前目錄下

//mypolicy.txt
keystore "mykey";

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

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

grant codeBase "file:${com.ice.home}/com*"{
    permission java.io.FilePermission "friend.txt","read";
    permission java.io.FilePermission "stranger.txt","read";
};

這里friend簽名的類和${com.ice.home}.com(后面設置 為”e:\java\security\com”,存放著Doer接口的class文件)可以讀取”friend.txt” 和”stranger.txt”,而stranger簽名的類只能讀取”stranger.txt”
(這里為了方便,直接使用mykey而非發布的證書)
(1).添加Doer接口類的class文件(對應路徑)和friend.txt和stranger.txt兩個測試文件
(2).通過權限檢查的例子:

public class ProtectionDomainTest {
    public static void main(String[] args){
    TextFileDisplayer tfd = new TextFileDisplayer("stranger.txt");
    Friend friend = new Friend(tfd, true);
    Stranger stranger = new Stranger(friend, true);
    stranger.doYourThing();
    }
}

調用java -Djava.security.manager -Djava.security.policy=mypolicy.txt -Dcom.ice.home=e:\java\security -cp .;friend.jar;stranger.jar ProtectionDomainTest測試運行,其中指定了com.ice.home的路徑,通過-cp設置了類路徑

 實例分析JVM安全體系:雙親委派、命名空間、保護域、策略

(3).不能通過權限檢查的例子:

public class ProtectionDomainTest {
    public static void main(String[] args){
    TextFileDisplayer tfd = new TextFileDisplayer("friend.txt");
    Friend friend = new Friend(tfd, true);
    Stranger stranger = new Stranger(friend, true);
    stranger.doYourThing();
    }
}

與(2)類似,但stranger會嘗試讓friend讀取”friend.txt”,這會被阻止

 實例分析JVM安全體系:雙親委派、命名空間、保護域、策略

 本文由用戶 jopen 自行上傳分享,僅供網友學習交流。所有權歸原作者,若您的權利被侵害,請聯系管理員。
 轉載本站原創文章,請注明出處,并保留原始鏈接、圖片水印。
 本站是一個以用戶分享為主的開源技術平臺,歡迎各類分享!