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

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

類加載器的做用就是將真實的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()方法實現:安全

 1 protected Class<?> loadClass(String name, boolean resolve)
 2         throws ClassNotFoundException
 3     {
 4         synchronized (getClassLoadingLock(name)) {
 5             // First, check if the class has already been loaded
 6             Class c = findLoadedClass(name);
 7             // 該類沒有被加載
 8             if (c == null) { 
 9                 long t0 = System.nanoTime();
10                 try {
11                     if (parent != null) {
12                     // 先交由父加載器嘗試加載
13                         c = parent.loadClass(name, false);
14                     } else {
15                     // 父加載器爲空,即爲BootstrapClassLoader,那麼查看啓動類中是否有該類
16                         c = findBootstrapClassOrNull(name);
17                     }
18                 } catch (ClassNotFoundException e) {
19                     // ClassNotFoundException thrown if class not found
20                     // from the non-null parent class loader
21                 }
22                 //父類沒法加載該類,則由本身嘗試加載
23                 if (c == null) {
24                     // If still not found, then invoke findClass in order
25                     // to find the class.
26                     long t1 = System.nanoTime();
27                     c = findClass(name);
28 
29                     // this is the defining class loader; record the stats
30                     sun.misc.PerfCounter.getParentDelegationTime().addTime(t1 - t0);
31                     sun.misc.PerfCounter.getFindClassTime().addElapsedTimeFrom(t1);
32                     sun.misc.PerfCounter.getFindClasses().increment();
33                 }
34             }
35             if (resolve) {
36                 resolveClass(c);
37             }
38             return c;
39         }
40     }

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

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

2.命名空間 post


  類加載體系爲不一樣類加載器加載的類提供不一樣的命名空間,同一命名空間內的類能夠互相訪問,不一樣命名空間的類不知道彼此的存在(除非顯式提供訪問機制)。同一類能夠再不一樣的命名空間內,但沒法在同一命名空間內重複出現。測試

命名空間是這樣定義的:實際完成加載類的工做的加載器爲定義類加載器,而加載的雙親委託路徑上的全部加載器爲初始類加載器,某個加載器的命名空間就是全部以該加載器爲初始類加載器的類所組成。優化

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

以本身實現的類加載器爲例:

  1 package com.ice.classloader;
  2 
  3 
  4 import java.io.ByteArrayOutputStream;
  5 import java.io.File;
  6 import java.io.FileInputStream;
  7 import java.io.FileNotFoundException;
  8 import java.io.IOException;
  9 import java.io.InputStream;
 10 
 11 public class MyClassLoader extends ClassLoader {
 12 
 13     private String name;    //加載器名稱
 14     private String path = "E:\\WorkSpace\\ClassLoaderTest\\";  //加載路徑
 15     private static final String HOME = "E:\\WorkSpace\\ClassLoaderTest\\";
 16     private final String classFileType = ".class"; 
 17 
 18     public MyClassLoader(String name) {
 19         super(); 
 20         this.name = name;
 21     }
 22 
 23     public MyClassLoader(ClassLoader parent, String name) {
 24         super(parent); 
 25         this.name = name;
 26     }
 27 
 28     @Override
 29     public String toString() {
 30         return this.name;
 31     }
 32 
 33     public String getPath() {
 34         return path;
 35     }
 36 
 37     public void setPath(String path) {
 38         this.path = path;
 39     }
 40 
 41     @Override
 42     public Class<?> findClass(String name) throws ClassNotFoundException {
 43         byte[] data = this.loadClassData(name);
 44         if(data == null)
 45             throw new ClassNotFoundException();
 46         return this.defineClass(name, data, 0, data.length);
 47 
 48     }
 49 
 50     private byte[] loadClassData(String name) {
 51 
 52         InputStream is = null;
 53         byte[] data = null;
 54         ByteArrayOutputStream baos = null;
 55 //        System.out.println("  classloader:" + this.name + " try to load");
 56         try {
 57             //類名轉化爲路徑
 58             name = name.replace(".", "\\");
 59             is = new FileInputStream(new File(path + name + classFileType));
 60 
 61             baos = new ByteArrayOutputStream();
 62             int ch = 0;
 63             while (-1 != (ch = is.read())) {
 64 
 65                 baos.write(ch);
 66             }
 67 
 68             data = baos.toByteArray();
 69         }
 70         catch (FileNotFoundException e) {
 71 //            e.printStackTrace();
 72             return null;
 73         }
 74         catch (IOException ioe) {
 75             ioe.printStackTrace();
 76       }
 77         finally {
 78             try {
 79                 is.close();
 80                 baos.close();
 81             }
 82             catch (Exception e2) {
 83             }
 84         }
 85         return data;
 86     }
 87 
 88 
 89     public static void main(String[] args) throws Exception {
 90         //假定的系統加載器
 91         MyClassLoader father = new MyClassLoader("father");
 92         father.setPath(HOME + "syslib\\");
 93 
 94         MyClassLoader child = new MyClassLoader(father, "child");
 95         child.setPath(HOME + "ext\\");
 96 
 97         MyClassLoader user = new MyClassLoader("user");
 98         user.setPath(HOME + "usr\\");
 99         System.out.println("-------------test parent--------------");
100         //測試父加載器關係
101         traverseParent(child);
102         System.out.println("-------------test load begin from child--------------");
103         //測試加載
104         test(child);
105         //測試命名空間
106         System.out.println("-------------test namespace--------------");
107         testNameSpace(user);
108 
109     }
110 
111     public static void traverseParent(ClassLoader loader) throws Exception{
112         if(loader == null) return;
113         System.out.println("travase classloader:" + loader.toString());
114         while(loader.getParent() != null){
115             System.out.println(loader.getParent());
116             loader = loader.getParent();
117         }
118     }
119 
120     public static void test(ClassLoader loader) throws Exception {
121         Class<?> clazz = loader.loadClass("com.ice.classloader.LoadedClass");
122         Object object = clazz.newInstance();
123     }
124 
125     public static void testNameSpace(ClassLoader loader) throws Exception {
126         Class<?> clazz = loader.loadClass("com.ice.classloader.LoadedClass");
127         Object object = clazz.newInstance();
128         try{
129             LoadedClass lc = (LoadedClass) object;
130         }catch(Exception e){
131             e.printStackTrace();
132         }
133     }
134 }

  被加載類LoadedClass的定義以下:

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

