1. 程式人生 > >java之jvm學習筆記十一(訪問控制器)

java之jvm學習筆記十一(訪問控制器)

                 這一節,我們要學習的是訪問控制器,在閱讀本節之前,如果沒有前面幾節的基礎,對你來說可能會比較困難!

知識回顧:

                  我們先來回顧一下前幾節的內容,在筆記三的時候我們學了類裝載器,它主要的功能就是裝載類,在裝載的前後,class檔案校驗器會對class檔案進行四趟的校驗,而第一趟的校驗會對檔案的結構進行校驗,對檔案的結構完整性的校驗時會校驗class檔案的hash摘要是否一致以確定檔案沒有中途被修改過,所以基於class檔案校驗我們又學習了jar的認證和簽名,當class檔案被裝載到記憶體的時候,一個應用啟動時,jvm會為該應用生成一個Policy

的單例物件,它用於讀取策略檔案的grant資訊,當類裝載器裝載一個類的時候,它根據jar包中的簽名信息、證書、jar的url資訊生成一個CodeSource物件,CodeSource物件向Policy物件索要一個PermissionCollecion許可權集合,它是由各個grant子句中的permission語句的例項對映,再由CodeSource物件、PermissionCollecion許可權集合、類載入器交由類載入器的defineClass方法組成了ProtectionDomain保護域。最後class位元組碼在記憶體中被放在了這個保護域中。

                 是的內容非常的多,概念也非常的多,所以如果你對前面的知識回顧一頭霧水,建議還是倒回去把那些基礎的概念再補一補。

                  回顧完目前為止的所有知識之後,我們需要解決兩個問題

                  第一,什麼是訪問控制器。

                  第二,它是怎麼樣和安全管理器配合工作的。

                  我們先來簡單的回答第一個問題,你可以聽不明白,但是如果你耐性的往下看,在我回答第二個問題的時候,我們會做幾個比較複雜的demo,而這些複雜的demo,會在無形之中讓你真正的認識到什麼是訪問控制器。在文章的最後如果篇幅夠的話我們也會帶大家來讀一讀jdk裡的原始碼,看看他和安全管理是怎麼配合工作的。

                  那麼什麼是訪問控制器?

                  類java.security.AccessControler提供了一個預設的安全策略執行機制,他使用棧檢查機制來決定潛在的不安全操作是否被允許。這個訪問控制器不能夠被例項化,它不是一個物件,而是集合在單個類的多個靜態方法。AccesControler最核心的方法是checkPermission,這個方法決定一個特定的操作是否被允許,他接收一個Perssmission的子類物件,當AccessControler確定操作被允許,它將簡單的返回,而如果操作被禁止,它將異常中止,並丟擲一個AcssessControlException,或者是它的子類。

-----------------------------------------------------------------------基礎紮實的你完全可以忽略上面的內容----------------------------------------------------------------------------------

                   關於什麼是訪問控制器,聽不明白,不要著急,下面我們先來做一個簡單地demo,這個demo主要是為了後面我們來實現一個自己的AceessControler做準備,是關於implies這個方法理解,這個方法可以說是串聯起我們所有內容的核心。

  1. publicstaticvoid main(String[] args) {  
  2.     Permission perOne = new FilePermission("d:/tmp/test.txt",SecurityConstants.FILE_READ_ACTION);  
  3.     Permission perAll = new FilePermission("d:/tmp/*",SecurityConstants.FILE_READ_ACTION);  
  4.     System.out.println(perOne.implies(perAll));  
  5.     System.out.println(perAll.implies(perOne));  
  6. }  

輸出的結果為:

false

true

說明:implies方法就是用於判斷一個許可權的範圍是不是包含了另外一個許可權的範圍,在這個demo裡,我們試著去判斷對於perAll的許可權是否包含perOne的許可權還有perOne的許可權是否包含perAll許可權,很顯然,perAll許可權是包含perOne的。而實際上AccessControler裡有一個許可權棧,它就是遍歷棧幀中的PermissionCollecion裡的每個Permission然後呼叫裡Permission的implies來判斷是否包含某個許可權的。

