国产探花免费观看_亚洲丰满少妇自慰呻吟_97日韩有码在线_资源在线日韩欧美_一区二区精品毛片,辰东完美世界有声小说,欢乐颂第一季,yy玄幻小说排行榜完本

首頁(yè) > 學(xué)院 > 開(kāi)發(fā)設(shè)計(jì) > 正文

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

2019-11-14 15:06:20
字體:
來(lái)源:轉(zhuǎn)載
供稿:網(wǎng)友

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

類(lèi)加載器的作用就是將真實(shí)的class文件根據(jù)位置將該java類(lèi)的字節(jié)碼裝入內(nèi)存,并生成對(duì)應(yīng)的Class對(duì)象。用戶(hù)可以通過(guò)繼承ClassLoader和重寫(xiě)findClass方法來(lái)定義自己的類(lèi)加載器進(jìn)行加載,系統(tǒng)類(lèi)加載器按照層次,分為:
(1).啟動(dòng)類(lèi)加載器(Bootstrap ClassLoader):將加載 /JAVAHOME/lib以及為-Xbootclasspath所指定的目錄下的類(lèi)庫(kù),是核心Java API的class文件,用于啟動(dòng)Java虛擬機(jī)
(2).擴(kuò)展類(lèi)加載器(Extension ClassLoader):將加載/JAVAHOME/lib/ext以及為java.ext.dirs所指定的目錄下的類(lèi)庫(kù)
(3).應(yīng)用程序類(lèi)加載器(application/System ClassLoader):將加載ClassPath下所指定的類(lèi)庫(kù),或者稱(chēng)為類(lèi)路徑加載器

1.雙親委派
類(lèi)的加載將使用雙親委派的方式,注意這里的雙親關(guān)系并非通過(guò)繼承來(lái)實(shí)現(xiàn),而是加載器之間指定或默認(rèn)的委托加載關(guān)系,可以看到在 /java/lang/ClassLoader.java中,通過(guò)ClassLoader的構(gòu)造方法顯式指定了其父加載器,而若沒(méi)有指定父加載器,那么將 會(huì)把系統(tǒng)類(lèi)加載器AppClassLoader作為默認(rèn)的父加載器

1
2
3
4
5
6
7
8
9
PRivate ClassLoader(Void unused, ClassLoader parent) {
        this.parent = parent;
        //...
}
 
protected ClassLoader() {
        //getSystemClassLoader()
        this(checkCreateClassLoader(), getSystemClassLoader());
}

加載器對(duì)類(lèi)的加載調(diào)用loadClass()方法實(shí)現(xiàn):

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
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);
            // 該類(lèi)沒(méi)有被加載
            if (c == null) {
                long t0 = System.nanoTime();
                try {
                    if (parent != null) {
                    // 先交由父加載器嘗試加載
                        c = parent.loadClass(name, false);
                    } else {
                    // 父加載器為空,即為BootstrapClassLoader,那么查看啟動(dòng)類(lèi)中是否有該類(lèi)
                        c = findBootstrapClassOrNull(name);
                    }
                } catch (ClassNotFoundException e) {
                    // ClassNotFoundException thrown if class not found
                    // from the non-null parent class loader
                }
                //父類(lèi)無(wú)法加載該類(lèi),則由自己嘗試加載
                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;
        }
    }

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

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

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

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

可以預(yù)見(jiàn),子加載器的命名空間包括其父/祖先加載器的命名空間和只有自己才可以加載的類(lèi)所組成。根據(jù)加載體系結(jié)構(gòu)的安全機(jī)制,同一命名空間內(nèi)的類(lèi)可 以互相訪問(wèn),所以父加載器所加載的類(lèi)不一定可以訪問(wèn)子加載器所加載的類(lèi),但子加載器所加載的類(lèi)必然可以訪問(wèn)父加載器加載的類(lèi)。父加載器加載的類(lèi)就好像小箱 子,子加載器加載的類(lèi)可能用到父加載器加載的類(lèi),就像一個(gè)大箱子,只能把小箱子放進(jìn)大箱子,而不能反過(guò)來(lái)做(當(dāng)然顯式的訪問(wèn)機(jī)制除外)