(1).雙親委派結果 
  child加載器會委託father進行加載,若father的加載目錄下存在着對應的class文件,則會由父加載器father進行對應的加載工做(father也會交由AppClassLoader和ExtClassLoader嘗試進行加載,但這兩個加載器並不知道如何加載,故而最後會本身嘗試進行加載) 

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

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

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

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

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

 1 package com.ice.classloader;
 2 
 3 public class Virus {
 4 
 5     public Virus() {
 6         System.out.println("Virus is loaded by: "
 7                 + this.getClass().getClassLoader());
 8         MyClassLoader cl = (MyClassLoader) this.getClass().getClassLoader();
 9         System.out.println("secret is:" + cl.secret);
10     }
11 
12 }

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

 1 public class MyClassLoader extends ClassLoader {
 2     protected int secret = -1;
 3 //...
 4     public static void main(String[] args) throws Exception {
 5             //其父加載器爲Bootstrap ClassLoader
 6             MyClassLoader loader = new MyClassLoader(null, "loader");
 7             loader.setPath(HOME + "usr\\");
 8 
 9             MyClassLoader attacker = new MyClassLoader("attacker");
10             attacker.setPath(HOME + "attacker\\");
11 
12             System.out.println("MyClassLoader's classloader:" + MyClassLoader.class.getClassLoader());
13 
14             System.out.println("-------------test parent--------------");
15             //測試父加載器關係
16             traverseParent(attacker);
17 
18             System.out.println("-------------test in-package access--------------");
19             testVirus(attacker);
20 
21         }
22 
23         public static void traverseParent(ClassLoader loader) throws Exception{
24             if(loader == null) return;
25             System.out.println("travase classloader:" + loader.toString());
26             while(loader.getParent() != null){
27                 System.out.println(loader.getParent());
28                 loader = loader.getParent();
29             }
30         }
31 
32 
33         public static void testVirus(ClassLoader loader) throws Exception {
34             Class<?> clazz = loader.loadClass("com.ice.classloader.Virus");
35             Object object = clazz.newInstance();
36         }
37     }

結果以下: 

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

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

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

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

 1 public FileInputStream(File file) throws FileNotFoundException {
 2         String name = (file != null ? file.getPath() : null);
 3         SecurityManager security = System.getSecurityManager();
 4         if (security != null) {
 5             security.checkRead(name);
 6         }
 7         if (name == null) {
 8             throw new NullPointerException();
 9         }
10         fd = new FileDescriptor();
11         fd.incrementAndGetUseCount();
12         open(name);
13     }

  checkRead()即以讀動做的FilePermission爲參數調用checkPermission()

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

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

 1 public static void checkPermission(Permission perm)
 2                  throws AccessControlException
 3     {
 4         //System.err.println("checkPermission "+perm);
 5         //Thread.currentThread().dumpStack();
 6 
 7         if (perm == null) {
 8             throw new NullPointerException("permission can't be null");
 9         }
10 
11         AccessControlContext stack = getStackAccessControlContext();
12         // if context is null, we had privileged system code on the stack.
13         if (stack == null) {
14            //...debug相關
15             return;
16         }
17 
18         AccessControlContext acc = stack.optimize();
19         acc.checkPermission(perm);
20     }

  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()斷定其是否具備對應的訪問權限。

 1 public void checkPermission(Permission perm)
 2         throws AccessControlException
 3     {
 4        //...
 5         if (context == null)
 6             return;
 7 
 8         for (int i=0; i< context.length; i++) {
 9             if (context[i] != null &&  !context[i].implies(perm)) {
10                 //...
11                 throw new AccessControlException("access denied "+perm, perm);
12             }
13         }
14 
15         // ...
16 
17         return;
18     }