下面我們來做另外的一個demo,這個demo我們採取累加型的方法一點點的新增程式碼,以讓你瞭解整個AccessControler和SecurityManager是怎麼配合著工作的,這個demo稍微會複雜一點

                 步驟一:試著實現自己的安全管理器,實驗是否成功,以下主要分三步來完成

                 第一步:實現一個自己的類MySecurityManager,它繼承自SecurityManager,重寫它的checkRead方法,我們直接讓他丟擲一個SecurityException異常。(copy吧少年,要的是你知識的儲備,不是要你把程式碼背下來),

  1. package com.yfq.test;  
  2. publicclass MySecurityManager extends SecurityManager {  
  3.     @Override
  4.     publicvoid checkRead(String file) {  
  5.         //super.checkRead(file, context);
  6.         thrownew SecurityException("你沒有的許可權");    
  7.     }  
  8. }  

                   第二步:實現一個簡單的類,主要用來測試我們自己定義的安全管理器起作用了沒有,我們這裡藉助了FileInputStream,因為FileInputStream會呼叫安全管理器去校驗許可權(我們在筆記六已經詳細的講解過),所以用FileInputStream測試我們自己的安全管理器非常的適合。    

  1. package com.yfq.test;  
  2. import java.io.FileInputStream;  
  3. import java.io.IOException;  
  4. import java.security.ProtectionDomain;  
  5. publicclass TestMySecurityManager {  
  6.     publicstaticvoid main(String[] args) {  
  7.         System.setSecurityManager(new MySecurityManager());  
  8.         try {  
  9.             FileInputStream fis = new FileInputStream("test");  
  10.              } catch (IOException e) {  
  11.             e.printStackTrace();  
  12.         }  
  13.     }  
  14. }  

現在簡單的說明一下:

1.TestMySecurityManager的main函式第一行其實就是註冊我們自己的安全管理器(還有一種安裝安全管理器的方式,記得不,如果你忘記了請你看看筆記六)

2.FileInputStream fis = new FileInputStream("test");這一行建立了一個FileInputStream物件,這個構造器內部會呼叫 public FileInputStream(File file);這個構造器,而這個構造會呼叫Ststem.getSercurityManager來取得當前的安全管理器security,然後呼叫它的checkRead方法來校驗許可權。由於我們在第一行註冊了自己的安全管理器,所以它將呼叫我們自己的安全管理器的checkRead來執行校驗。

                     第三步:執行程式

  1. Exception in thread "main" java.lang.SecurityException: 你沒有的許可權  
  2.     at com.yfq.test.MySecurityManager.checkRead(MySecurityManager.java:8)  
  3.     at java.io.FileInputStream.<init>(FileInputStream.java:100)  
  4.     at java.io.FileInputStream.<init>(FileInputStream.java:66)  
  5.     at com.yfq.test.TestMySecurityManager.main(TestMySecurityManager.java:11)  

好了,到這裡說明我們自己的安全管理器安裝上去了。上面的異常正好是我們期望見到的。

                    步驟二:我們來實現一個自己的類MyFileInputStream(當然這個不是真正意義的位元組流包裝類),它用於取代FileInputStream,它可以模擬FileInputStream是怎麼去呼叫安全管理器,怎麼去執行校驗的。

                    第一步:編寫MyFileInputStream(copy吧少年,不要自己狂敲)

  1. package com.yfq.test;  
  2. import java.io.File;  
  3. import java.io.FileNotFoundException;  
  4. publicclass MyFileInputStream {  
  5.     public MyFileInputStream(String name) throws FileNotFoundException {  
  6.         this(name != null ? new File(name) : null);  
  7.     }  
  8.     public MyFileInputStream(File file) throws FileNotFoundException {  
  9.         String name = (file != null ? file.getPath() : null);  
  10.         SecurityManager security = System.getSecurityManager();  
  11.         if (security != null) {  
  12.             security.checkRead(name);  
  13.         }  
  14.         }  
  15. }  


簡單的說一下邏輯,這個類MyFileInputStream(String name)的建構函式呼叫MyFileInputStream(File file)這個建構函式,而MyFileInputStream(File file)這個建構函式通過System.getSecurityManager();取出當前的SecurityManager,然後呼叫它的checkRead方法。是滴,這個其實是FileInputStream原始碼裡的邏輯,我只是把一些有妨礙我們理解的程式碼去掉了而已。

                    第二步,修改步驟一里的TestMySecurityManager裡的main用自己的類替換FileInputStream函式如下

  1. package com.yfq.test;  
  2. import java.io.IOException;  
  3. public class TestMySecurityManager {  
  4.     public static void main(String[] args) {  
  5.         System.setSecurityManager(new MySecurityManager());  
  6.         try {  
  7.             MyFileInputStream fis = new MyFileInputStream("test");  
  8.         } catch (IOException e) {  
  9.             e.printStackTrace();  
  10.         }  
  11.     }  
  12. }  


                   第三步,執行程式,好吧如果你用ecplise那麼肯定報錯,看看這個錯誤

 居然找不到我們的類,你鬱悶沒有,即使你跑到TestMySecurityManager.class的目錄下,再執行還是這個問題。