以自己實(shí)現(xiàn)的類(lèi)加載器為例:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
  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;    //加載器名稱(chēng)
    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 {
            //類(lèi)名轉(zhuǎn)化為路徑
            name = name.replace(".", "http://");
            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 {
        //假定的系統(tǒng)加載器
        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--------------");
        //測(cè)試父加載器關(guān)系
        traverseParent(child);
        System.out.println("-------------test load begin from child--------------");
        //測(cè)試加載
        test(child);
        //測(cè)試命名空間
        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();
        }
    }
}

被加載類(lèi)LoadedClass的定義如下:

1
2
3
4
5
6
7
8
9
10
11
12
//被加載類(lèi)
package com.ice.classloader;
 
public class LoadedClass {
 
    public LoadedClass() {
        System.out.println("LoadedClass is loaded by: "
                + this.getClass().getClassLoader());
 
    }
 
}

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

當(dāng)father的加載目錄下沒(méi)有對(duì)應(yīng)的class文件,則會(huì)交由child進(jìn)行加載

(2).命名空間隔離
由于MyClassLoader是通過(guò)系統(tǒng)的(應(yīng)用程序類(lèi)加載器/類(lèi)路徑加載器加載的),而 LoadedClass是由user加載器所加載的,AppClassLoader加載器是user加載器的父加載器,故由父加載器加載的類(lèi) MyClassLoader無(wú)法看見(jiàn)子加載器user所加載的LoadedClass類(lèi),在MyClassLoader中嘗試實(shí)例化 LoadedClass類(lèi)時(shí)就會(huì)出現(xiàn)如下錯(cuò)誤:

對(duì)應(yīng)出錯(cuò)的正是嘗試實(shí)例化LoadedClass類(lèi)的那一行

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

(3).運(yùn)行時(shí)包
當(dāng)請(qǐng)求加載一個(gè)com.ice.classloader.virus類(lèi)時(shí),AppClassLoader路徑下沒(méi)有 該類(lèi)的class文件,那么attaker加載器將會(huì)加載這個(gè)virus類(lèi),并暗示其為com.ice.classloader的一部分,該類(lèi)想要獲取 com.ice.classloader包下被信任類(lèi)的訪問(wèn)權(quán)限。但由于權(quán)限檢查時(shí),由于該Virus類(lèi)由attacker加載而非 AppClassLoader加載,故對(duì)MyClassLoader受保護(hù)成員的訪問(wèn)將會(huì)被阻止。

1
2
3
4
5
6
7
8
9
10
11
12
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由用戶(hù)自定義的加載器attacker所加載,雖然AppClassLoader是attacker 的父加載器,即MyClassLoader對(duì)Virus可見(jiàn),但由于兩者不是由同一個(gè)加載器所加載,即不屬于同一個(gè)運(yùn)行時(shí)包,那么Virus對(duì) MyClassLoader的受保護(hù)成員訪問(wèn)受限

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
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--------------");
            //測(cè)試父加載器關(guān)系
            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();
        }
    }

結(jié)果如下:

注意命名空間的隔離與運(yùn)行時(shí)包隔離的區(qū)別,不同命名空間的類(lèi)之間不可見(jiàn),而同一命名空間內(nèi)的類(lèi)可能由不同的加載器進(jìn)行加載,如啟動(dòng)類(lèi)加載器加載 的核心JavaAPI和用戶(hù)自定義加載器加載的類(lèi),這些類(lèi)及時(shí)聲明定義為同一個(gè)包,但是由于不是由同一個(gè)加載器加載的,即不屬于同一個(gè)運(yùn)行時(shí)包,那么不同 運(yùn)行時(shí)包內(nèi)的類(lèi)之間就存在對(duì)包可見(jiàn)成員的訪問(wèn)限制。

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