那麼,類的訪問權限(保護域)是如何指定的? 
(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實例:

 1 protected final Class<?> defineClass(String name, byte[] b, int off, int len,
 2                                          ProtectionDomain protectionDomain)
 3         throws ClassFormatError
 4     {
 5         protectionDomain = preDefineClass(name, protectionDomain);
 6 
 7         Class c = null;
 8         String source = defineClassSourceLocation(protectionDomain);
 9 
10         try {
11             c = defineClass1(name, b, off, len, protectionDomain, source);
12         } catch (ClassFormatError cfe) {
13             c = defineTransformedClass(name, b, off, len, protectionDomain, cfe,
14                                        source);
15         }
16 
17         postDefineClass(c, protectionDomain);
18         return c;
19     }

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

 1 private ProtectionDomain preDefineClass(String name,
 2                                             ProtectionDomain pd)
 3     {
 4         if (!checkName(name))
 5             throw new NoClassDefFoundError("IllegalName: " + name);
 6 
 7         if ((name != null) && name.startsWith("java.")) {
 8             throw new SecurityException
 9                 ("Prohibited package name: " +
10                  name.substring(0, name.lastIndexOf('.')));
11         }
12         if (pd == null) {
13             pd = defaultDomain;
14         }
15 
16         if (name != null) checkCerts(name, pd.getCodeSource());
17 
18         return pd;
19     }

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

類加載器的實現能夠經過將代碼來源(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接口:

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

Doer的實現類Friend,由Friend所簽名,將做爲受信認類訪問「friend.txt」和「stranger.txt」

 1 // /com/ice/security/friend/Friend.java
 2 package com.ice.security.friend;
 3 
 4 import java.security.AccessController;
 5 import java.security.PrivilegedAction;
 6 
 7 import com.ice.security.doer.Doer;
 8 
 9 public class Friend implements Doer{
10     private Doer next;
11     private boolean direct;
12 
13     public Friend(Doer next, boolean direct){
14         this.next = next;
15         this.direct = direct;
16     }
17 
18     @Override
19     public void doYourThing() {
20         if(direct){
21             next.doYourThing();
22         }else{
23             AccessController.doPrivileged(
24                     new PrivilegedAction() {
25                         public Object run(){
26                             next.doYourThing();
27                             return null;
28                         }
29                     }
30             );
31         }
32     }
33 
34 }

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:

 1 //TextFileDisplayer.java
 2 import java.io.CharArrayWriter;
 3 import java.io.FileReader;
 4 import java.io.IOException;
 5 
 6 import com.ice.security.doer.Doer;
 7 
 8 
 9 public class TextFileDisplayer implements Doer{
10     private String fileName;
11     public TextFileDisplayer(String fileName){
12         this.fileName = fileName;
13     }
14 
15     @Override
16     public void doYourThing() {
17             try{
18                 FileReader fr = new FileReader(fileName);
19                 try {
20                     CharArrayWriter caw = new CharArrayWriter();
21                     int c;
22                     while((c = fr.read()) != -1){
23                         caw.write(c);
24                     }
25                     System.out.println(caw.toString());
26                 } catch (IOException e) {
27 
28                 }finally{
29                     try{
30                         fr.close();
31                     }catch (IOException e){
32 
33                     }
34                 }
35             }catch (IOException e) {
36 
37             }
38     }
39 
40 }

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文件打包 

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

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

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

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

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

能夠看到在目錄下生成了一個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的發行證書 

 

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).經過權限檢查的例子:

1 public class ProtectionDomainTest {
2     public static void main(String[] args){
3     TextFileDisplayer tfd = new TextFileDisplayer("stranger.txt");
4     Friend friend = new Friend(tfd, true);
5     Stranger stranger = new Stranger(friend, true);
6     stranger.doYourThing();
7     }
8 }

調用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設置了類路徑 

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

1 public class ProtectionDomainTest {
2     public static void main(String[] args){
3     TextFileDisplayer tfd = new TextFileDisplayer("friend.txt");
4     Friend friend = new Friend(tfd, true);
5     Stranger stranger = new Stranger(friend, true);
6     stranger.doYourThing();
7     }
8 }

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

相關文章
相關標籤/搜索