我就不賣關子了,還是環境變數沒有設定好。這裡涉及到一些比較基礎的問題,我簡單的提一下,不然可能永遠都講不完了

我們知道配置jdk的環境的時候我們總是習慣設定三個變數

  1. path=%JAVA_HOME%/bin  
  2. JAVA_HOME=C:/Java/jdk1.6.0_01  
  3. CLASSPATH=.;%JAVA_HOME%/lib/dt.jar;%JAVA_HOME%/lib/tools.jar      

這三個變數代表什麼意思呢?

path,其實就是我們的java工具的目錄,像我們編譯java檔案用到javac,還有執行class檔案用到的java命令,包括我前面見到的金鑰生成工具keytool和簽名工具jarsigner。都是在這個path被配置的前提下才能正常執行的。

JAVA_HOME這個僅僅是一個變數名,你喜歡改成別的名字也可以,只是呼叫它的地方需要作出對應的修改

CLASSPATH:這個就是引起我們現在問題的地方,我們知道類載入器會載入類,但是它如何知道到哪裡去載入類,這個路徑就是告訴類載入器class檔案放在了那個地方。

好了既然是這樣的話,我們來設定一下CLASSPATH,

                       第四步,設定CLASSPATH.到com.yfq.test.TestMySecurityManager所在的編譯目錄

                       在cmd視窗我們輸入java -classpath D:/workspace/MySecurityManager/bin com.yfq.test.TestMySecurityManager。

                        檢視控制檯輸出   
                

報錯的提示變了,它提示MyFileInputStream這個類找不到,但是它命名和com.yfq.test.TestMySecurityManager在同個編譯目錄下,為什麼?

好吧,這裡我就不繞彎子了,我們再修改com.yfq.test.TestMySecurityManager,將設定自己的安全管理器的那行先簡單地註釋掉如下

  1. package com.yfq.test;  
  2. import java.io.IOException;  
  3. public class TestMySecurityManager {  
  4.     public static void main(String[] args) {  
  5.         //System.setSecurityManager(new MySecurityManager());  
  6.         try {  
  7.             MyFileInputStream fis = new MyFileInputStream("test");  
  8.         } catch (IOException e) {  
  9.             e.printStackTrace();  
  10.         }  
  11.     }  
  12. }  

編譯之後再執行java -classpath D:/workspace/MySecurityManager/bin com.yfq.test.TestMySecurityManager,沒有報錯了。為什麼會這樣子???

重點:講了這麼一大篇幅,我無非要告訴你,在一般的情況下,同個執行緒中,我們用的是同一個類載入器去動態載入所需要的類檔案,但是,如果我們設定了SecurityManager的時候,情況就不一樣了,當我們設定了安全管理器之後,當前類由於需要用到安全管理器來判斷當前類是否有載入類MyFileInputStream的許可權,所以當前類會委託SecurityManager來載入MyFileInputStream,而對於SecurityManger來說它就從CLASSPATH指定的路徑載入我們的類,所以它沒有找到我們的MyFileInputStream類

                      第五步,解決SecurityManager載入類,找不到類的問題。

                      解決方案太多了,第一種方法:直接修改系統的配置CLASSPATH將MyFileInputStream所在的類加到CLASSPATH中,但是這樣太笨了。

                                                   第二種方法:直接使用set classpath命令,我們執行這個命令set classpath=.;D:/workspace/MySecurityManager/bin;%classpath%再執行

                                                   java com.yfq.test.TestMySecurityManager,問題解決。

                                                  第三種方法  : java -cp "C:\Program Files\Java\jdk1.6.0_12\lib\tools.jar";"C:\Program Files\Java\jdk1.6.0_12\lib\dt.jar";"D:/workspace/MySecurityManager/bin";. com.yfq.test.TestMySecurityManager

                                                   第四種方法:java -classpath  "C:\Program Files\Java\jdk1.6.0_12\lib\tools.jar";"C:\Program Files\Java\jdk1.6.0_12\lib\dt.jar";"D:/workspace/MySecurityManager/bin";.