Java的沙箱模型可以由用戶(hù)自定義,這是通過(guò)用戶(hù)定制沙箱的安全管理器(SecurityManager)來(lái)定義沙箱的安全邊界,以為程序運(yùn) 行指定用戶(hù)自定義的安全策略和訪問(wèn)控制。應(yīng)用程序通過(guò) System.setSecurityManager()/“-Djava.security.manager”來(lái)指定/啟動(dòng)安全管理器,每當(dāng) JavaAPI執(zhí)行一些可能不安全的操作時(shí),如對(duì)文件的讀寫(xiě)和刪除等,就會(huì)向安全管理器進(jìn)行權(quán)限檢查,若權(quán)限檢查不通過(guò),將會(huì)拋出一個(gè)安全異常,若權(quán)限檢 查通過(guò),則允許該操作的執(zhí)行。

比如創(chuàng)建一個(gè)FileInputStream時(shí),會(huì)調(diào)用SecurityManager的checkRead()進(jìn)行讀取權(quán)限的檢查:

1
2
3
4
5
6
7
8
9
10
11
12
13
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()即以讀動(dòng)作的FilePermission為參數(shù)調(diào)用checkPermission()

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

jdk1.2版本后,可以使用checkPermission(Permission perm)和checkPermission(Permission perm, Object context)來(lái)進(jìn)行權(quán)限檢查,其中perm為請(qǐng)求執(zhí)行操作所需要的權(quán)限,如java.io.FilePermission對(duì)“/usr /indata.txt”請(qǐng)求“read”操作。checkPermission()實(shí)際上在對(duì)當(dāng)前線程的方法棧進(jìn)行優(yōu)化后,獲得一個(gè)訪問(wèn)控制環(huán)境 AccessControlContext,并調(diào)用其checkPermission()方法

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
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相關(guān)
            return;
        }
 
        AccessControlContext acc = stack.optimize();
        acc.checkPermission(perm);
    }

