<rt id="bn8ez"></rt>
<label id="bn8ez"></label>

  • <span id="bn8ez"></span>

    <label id="bn8ez"><meter id="bn8ez"></meter></label>

    Jungleford's Home BlogJava分舵

    Java技術(shù)研究,兼探討歷史話題

    BlogJava 首頁(yè) 新隨筆 聯(lián)系 聚合 管理
      24 Posts :: 0 Stories :: 53 Comments :: 0 Trackbacks

    jungleford如是說(shuō)

         安全性是
    Java鼓吹得最多的特性之一,的確,Java的安全特性涵蓋了從應(yīng)用級(jí)別到語(yǔ)言級(jí)別乃至JVM本身。以前大家都知道有個(gè)Sandbox,但僅有Sandbox尚不能滿足,或者說(shuō)不能很方便地做到我們所需要的全部安全需求,譬如現(xiàn)在一個(gè)系統(tǒng)首先起碼需要一個(gè)登錄功能,更進(jìn)一步的話,還需要對(duì)用戶訪問(wèn)資源的行為進(jìn)行約束,下面我想大致講一下Java是怎樣做這些事情的,基本上是一個(gè)總結(jié)或者說(shuō)是“讀后感”的性質(zhì),同時(shí)給出一個(gè)簡(jiǎn)單的實(shí)現(xiàn)例子,這個(gè)例子其實(shí)還是模仿人家的,呵呵……

    1.Java的訪問(wèn)控制機(jī)制

         談到訪問(wèn)控制,或者說(shuō)“授權(quán)”,這里有兩層含義,一是從資源的角度,這個(gè)socket端口是否被允許操作?這個(gè)文件是可讀的?可寫(xiě)的?還是可執(zhí)行的?還是以上都行?這就是我們?cè)赨NIX下用“l(fā)s -l”命令列出當(dāng)前目錄下文件時(shí),那些“-rwx-”之類(lèi)的含義;二是從訪問(wèn)者的角度,我想通過(guò)80端口看Web上新浪歐洲杯的新聞,在這個(gè)系統(tǒng)中有沒(méi)有這個(gè)資格?我想播放D盤(pán)上一個(gè)名為“friends.rm”的視頻文件,我得到了訪問(wèn)這個(gè)文件的權(quán)限了嗎?我有運(yùn)行播放器的權(quán)限嗎?
         Java在訪問(wèn)控制策略上同時(shí)考慮了這兩方面內(nèi)容,你說(shuō)“不對(duì)呀,我用
    FileOutputStream寫(xiě)文件,用Socket類(lèi)連接遠(yuǎn)程主機(jī)都用得好好的,沒(méi)什么限制呀”,這我們得先談?wù)勈裁唇凶觥?B>安全管理器”(SecurityManger)。安全管理器從JDK 1.0就開(kāi)始有了,多古老啊!Java從設(shè)計(jì)的那一天開(kāi)始就考慮了安全因素,安全管理器是Sandbox的最重要的一個(gè)部分,也是訪問(wèn)控制的總協(xié)調(diào)者,我們能夠在通常情況下正常使用網(wǎng)絡(luò)和文件,那是因?yàn)楫?dāng)啟動(dòng)application的時(shí)候(注意是application,不是applet!),如果你不加“-Djava.security.manager”選項(xiàng),JVM是不會(huì)啟動(dòng)Sandbox的,這時(shí)你可以“為所欲為”,而不會(huì)碰到SecurityException之類(lèi)的異常;一旦加入了“-Djava.security.manager”選項(xiàng),你就會(huì)發(fā)現(xiàn)有一連串的異常出現(xiàn)嘍!

    Exception in thread "main" java.security.AccessControlException: access denied (……)
    ……


    Java內(nèi)置了一個(gè)默認(rèn)的安全策略,這種情況下安全管理器首先裝載的是這個(gè)默認(rèn)的策略,不信啊,不信你檢查一下你的“%JAVA_HOME%\jre\lib\security\”目錄,是不是有個(gè)叫“java.policy”的文件?用notepad打開(kāi)看看:


    // Standard extensions get all permissions by default

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

    // default permissions granted to all domains


    grant { 
        // Allows any thread to stop itself using the java.lang.Thread.stop()

        // method that takes no argument.
        // Note that this permission is granted by default only to remain
        // backwards compatible.
        // It is strongly recommended that you either remove this permission
        // from this policy file or further restrict it to code sources
        // that you specify, because Thread.stop() is potentially unsafe.
        // See "http://java.sun.com/notes" for more information.
        permission java.lang.RuntimePermission "stopThread";

        // allows anyone to listen on un-privileged ports

        permission java.net.SocketPermission "localhost:1024-""listen";

        // "standard" properies that can be read by anyone


        permission java.util.PropertyPermission "java.version""read";
        permission java.util.PropertyPermission "java.vendor""read";
        permission java.util.PropertyPermission "java.vendor.url""read";
        permission java.util.PropertyPermission "java.class.version""read";
        permission java.util.PropertyPermission "os.name""read";
        permission java.util.PropertyPermission "os.version""read";
        permission java.util.PropertyPermission "os.arch""read";
        permission java.util.PropertyPermission "file.separator""read";
        permission java.util.PropertyPermission "path.separator""read";
        permission java.util.PropertyPermission "line.separator""read";

        permission java.util.PropertyPermission "java.specification.version""read";
        permission java.util.PropertyPermission "java.specification.vendor""read";
        permission java.util.PropertyPermission "java.specification.name""read";

        permission java.util.PropertyPermission "java.vm.specification.version""read";
        permission java.util.PropertyPermission "java.vm.specification.vendor""read";
        permission java.util.PropertyPermission "java.vm.specification.name""read";
        permission java.util.PropertyPermission "java.vm.version""read";
        permission java.util.PropertyPermission "java.vm.vendor""read";
        permission java.util.PropertyPermission "java.vm.name""read";
    };


    可以看到,JVM給沙箱內(nèi)的application分配的權(quán)限僅限于中止線程,監(jiān)聽(tīng)1024以上的TCP端口,以及對(duì)一些系統(tǒng)屬性的讀取權(quán)限,像一般的socket操作和文件操作的權(quán)限都沒(méi)有。
         了解了安全管理器的概念以后我們回到授權(quán)問(wèn)題上來(lái)。對(duì)用戶來(lái)說(shuō),最擔(dān)心的莫過(guò)于機(jī)器中病毒,病毒本質(zhì)上是一種惡意的程序,所以訪問(wèn)控制首先是要對(duì)代碼的權(quán)限進(jìn)行控制,上面我一直都在談Sandbox,也就是所謂的“沙箱”,熟悉Java安全性發(fā)展歷史的朋友大概對(duì)它不會(huì)陌生,初期的Java是采用這樣一種安全策略,即:本地代碼是可信的,而遠(yuǎn)程代碼是不可信的,譬如applet是一種從網(wǎng)絡(luò)上下載到本地并在瀏覽器上運(yùn)行的一段遠(yuǎn)程代碼,因而是不可信的,所以早期的applet被完全置于Sandbox當(dāng)中,得到的權(quán)限是非常有限的;在1.0以后,直至Java 2出現(xiàn)之前,安全策略作了一些靈活的改變,applet不再是完全被歧視的“二等公民”了,因?yàn)橛辛?B>簽名applet,用戶可以選擇信任這種經(jīng)過(guò)簽名的applet,從而applet也可以做一些以前被認(rèn)為是“出格”的事情;到了Java 2,情況又變了,以前一向被信任的本地代碼似乎也變得不是那么可靠了,這還真說(shuō)不準(zhǔn),難保誰(shuí)不會(huì)在你出去跟女朋友逛街的時(shí)候,偷偷溜進(jìn)來(lái)在你機(jī)器上拷個(gè)病毒什么的 ^_^ ,這樣本地代碼就落到了和遠(yuǎn)程代碼相等同的地位了,這是比較符合現(xiàn)實(shí)世界場(chǎng)景的,在Java 2中的安全策略被稱之為“可配置的安全策略”,任何代碼,只要是通過(guò)安全管理器訪問(wèn),就必須為它預(yù)先設(shè)定好訪問(wèn)權(quán)限,在這個(gè)之外的資源還是別的什么東東,對(duì)不起,

    java.security.AccessControlException: access denied……

    此路不通!


        簡(jiǎn)單總結(jié)一下Java安全模型的發(fā)展史,大概就是下面的幾幅圖了:
    o_security1.0.gif


    o_security1.1.gif

    o_security1.2.gif
         由于現(xiàn)在普遍是多用戶的系統(tǒng),所以在實(shí)現(xiàn)代碼級(jí)訪問(wèn)控制之外,我們還希望能夠?qū)τ脩舻男袨檫M(jìn)行約束,因?yàn)閷?duì)系統(tǒng)造成破壞的因素不僅僅是惡意代碼,人自身的有意或無(wú)意的不當(dāng)操作也會(huì)危及系統(tǒng),譬如向上面說(shuō)的你不在的時(shí)候別人可以在你機(jī)器上拷病毒,如果系統(tǒng)能在你不在的時(shí)候也能拒絕這個(gè)家伙的登錄企圖,那樣麻煩豈不是少很多?于是在Java安全核心之外,提供了一個(gè)名為“Java認(rèn)證與授權(quán)服務(wù)”(Java Authentication and Authorization Services,JAAS)東東,專門(mén)用來(lái)處理對(duì)用戶的認(rèn)證和授權(quán),這也就是所謂的“以用戶為中心的授權(quán)模型”,說(shuō)白了就是在“以代碼為中心的授權(quán)模型”上再加一層,首先用戶要獲得訪問(wèn)權(quán)限,然后用戶去操縱代碼,代碼來(lái)實(shí)行真正的訪問(wèn)操作。下面我主要是講講JAAS是如何工作的。

    2.了解幾個(gè)主要的API

         JAAS的API基本上位于javax.security.auth包及其下屬子包中,很容易找到的。

  • javax.security.auth.Subject
         Subject表征系統(tǒng)中一個(gè)認(rèn)證的用戶,這個(gè)詞時(shí)而被譯為“主題”時(shí)而被以為“主體”(下面我要談到的Principal有時(shí)候也被譯為“主體”),不管它有幾個(gè)馬甲,反正你就可以看成是在Java中你這個(gè)人的影子,你對(duì)系統(tǒng)的訪問(wèn)就體現(xiàn)為Subject.doAs()或Subject.doAsPrivileged()方法。

     

  • java.security.Principal
         Principal代表用戶的一種身份對(duì)象,一個(gè)用戶的身份可能不只一個(gè),他所在的組或所擔(dān)任的角色也是一種身份,“張翠山”可以說(shuō)“鐵劃銀鉤”,可以說(shuō)“張三豐的徒弟”,可以說(shuō)“張無(wú)忌他老爹”,我說(shuō)“武當(dāng)七俠”甚至“武當(dāng)派”,當(dāng)然也沒(méi)錯(cuò),這是一個(gè)組,呵呵。通過(guò)一次登錄后,可能向Subject插入一個(gè)或多個(gè)Principal,這時(shí)候Subject才有實(shí)際意義,而不是一個(gè)空殼。

     

  • javax.security.auth.login.LoginContext
         LoginContext旨在提供一個(gè)開(kāi)放的登錄總接口,你只需要用從策略文件中取得的策略名,以及下面介紹的回調(diào)對(duì)象創(chuàng)建得到一個(gè)LoginContext,再調(diào)用一次login()方法即可完成登錄,登錄模塊在這里是透明的。


  • javax.security.auth.spi.LoginModule
         登錄模塊實(shí)現(xiàn)了對(duì)用戶的認(rèn)證邏輯,它的作用是在登錄配置文件中得到體現(xiàn),在后面的例子里我們會(huì)看到怎么編寫(xiě)一個(gè)登錄配置文件以及上面說(shuō)過(guò)的策略文件。LoginModule接口包括五個(gè)主要的方法:

            
    initialize方法,初始化模塊,保存當(dāng)前Subject以及一些參數(shù)。

            
    login方法,判斷一次登錄過(guò)程中是否認(rèn)證通過(guò)。

            
    commit方法,是否提交登錄結(jié)果。咦,login不就行了嗎?干嗎要來(lái)個(gè)提交呢?這是因?yàn)镴AAS采用的是類(lèi)似于數(shù)據(jù)庫(kù)事務(wù)處理的過(guò)程,將整體登錄分為兩階段,盡管你login成功,但系統(tǒng)仍有權(quán)力根據(jù)你這次login的“地位”來(lái)決定究竟要不要接納你的身份,只有通過(guò)commit,用戶的Principal才會(huì)被真正添加到Subject當(dāng)中,哼哼,真陰險(xiǎn)!這里所說(shuō)的login的“地位”是指策略文件中登錄模塊的“控制標(biāo)記”選項(xiàng),有點(diǎn)類(lèi)似于優(yōu)先級(jí)的概念,因?yàn)榈卿浺粋€(gè)系統(tǒng)的過(guò)程可能會(huì)經(jīng)過(guò)不止一個(gè)登錄模塊,譬如我們登錄系統(tǒng)輸入口令,但這個(gè)口令可能保存在一個(gè)數(shù)據(jù)庫(kù)或LDAP目錄中,訪問(wèn)這個(gè)數(shù)據(jù)源也需要經(jīng)過(guò)認(rèn)證,這就不止一個(gè)登錄模塊了吧?所以我們需要分清哪些認(rèn)證過(guò)程是重要的,哪些又是次要的,系統(tǒng)對(duì)用戶身份的接收與否是對(duì)這些策略綜合權(quán)衡的結(jié)果。

            
    abort方法:哎呀,上面解釋得是不是太多了?我們?cè)倏纯碼bort,還記得數(shù)據(jù)庫(kù)事務(wù)處理的回退過(guò)程(roll back)嗎?abort就有點(diǎn)像roll back,表示系統(tǒng)并不接受你的身份,以前做過(guò)的統(tǒng)統(tǒng)作廢,現(xiàn)場(chǎng)又恢復(fù)到和登錄前完全一樣。

            
    logout方法:注銷(xiāo)過(guò)程,清除內(nèi)部狀態(tài),并刪除Subject中全部的Principal。

     

  • javax.security.auth.callback.CallbackHandler
         回調(diào)對(duì)象是JAAS中用以將交互過(guò)程和認(rèn)證邏輯分離的一種機(jī)制,這也是符合OO和松散耦合(loosely coupled是一個(gè)時(shí)髦詞匯 ^_^)精神的。JAAS已經(jīng)實(shí)現(xiàn)了一些常用的回調(diào)對(duì)象,包括取得用戶名的NameCallback,取得口令的PasswordCallback,從終端獲得輸入文本的TextInputCallback,向終端發(fā)出文本消息的TextOutputCallback等等。我們所要做的僅僅是實(shí)現(xiàn)一個(gè)CallbackHandler接口,根據(jù)不同的交互信息類(lèi)型,把從終端得到的信息填到相應(yīng)的Callback中去就行了。后面的例子我是用了一個(gè)JoptionPane提示文本框來(lái)輸入用戶名和口令的。

     

  • java.security.PrivilegedAction
          上面說(shuō)了那么多登錄相關(guān)的接口,該說(shuō)說(shuō)授權(quán)了,如果我們只談寫(xiě)源代碼,那么很簡(jiǎn)單,只要實(shí)現(xiàn)一個(gè)PrivilegedAction接口,覆蓋一個(gè)run()方法,把你想要做的事情統(tǒng)統(tǒng)放到這個(gè)run中就可以了。但我說(shuō)的只是寫(xiě)源代碼部分,授權(quán)方面用得較多的還是在管理方面,譬如如何編寫(xiě)一個(gè)策略文件,下面我們就來(lái)看看JAAS登錄和訪問(wèn)控制的一個(gè)完整流程。

    3.基本流程
          JAAS被稱為是“可插拔的認(rèn)證框架”(Pluggable Authentication Module,PAMs),其實(shí)PAM也不是SUN的專利,Linux上就有這方面的實(shí)現(xiàn),但PAM確實(shí)是較早用在了Solaris系統(tǒng)上。我們看看JAAS在認(rèn)證和授權(quán)方面是怎么體現(xiàn)PAM思想的:

        主要包括這么幾個(gè)部分:
        用戶的Principal(MyPrincipal.class)
        登錄模塊(MyLoginModule.class)
        回調(diào)對(duì)象(MyCallbackHandler.class)
        訪問(wèn)代碼(MyAction.class)
        系統(tǒng)入口(JAASTest.class)
        資源(myfile.txt)
        策略配置文件(login.conf)
        登錄配置文件(jaas.policy)
        啟動(dòng)腳本(JAASTest.bat)

          由于啟動(dòng)java的選項(xiàng)太長(zhǎng),所以寫(xiě)了一個(gè)shell,在控制臺(tái)下運(yùn)行JAASTest.bat,選項(xiàng)“-Djava.security.manager”指定啟用安全管理器,執(zhí)行的是JAASTest類(lèi)的main線程,由于shell指定選項(xiàng)“-Djava.security.policy=jaas.policy”,該策略文件允許當(dāng)前代碼創(chuàng)建LoginContext,并授權(quán)進(jìn)行其它一些操作,它首先初始化一個(gè)LoginContext,選項(xiàng)“-Djava.security.auth.login.config=login.conf”指定了登錄配置文件,所以在當(dāng)前目錄下找到文件login.conf,該文件中指定的登錄策略名稱為“JAASTest”,所以在LoginCotext中第一個(gè)參數(shù)也是“JAASTest”,同時(shí)使用我們自定義的回調(diào)對(duì)象MyCallbackHandler。創(chuàng)建LoginContext成功,可以進(jìn)行登錄了,調(diào)用LoginContext的login方法,該方法找到login.conf中的登錄模塊MyLoginModule(當(dāng)然可以有若干個(gè)登錄模塊,這里我只用了一個(gè)),執(zhí)行該模塊的登錄過(guò)程,MyLoginModule首先初始化:

    Login module initializing ...


    并使用LoginContext所賦予它的回調(diào)對(duì)象MyCallbackHandler,該回調(diào)過(guò)程彈出兩個(gè)圖形對(duì)話框,要求輸入用戶名和口令,
    o_user.jpg                            o_password.jpg

    我們使用指定的用戶名“user”和口令“l(fā)etmepass”,確定以后分別傳給當(dāng)前的NameCallback和PasswordCallback,然后回到MyLoginModule的login過(guò)程,該過(guò)程從回調(diào)對(duì)象處得到NameCallback和PasswordCallback,進(jìn)行認(rèn)證(這里僅僅是簡(jiǎn)單的用戶名和口令的對(duì)比),

    MyLoginModule: Authentication pass!


    并決定是否commit,由于在login.conf中定義該登錄模塊是required,所以是一個(gè)必須通過(guò)才能整體認(rèn)證成功的模塊。

    MyLoginModule: Add a new principal to current subject.

    如果整體得到認(rèn)證通過(guò),那么Subject就可以授權(quán)允許MyAction中的代碼了,如語(yǔ)句Subject.doAs(…)所示,該代碼的動(dòng)作是讀取當(dāng)前目錄下的myfile.txt文件,并將其內(nèi)容打印到控制臺(tái),注意到在策略文件jaas.policy中賦予MyPrincipal身份對(duì)myfile.txt的讀取權(quán)限,所以我們成功看到控制臺(tái)下出現(xiàn)

    Access successfully! Reading file:
    ==================================
    Why?
    Because they care!
    Because they want to know the truth!
    Because they want their country back!
    Because it still belongs to us as long as the people have the guts to fight for what they believe in!

    ==================================

         這是我喜歡的一部經(jīng)典影片“JFK”中檢察官Garrison激情的最后陳詞中的一段,呵呵!
         以上過(guò)程我們可以用個(gè)圖表來(lái)表示:
    o_jaas.gif 
    4.簡(jiǎn)單的例子

         以上流程中使用到的Java源代碼和配置文件如下:

    // MyPrincipal.java
    package com.jungleford.auth;
    
    import java.security.Principal;
    
    public class MyPrincipal implements Principal
    { // 一個(gè)Principal的例子
      private String name; // Principal的名字
    
      public MyPrincipal(String name)
      {
        this.name = name;
      }
    
      public String getName()
      { //取得Principal的名字
        return this.name;
      }
    
      public boolean equals(Object principal)
      { // 判斷兩個(gè)Pincipal相同的依據(jù)
        if (principal instanceof MyPrincipal)
          return this.name.equals(((MyPrincipal)principal).getName());
        else
          return false;
      }
    
      public String toString()
      { // Principal的表示
        return "MyPrincipal: " + this.name;
      }
    
      public int hashCode()
      { // 確定本對(duì)象的散列值
        // 用于有基于散列容器的場(chǎng)合,判斷在散列容器中是否是同一個(gè)對(duì)象。
        // 如果對(duì)hashCode感興趣,請(qǐng)參見(jiàn):
        // http://www-900.ibm.com/developerWorks/cn/java/j-jtp05273/
        return this.name.hashCode();
      }
    }
    

    // MyLoginModule.java
    package com.jungleford.auth;
    
    import java.util.*;
    import java.io.IOException;
    import java.security.Principal;
    import javax.security.auth.*;
    import javax.security.auth.callback.*;
    import javax.security.auth.login.*;
    import javax.security.auth.spi.*;
    
    public class MyLoginModule implements LoginModule
    { // 一個(gè)登錄模塊的例子
      private Subject subject; // 登錄主體的表征
      private CallbackHandler cbHandler; // 回調(diào)對(duì)象,提供終端下獲取用戶名、口令的界面
      private Map sharedState; // 用于緩存中間結(jié)果的共享區(qū)
      private Map options; // 用于保存某些登錄模塊所需要用到的一些配置選項(xiàng)
    
      private boolean succeeded = false; // 一次login成功的標(biāo)志
      private boolean cmtSucceeded = false; // 整體登錄成功的提交標(biāo)志
    
      private String username; // 取得用戶名
      private char[] password; // 取得口令
    
      private Principal principal; // 取得登錄后的身份標(biāo)志
    
      public void initialize(Subject subject,
                               CallbackHandler cbHandler,
                               Map sharedState,
                               Map options)
      { // 初始化過(guò)程
        System.out.println("Login module initializing ...");
        System.out.println();
        this.subject = subject;
        this.cbHandler = cbHandler;
        this.sharedState = sharedState;
        this.options = options;
      }
    
      public boolean login() throws LoginException
      { // 一次登錄過(guò)程
        if (cbHandler == null) // 尚未配置回調(diào)對(duì)象
          throw new LoginException("Error: No CallbackHandler available " +
                                      "to garner authentication information from the user");
    
        Callback[] cbs = new Callback[2]; // 僅使用用戶名回調(diào)和口令回調(diào)
        cbs[0] = new NameCallback("Login: ");
        cbs[1] = new PasswordCallback("Password: ", false);
    
        try
        {
          cbHandler.handle(cbs);
          username = ((NameCallback)cbs[0]).getName();
          char[] temp = ((PasswordCallback)cbs[1]).getPassword();
          if (temp == null)
          { // 口令為空
            temp = new char[0];
          }
          password = new char[temp.length];
          System.arraycopy(temp, 0, password, 0, temp.length);
          ((PasswordCallback)cbs[1]).clearPassword(); // 清除內(nèi)存中的口令痕跡
        }
        catch (IOException ioe)
        {
          throw new LoginException(ioe.toString());
        }
        catch (UnsupportedCallbackException uce)
        {
          throw new LoginException("Error: " + uce.getCallback().toString() +
                                      " not available to garner authentication information " +
                                      "from the user");
        }
    
        boolean usrCorrect = false; // 用戶名正確否?
        boolean pwdCorrect = false; // 口令正確否?
        if (username.equals("user")) // 目前僅允許用戶名為user的登錄
          usrCorrect = true;
        if (usrCorrect &&
            password.length == 9 &&
            password[0] == 'l' &&
            password[1] == 'e' &&
            password[2] == 't' &&
            password[3] == 'm' &&
            password[4] == 'e' &&
            password[5] == 'p' &&
            password[6] == 'a' &&
            password[7] == 's' &&
            password[8] == 's') // user的口令指定為letmepass
        {
          System.out.println("MyLoginModule: Authentication pass!");
          System.out.println();
          pwdCorrect = true;
          succeeded = true;
          return true; // 一次登錄成功
        }
        else
        {
          System.out.println("MyLoginModule: Authentication failed!");
          System.out.println();
          succeeded = false;
          username = null;
          for (int i = 0; i < password.length; i++) // 清除內(nèi)存中的口令痕跡
            password[i] = ' ';
          password = null;
          if (!usrCorrect)
          {
            throw new FailedLoginException("Username incorrect!");
          }
          else
          {
            throw new FailedLoginException("Password incorrect!");
          }
        }
      }
    
      public boolean commit() throws LoginException
      { // 根據(jù)登錄配置策略判斷是否整體登錄成功
        if (succeeded == false)
        {
          return false;
        }
        else
        {
          principal = new MyPrincipal(username);
          if (!subject.getPrincipals().contains(principal))
          subject.getPrincipals().add(principal); // 把新的身份添加到subject中
    
          System.out.println("MyLoginModule: Add a new principal to current subject.");
          System.out.println();
    
          username = null;
          for (int i = 0; i < password.length; i++) // 清除內(nèi)存中的口令痕跡
            password[i] = ' ';
          password = null;
    
          cmtSucceeded = true;
          return true;
        }
      }
    
      public boolean abort() throws LoginException
      { // 放棄登錄,將狀態(tài)復(fù)位至登錄前
        if (succeeded == false)
        {
          return false;
        }
        else if (succeeded == true && cmtSucceeded == false)
        {
          succeeded = false;
          username = null;
          if (password != null)
          {
            for (int i = 0; i < password.length; i++) // 清除內(nèi)存中的口令痕跡
              password[i] = ' ';
            password = null;
          }
          principal = null;
        }
        else
        {
          logout();
        }
        return true;
      }
    
      public boolean logout() throws LoginException
      { // 注銷(xiāo),并將狀態(tài)復(fù)位至登錄前
        subject.getPrincipals().remove(principal);
        succeeded = false;
        succeeded = cmtSucceeded;
        username = null;
        if (password != null)
        {
          for (int i = 0; i < password.length; i++) // 清除內(nèi)存中的口令痕跡
            password[i] = ' ';
          password = null;
        }
        principal = null;
        return true;
      }
    }
    

    // MyCallbackHandler.java
    package com.jungleford.auth;
    
    import java.io.IOException;
    import javax.security.auth.callback.*;
    import javax.swing.*;
    import java.awt.*;
    import java.awt.event.*;
    
    public class MyCallbackHandler implements CallbackHandler
    {
      public void handle(Callback[] cbs)
      throws IOException, UnsupportedCallbackException
      {
        String username =
        JOptionPane.showInputDialog(null,
                                      "<html>Available name: " +
                                      "<font color=\"blue\">user</font></html>",
                                      "Enter your name",
        JOptionPane.QUESTION_MESSAGE);
        String password =
        JOptionPane.showInputDialog(null,
                                      "<html>Available password: " +
                                      "<font color=\"blue\">letmepass</font></html>",
                                      "Enter your password",
        JOptionPane.QUESTION_MESSAGE);
        for (int i = 0; i < cbs.length; i++)
        {
          if (cbs[i] instanceof TextOutputCallback)
          {
            TextOutputCallback toc = (TextOutputCallback)cbs[i];
            switch (toc.getMessageType())
            {
              case TextOutputCallback.INFORMATION:
                System.out.println(toc.getMessage());
                break;
              case TextOutputCallback.ERROR:
                System.out.println("Error: " + toc.getMessage());
                break;
              case TextOutputCallback.WARNING:
                System.out.println("Warning: " + toc.getMessage());
                break;
              default:
                throw new IOException("Unsupported message type: " +
                                         toc.getMessageType());
            }
          }
          else if (cbs[i] instanceof NameCallback)
          {
            // prompt the user for a username
            NameCallback nc = (NameCallback)cbs[i];
            //System.err.print(nc.getPrompt());
            //System.err.flush();
            nc.setName(username);
          }
          else if (cbs[i] instanceof PasswordCallback)
          {
            // prompt the user for sensitive information
            PasswordCallback pc = (PasswordCallback)cbs[i];
            //System.err.print(pc.getPrompt());
            //System.err.flush();
            pc.setPassword(password.toCharArray());
          }
          else
          {
            throw new UnsupportedCallbackException(cbs[i], "Unrecognized Callback");
          }
        }
      }
    }
    

    //MyAction.java
    package com.jungleford.auth;
    
    import java.io.*;
    import java.security.*;
    
    public class MyAction implements PrivilegedAction
    { // 對(duì)資源的授權(quán)訪問(wèn)動(dòng)作
      public Object run()
      { // run方法是必須overriding的
        // 這里我們假設(shè)訪問(wèn)動(dòng)作是讀取當(dāng)前目錄下myfile.txt文件的內(nèi)容
        File file = new File("myfile.txt");
        String content = "";
        try
        {
          BufferedReader reader =
          new BufferedReader(
          new FileReader(file));
          String line = reader.readLine();
          while (line != null)
          {
            content += line + "\n";
            line = reader.readLine();
          }
        }
        catch (Exception e)
        {
          System.err.println("Error: Reading file failed!");
          System.err.println();
          e.printStackTrace();
        }
        return content;
      }
    }
    

    //JAASTest.java
    package com.jungleford.auth;
    
    import javax.security.auth.Subject;
    import javax.security.auth.login.LoginContext;
    
    public class JAASTest
    { // 測(cè)試我們JAAS登錄和授權(quán)的shell
      public static void main(String[] args)
      {
        LoginContext lc = null;
        try
        {
          // 創(chuàng)建context,使用自定義的回調(diào)對(duì)象,策略名為JAASTest
          // 簡(jiǎn)單起見(jiàn),僅使用一個(gè)MyLoginModule模塊
          lc = new LoginContext("JAASTest", new MyCallbackHandler());
        }
        catch (Exception e)
        {
          System.err.println("Error: Creating login context failed!");
          System.err.println();
          e.printStackTrace();
          System.exit(-1);
        }
        try
        { // 整體登錄
          lc.login();
        }
        catch (Exception e)
        {
          System.err.println("Error: Login failed!");
          System.err.println();
          e.printStackTrace();
          System.exit(-1);
        }
        // 獲得授權(quán)訪問(wèn)
        Object object = Subject.doAs(lc.getSubject(), new MyAction());
        System.out.println("Access successfully! Reading file:");
        System.out.println("==================================");
        System.out.println(object);
        System.out.println("==================================");
        System.exit(0);
      }
    }
    

    //login.conf
    JAASTest
    {
    	com.jungleford.auth.MyLoginModule required;
    };
    

    //jaas.policy
    grant
    {
    	permission javax.security.auth.AuthPermission "createLoginContext";
    	permission javax.security.auth.AuthPermission "doAs";
    	permission javax.security.auth.AuthPermission "modifyPrincipals";
    	permission javax.security.auth.AuthPermission "getSubject";
    };
    
    grant principal com.jungleford.auth.MyPrincipal "guest"
    {
    	permission java.io.FilePermission "myfile.txt","read";
    };
    

    //JAASTest.bat
    java -Djava.security.manager
         -Djava.security.auth.login.config=login.conf
         -Djava.security.policy=jaas.policy
         com.jungleford.auth.JAASTest
    

    //myfile.txt
    Why?
    Because they care!
    Because they want to know the truth!
    Because they want their country back!
    Because it still belongs to us as long as the people have the guts to fight for what they believe in!
    

    參考資料

  • Java Security Architecture
  • Java 授權(quán)內(nèi)幕
  • Java安全性 第二部分:認(rèn)證與授權(quán)
  • Java Security, 2nd Edition, by Scott Oaks
  • J2EE Security, by Pankaj Kumar
  • posted on 2005-04-02 16:20 jungleford 閱讀(472) 評(píng)論(0)  編輯  收藏 所屬分類(lèi): 咖啡屋 - Java 技術(shù)研究
    主站蜘蛛池模板: 亚洲va久久久久| 免费一级毛片在线观看| 亚洲五月午夜免费在线视频| 亚洲人成网站日本片| 在线观看亚洲av每日更新| 日美韩电影免费看| 18禁美女裸体免费网站| 免费看少妇高潮成人片| 看Aⅴ免费毛片手机播放| 麻豆狠色伊人亚洲综合网站 | 亚洲av无码久久忘忧草| 亚洲成AV人在线观看天堂无码| 亚洲精品动漫人成3d在线| 四虎影院免费在线播放| 无人在线观看免费高清视频| 69视频在线观看免费| 美女视频黄的免费视频网页 | 免费一级毛片一级毛片aa| 久久精品女人天堂AV免费观看| 97青青草原国产免费观看| 黄网站色视频免费在线观看的a站最新 | 亚洲av成本人无码网站| 亚洲AV成人影视在线观看| 亚洲免费观看在线视频| 亚洲白色白色在线播放| 亚洲综合自拍成人| 精品无码一区二区三区亚洲桃色 | 在线精品自拍亚洲第一区| 亚洲免费网站观看视频| 亚洲人成网站18禁止| 亚洲日韩国产二区无码| 性色av极品无码专区亚洲| 亚洲AV无码精品国产成人| 亚洲av综合av一区二区三区| 亚洲欧美成人一区二区三区| 精品国产日韩亚洲一区在线| 污视频网站免费观看| 国产男女爽爽爽免费视频| 在线免费观看h片| 日韩内射激情视频在线播放免费| 久操视频在线免费观看|