checkPermission()會(huì)從方法的棧頂向棧底遍歷(檢查方法所在類(lèi)的保護(hù)域權(quán)限,context是一個(gè) ProtectionDomain數(shù)組),當(dāng)遇到一個(gè)沒(méi)有權(quán)限的棧幀就會(huì)拋出一個(gè)AccessControlException。即對(duì)于一次需要進(jìn)行權(quán)限 檢查的訪問(wèn),對(duì)于該訪問(wèn)的方法的每一個(gè)調(diào)用層次都必須具有對(duì)應(yīng)的訪問(wèn)權(quán)限。
對(duì)權(quán)限的判定是通過(guò)implies()來(lái)進(jìn)行的,implies()在Permission類(lèi)、PermissionCollection、ProtectionDomain類(lèi)中聲明。在Permission類(lèi)(具體實(shí)現(xiàn)的子類(lèi))中,該方法將確定由該P(yáng)ermission所代表的對(duì)象,是否隱含了將要判斷的Permission對(duì)象的權(quán)限中, 如對(duì)”/test/*”目錄的讀寫(xiě)權(quán)限testAllPermission,隱含了對(duì)”/test/test.txt”文件的讀寫(xiě)權(quán)限 testFilePermission,即testAllPermission.implies(testFilePermission) 的值為true,反之為false。 在ProtectionDomain(其PermissionCollection)中,將進(jìn)行權(quán)限集合內(nèi) implies()的判定,實(shí)際上就是在PermissionCollection中遍歷保護(hù)域所擁有的權(quán)限,調(diào)用implies()判定其是否具有對(duì)應(yīng)的訪問(wèn)權(quán)限。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
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;
    }

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

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
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()中,會(huì)調(diào)用preDefineClass()獲取ProtectionDomain:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
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;
    }

當(dāng)沒(méi)有指定保護(hù)域時(shí),就會(huì)為其指定一個(gè)空的保護(hù)域,若指定了保護(hù)域則使用加載器所指定的保護(hù)域。

類(lèi)加載器的實(shí)現(xiàn)可以通過(guò)將代碼來(lái)源(CodeSource),即代碼庫(kù)和該class文件的所有簽名者信息,傳遞給當(dāng)前的 Policy對(duì)象的getPermissions()方法,來(lái)查詢(xún)?cè)摯a來(lái)源所擁有的權(quán)限集合PermissionCollection(在策略初始化時(shí) 生成),并以此構(gòu)造一個(gè)保護(hù)域傳遞給defineClass(),以此指定類(lèi)的保護(hù)域。

(3).Java應(yīng)用程序訪問(wèn)控制策略是由抽象類(lèi)java.security.Policy的子類(lèi)實(shí)例所描述的,通過(guò)設(shè)置 policy.provider屬性值來(lái)指定Policy的實(shí)現(xiàn)類(lèi),該屬性值定義在/jre/lib/security/java.security文件 中

1
2
3
4
5
#
# 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

可見(jiàn)默認(rèn)是使用PolicyFile類(lèi)來(lái)實(shí)現(xiàn)訪問(wèn)控制策略,該類(lèi)將使用從策略文件中讀取并解析訪問(wèn)控制策略的方式形成策略。
也可以通過(guò)實(shí)現(xiàn)自己的Policy并調(diào)用Policy的setPolicy()方法來(lái)替換當(dāng)前Policy對(duì)象。

對(duì)Java應(yīng)用程序的訪問(wèn)控制策略是由抽象類(lèi)java.security.Policy的子類(lèi)實(shí)例實(shí)現(xiàn)的,其實(shí)現(xiàn)方式可以采用很多種方法,如從一個(gè) 結(jié)構(gòu)化ASCII文件中讀取,從一個(gè)Policy的二進(jìn)制class文件中讀取,從一個(gè)數(shù)據(jù)庫(kù)中讀取,PolicyFile就是使用了從ASCII策略文 件中讀取的方法,策略文件定義在/jre/lib/security/java.security中:

1
2
3
4
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來(lái)指定用戶(hù)自己想要的策略,也可以在運(yùn)行時(shí)使用”-Djava.security.policy”命令行參數(shù)進(jìn)行設(shè)置,如:
-Djava.security.manager -Djava.security.policy = mypolicy.txt
其中如果沒(méi)有指定java.security.manager,那么應(yīng)用程序就不會(huì)安裝任何的安全管理器,而代碼也就沒(méi)有任何權(quán)限限制。mypolicy.txt就是用戶(hù)自頂一個(gè)策略文件,這里使用的是相對(duì)路徑,將使用程序的啟動(dòng)目錄

以/jre/lib/security/java.policy為例說(shuō)明策略文件,在該文件中使用上下文無(wú)關(guān)文法描述安全策略
如:

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

policy文件的基本語(yǔ)法如下:

1
2
3
4
5
6
7
8
9
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指定了簽名者的公鑰的證書(shū)文件所在位置,可以使相對(duì)URL,如keystore “mykey”,這個(gè)相對(duì)路徑指向了程序使用該策略文件的啟動(dòng)目錄,比如,該策略文件由policy.url.x指定在”e://security /policy/mypolicy.txt”,那么公鑰證書(shū)就在”e://security/policy/mykey”文件中。當(dāng)然也可以使用絕對(duì)路徑 指定公鑰路徑。
    keystore_type指定了密鑰倉(cāng)庫(kù)信息的存儲(chǔ)和數(shù)據(jù)格式,也定義了保護(hù)密鑰倉(cāng)庫(kù)中私鑰和密鑰倉(cāng)庫(kù)完整性的算法,默認(rèn)將使”JKS”類(lèi)型
  • grant子句:
    授予 指定類(lèi)型(代碼) 指定權(quán)限
    其中對(duì)代碼類(lèi)型的描述有兩種:
    signedBy表示簽名者別名,可以是由”,”分隔的若干個(gè)簽名者
    codeBase表示一個(gè)特定的加載位置,從該目錄下加載的代碼都將被賦予特定的權(quán)限
  • permission:
    permission由權(quán)限類(lèi)型、操作目標(biāo)、操作動(dòng)作三部分組成,如
    permission java.io.FilePermission “note.txt” “read”即為對(duì)程序啟動(dòng)目錄下(相對(duì)路徑)的note.txt的讀取權(quán)限

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

Doer接口:

1
2
3
4
5
6
// /com/ice/security/doer/Doer.java
package com.ice.security.doer;
 
public interface Doer {
    void doYourThing();
}

Doer的實(shí)現(xiàn)類(lèi)Friend,由Friend所簽名,將作為受信認(rèn)類(lèi)訪問(wèn)“friend.txt”和“stranger.txt”

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
// /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的實(shí)現(xiàn)類(lèi)Stranger,由Stranger所簽名,作為不受信認(rèn)類(lèi),僅能訪問(wèn)“stranger.txt”

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
//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文件的顯示輸出類(lèi)TextFileDisplayer:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
//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分別導(dǎo)出為jar文件,放在指定目錄(這里放在”E:/java/security”目錄下)以待不同的機(jī)構(gòu) 進(jìn)行簽名,F(xiàn)riend所在包假定為比較有信用的機(jī)構(gòu)”friend”進(jìn)行簽名,而Stranger所在包假定為一個(gè)不受信任的機(jī)構(gòu)”stranger” 進(jìn)行簽名。
(1).調(diào)用命令 jar cvf xxx.jar <_ClassPath> 進(jìn)行打包
(注意打包后,若沒(méi)有把jar包放在單獨(dú)的目錄下,需要?jiǎng)h除原java文件編譯產(chǎn)生的class文件,以免程序運(yùn)行直接加載目錄下class文件而非包內(nèi)的class文件)
這里分別調(diào)用
jar cvf friend.jar com/ice/security/friend/*.class 將friend包內(nèi)的class文件打包

jar cvf stranger.jar com/ice/security/stranger/*.class 將friend包內(nèi)的class文件打包

(2).使用keytool可以用來(lái)生成新的密鑰對(duì),并與一個(gè)別名關(guān)聯(lián),用密碼加以保護(hù)存放在keystore文件中
使用keytool -genkey -alias friend -keypass 123456 -validity 10000 -keystore mykey 命令:

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

類(lèi)似地,生成一個(gè)別名stranger的密鑰對(duì)

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

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

(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
導(dǎo)出friend和stranger的發(fā)行證書(shū)

 

2.編寫(xiě)自己的策略文件,放在當(dāng)前目錄下

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
//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簽名的類(lèi)和${com.ice.home}.com(后面設(shè)置 為”e:/java/security/com”,存放著Doer接口的class文件)可以讀取”friend.txt” 和”stranger.txt”,而stranger簽名的類(lèi)只能讀取”stranger.txt”
(這里為了方便,直接使用mykey而非發(fā)布的證書(shū))
(1).添加Doer接口類(lèi)的class文件(對(duì)應(yīng)路徑)和friend.txt和stranger.txt兩個(gè)測(cè)試文件
(2).通過(guò)權(quán)限檢查的例子:

1
2
3
4
5
6
7
8
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();
    }
}

調(diào)用java -Djava.security.manager -Djava.security.policy=mypolicy.txt -Dcom.ice.home=e:/java/security -cp .;friend.jar;stranger.jar ProtectionDomainTest測(cè)試運(yùn)行,其中指定了com.ice.home的路徑,通過(guò)-cp設(shè)置了類(lèi)路徑

(3).不能通過(guò)權(quán)限檢查的例子:

1
2
3
4
5
6
7
8
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)類(lèi)似,但stranger會(huì)嘗試讓friend讀取”friend.txt”,這會(huì)被阻止

全能程序員交流QQ群290551701,聚集很多互聯(lián)網(wǎng)精英,技術(shù)總監(jiān),架構(gòu)師,項(xiàng)目經(jīng)理!開(kāi)源技術(shù)研究,歡迎業(yè)內(nèi)人士,大牛及新手有志于從事IT行業(yè)人員進(jìn)入!


發(fā)表評(píng)論 共有條評(píng)論
用戶(hù)名: 密碼:
驗(yàn)證碼: 匿名發(fā)表
主站蜘蛛池模板: 华宁县| 普洱| 罗平县| 株洲县| 永城市| 监利县| 溧水县| 肃宁县| 思茅市| 永福县| 乌拉特后旗| 东源县| 双江| 绥滨县| 象州县| 米易县| 高雄县| 北安市| 绍兴县| 鞍山市| 海盐县| 太谷县| 大城县| 普兰县| 大英县| 海宁市| 延安市| 弥勒县| 凉城县| 千阳县| 慈溪市| 云龙县| 兰考县| 龙江县| 东安县| 黄平县| 澳门| 文山县| 绩溪县| 沭阳县| 镇江市|