hk2000c技術(shù)專欄

          技術(shù)源于哲學(xué),哲學(xué)來源于生活 關(guān)心生活,關(guān)注健康,關(guān)心他人

            BlogJava :: 首頁 :: 新隨筆 :: 聯(lián)系 :: 聚合  :: 管理 ::
            111 隨筆 :: 1 文章 :: 28 評(píng)論 :: 0 Trackbacks
          ?

          ?

          ? Java Authentication Authorization ServiceJAAS,Java驗(yàn)證和授權(quán)API)提供了靈活和可伸縮的機(jī)制來保證客戶端或服務(wù)器端的Java程序。Java早期的安全框架強(qiáng)調(diào)的是通過驗(yàn)證代碼的來源和作者,保護(hù)用戶避免受到下載下來的代碼的攻擊。JAAS強(qiáng)調(diào)的是通過驗(yàn)證誰在運(yùn)行代碼以及他/她的權(quán)限來保護(hù)系統(tǒng)面受用戶的攻擊。它讓你能夠?qū)⒁恍?biāo)準(zhǔn)的安全機(jī)制,例如Solaris NIS(網(wǎng)絡(luò)信息服務(wù))、Windows NT、LDAP(輕量目錄存取協(xié)議),Kerberos等通過一種通用的,可配置的方式集成到系統(tǒng)中。本文首先向你介紹JAAS驗(yàn)證中的一些核心部分,然后通過例子向你展示如何開發(fā)登錄模塊。

          ? 你是否曾經(jīng)需要為一個(gè)應(yīng)用程序?qū)崿F(xiàn)登錄模塊呢?如果你是一個(gè)比較有經(jīng)驗(yàn)的程序員,相信你這樣的工作做過很多次,而且每次都不完全一樣。你有可能把你的登錄模塊建立在Oracle數(shù)據(jù)庫的基礎(chǔ)上,也有可能使用的是NT的用戶驗(yàn)證,或者使用的是LDAP目錄。如果有一種方法可以在不改變應(yīng)用程序級(jí)的代碼的基礎(chǔ)上支持上面提到的所有這一些安全機(jī)制,對(duì)于程序員來說一定是一件幸運(yùn)的事。

          ? 現(xiàn)在你可以使用JAAS實(shí)現(xiàn)上面的目標(biāo)。JAAS是一個(gè)比較新的的Java API。在J2SE?1.3中,它是一個(gè)擴(kuò)展包;在J2SE 1.4中變成了一個(gè)核心包。在本文中,我們將介紹JAAS的一些核心概念,然后通過例子說明如何將JAAS應(yīng)用到實(shí)際的程序中。本文的例子是根據(jù)我們一個(gè)基于WebJava應(yīng)用程序進(jìn)行改編的,在這個(gè)例子中,我們使用了關(guān)系數(shù)據(jù)庫保存用戶的登錄信息。由于使用了JAAS,我們實(shí)現(xiàn)了一個(gè)健壯而靈活的登錄和身份驗(yàn)證模塊。

          ? 在JAAS出現(xiàn)以前,Java的安全模型是為了滿足跨平臺(tái)的網(wǎng)絡(luò)應(yīng)用程序的需要而設(shè)計(jì)的。在Java早期版本中,Java通常是作為遠(yuǎn)程代碼被使用,例如Applet,。因此最初的安全模型把注意力放在通過驗(yàn)證代碼的來源來保護(hù)用戶上。早期的Java安全機(jī)制中包含的概念,如SercurityManager,沙箱概念,代碼簽名,策略文件,多是為了保護(hù)用戶。

          ?

            JAAS的出現(xiàn)反映了Java的演變。傳統(tǒng)的服務(wù)器/客戶端程序需要實(shí)現(xiàn)登錄和存取控制,JAAS通過對(duì)運(yùn)行程序的用戶的進(jìn)行驗(yàn)證,從而達(dá)到保護(hù)系統(tǒng)的目的。雖然JAAS同時(shí)具有驗(yàn)證和授權(quán)的能力,在這篇文章中,我們主要介紹驗(yàn)證功能。

          ?

            通過在應(yīng)用程序和底層的驗(yàn)證和授權(quán)機(jī)制之間加入一個(gè)抽象層,JAAS可以簡(jiǎn)化涉及到Java Security包的程序開發(fā)。抽象層獨(dú)立于平臺(tái)的特性使開發(fā)人員可以使用各種不同的安全機(jī)制,而且不用修改應(yīng)用程序級(jí)的代碼。和其他Java Security API相似,JAAS通過一個(gè)可擴(kuò)展的框架:服務(wù)提供者接口(Service Provider Interface,SPI)來保證程序獨(dú)立于安全機(jī)制。服務(wù)提供者接口是由一組抽象類和接口組成的。圖一中給出了JAAS程序的整體框架圖。應(yīng)用程序級(jí)的代碼主要處理LoginContext。在LoginContext下面是一組動(dòng)態(tài)配置的LoginModulesLoginModule使用正確的安全機(jī)制進(jìn)行驗(yàn)證。

          ?

            圖一給出了JAAS的概覽。應(yīng)用程序?qū)拥拇a只需要和LoginContext打交道。在LoginContext之下是一組動(dòng)態(tài)配置的LoginModule對(duì)象,這些對(duì)象使用相關(guān)的安全基礎(chǔ)結(jié)構(gòu)進(jìn)行驗(yàn)證操作。

          ?

          ?????????????????????????????????????????????????????????????? 圖一 JAAS概覽

          JAAS提供了一些LoginModule的參考實(shí)現(xiàn)代碼,比如JndiLoginModule。開發(fā)人員也可以自己實(shí)現(xiàn)LoginModule接口,就象在我們例子中的RdbmsLonginModule。同時(shí)我們還會(huì)告訴你如何使用一個(gè)簡(jiǎn)單的配置文件來安裝應(yīng)用程序。

          ?

            為了滿足可插接性,JAAS是可堆疊的。在單一登錄的情況下,一組安全模塊可以堆疊在一起,然后被其他的安全機(jī)制按照堆疊的順序被調(diào)用。

            JAAS的實(shí)現(xiàn)者根據(jù)現(xiàn)在一些流行的安全結(jié)構(gòu)模式和框架將JASS模型化。例如可堆疊的特性同Unix下的可堆疊驗(yàn)證模塊(PAMPluggable Authentication Module)框架就非常相似。從事務(wù)的角度看,JAAS類似于雙步提交(Two-Phase Commit,2PC)協(xié)議的行為。JAAS中安全配置的概念(包括策略文件(Police File)和許可(Permission))來自于J2SE 1.2JAAS還從其他成熟的安全框架中借鑒了許多思想。

          在使用JAAS之前,你首先需要安裝JAAS。在J2SE 1.4中已經(jīng)包括了JAAS,但是在J2SE 1.3中沒有。如果你希望使用J2SE 1.3,你可以從SUN的官方站點(diǎn)上下載JAAS。當(dāng)正確安裝了JAAS后,你會(huì)在安裝目錄的lib目錄下找到jaas.jar。你需要將該路徑加入Classpath中。(注:如果你安裝了應(yīng)用服務(wù)器,其中就已經(jīng)包括了JAAS,請(qǐng)閱讀應(yīng)用服務(wù)器的幫助文檔以獲得更詳細(xì)的信息)。在Java安全屬性文件java.security中,你可以改變一些與JAAS相關(guān)的系統(tǒng)屬性。該文件保存在<jre_home/lib/security目錄中。

          JAAS的核心類和接口可以被分為三種類型,大多數(shù)都在javax.security.auth包中。在J2SE 1.4中,還有一些接口的實(shí)現(xiàn)類在com.sun.security.auth包中,如下所示:

          u?????? 普通類 SubjectPrincipal,Credential(憑證)

           Subject類代表了一個(gè)驗(yàn)證實(shí)體,它可以是用戶、管理員、Web服務(wù),設(shè)備或者其他的過程。該類包含了三中類型的安全信息:

          1)??????? 身份(Identities):由一個(gè)或多個(gè)Principal對(duì)象表示

          2)??????? 公共憑證(Public credentials):例如名稱或公共密鑰

          3)??????? 私有憑證(Private credentials):例如口令或私有密鑰

            Principal對(duì)象代表了Subject對(duì)象的身份。它們實(shí)現(xiàn)了java.security.Principaljava.io.Serializable接口。在Principal類中,最重要的方法是getName()。該方法返回一個(gè)身份名稱。在Subject對(duì)象中包含了多個(gè)Principal對(duì)象,因此它可以擁有多個(gè)名稱。由于登錄名稱、身份證號(hào)和Email地址都可以作為用戶的身份標(biāo)識(shí),可見擁有多個(gè)身份名稱的情況在實(shí)際應(yīng)用中是非常普遍的情況。

          在上面提到的憑證并不是一個(gè)特定的類或借口,它可以是任何對(duì)象。憑證中可以包含任何特定安全系統(tǒng)需要的驗(yàn)證信息,例如標(biāo)簽(ticket),密鑰或口令。Subject對(duì)象中維護(hù)著一組特定的私有和公有的憑證,這些憑證可以通過Subject 方法getPrivateCredentials()和getPublicCredentials()獲得。這些方法通常在應(yīng)用程序?qū)又械陌踩酉到y(tǒng)被調(diào)用。

          u?????? 驗(yàn)證 LoginContextLoginModule,CallBackHandlerCallback

          驗(yàn)證:LoginContext

            在應(yīng)用程序?qū)又校憧梢允褂?/span>LoginContext對(duì)象來驗(yàn)證Subject對(duì)象。LoginContext對(duì)象同時(shí)體現(xiàn)了JAAS的動(dòng)態(tài)可插入性(Dynamic Pluggability),因?yàn)楫?dāng)你創(chuàng)建一個(gè)LoginContext的實(shí)例時(shí),你需要指定一個(gè)配置。LoginContext通常從一個(gè)文本文件中加載配置信息,這些配置信息告訴LoginContext對(duì)象在登錄時(shí)使用哪一個(gè)LoginModule對(duì)象。

            下面列出了在LoginContext中經(jīng)常使用的三個(gè)方法:

          n???????? login () 進(jìn)行登錄操作。該方法激活了配置中制定的所有LoginModule對(duì)象。如果成功,它將創(chuàng)建一個(gè)經(jīng)過了驗(yàn)證的Subject對(duì)象;否則拋出LoginException異常。

          n???????? getSubject () 返回經(jīng)過驗(yàn)證的Subject對(duì)象

          n???????? logout () 注銷Subject對(duì)象,刪除與之相關(guān)的Principal對(duì)象和憑證

            驗(yàn)證:LoginModule

            LoginModule是調(diào)用特定驗(yàn)證機(jī)制的接口。J2EE 1.4中包含了下面幾種LoginModule的實(shí)現(xiàn)類:

          n???????? JndiLoginModule 用于驗(yàn)證在JNDI中配置的目錄服務(wù)

          n???????? Krb5LoginModule 使用Kerberos協(xié)議進(jìn)行驗(yàn)證

          n???????? NTLoginModul 使用當(dāng)前用戶在NT中的用戶信息進(jìn)行驗(yàn)證

          n???????? UnixLoginModule 使用當(dāng)前用戶在Unix中的用戶信息進(jìn)行驗(yàn)證

          ?

            同上面這些模塊綁定在一起的還有對(duì)應(yīng)的Principal接口的實(shí)現(xiàn)類,例如NTDomainPrincipalUnixPrincipal。這些類在com.sun.security.auth包中。

          ?

            LoginModule接口中包含了五個(gè)方法:

          1)??????? initialize () 當(dāng)創(chuàng)建一LoginModule實(shí)例時(shí)會(huì)被構(gòu)造函數(shù)調(diào)用

          2)??????? login () 進(jìn)行驗(yàn)證,通常會(huì)按照登錄條件生成若干個(gè)Principal對(duì)象

          3)??????? commit () 進(jìn)行Principal對(duì)象檢驗(yàn),按照預(yù)定義Principal條件檢驗(yàn)Login生成的Principal對(duì)象,所有需要的條件均符合后,把若干個(gè)生成的Principal對(duì)象付給Subject對(duì)象,JAAS架構(gòu)負(fù)責(zé)回傳給LoginContext.

          4)??????? abort () 當(dāng)任何一個(gè)LoginModule對(duì)象驗(yàn)證失敗時(shí)都會(huì)調(diào)用該方法。任何已經(jīng)和Subject對(duì)象綁定的Principal對(duì)象都會(huì)被解除綁定。

          5)??????? logout () 刪除與Subject對(duì)象關(guān)聯(lián)的Principal對(duì)象和憑證,消除Subject,Principal等認(rèn)證對(duì)象。

           驗(yàn)證:CallbackHandlerCallback

            CallbackHandlerCallback對(duì)象可以使LoginModule對(duì)象從系統(tǒng)和用戶那里收集必要的驗(yàn)證信息,同時(shí)獨(dú)立于實(shí)際的收集信息時(shí)發(fā)生的交互過程。

            

            JAASjavax.sevurity.auth.callback包中包含了七個(gè)Callback的實(shí)現(xiàn)類和兩個(gè)CallbackHandler的實(shí)現(xiàn)類:ChoiceCallback、ConfirmationCallback、LogcaleCallback、NameCallbackPasswordCallback、TextInputCallback、TextOutputCallbackDialogCallbackHandlerTextCallBackHandler。Callback接口只會(huì)在客戶端會(huì)被使用到。我將在后面介紹如何編寫你自己的CallbackHandler類。

          ?

          u?????? 授權(quán) Policy,AuthPermission,PrivateCredentialPermission

          ?

            

          ?

           

            

          ?

          ?

          在應(yīng)用程序中使用JAAS驗(yàn)證通常會(huì)涉及到以下幾個(gè)步驟,如下圖所示:

          1)??????? 創(chuàng)建一個(gè)LoginContext的實(shí)例。

          2)??????? 為了能夠獲得和處理驗(yàn)證信息,客戶端將若干個(gè)CallBackHandler對(duì)象作為參數(shù)傳送給LoginContext。

          3)??????? JAAS通過配置文件查到相關(guān)的LoginModule處理類。

          4)??????? 通過調(diào)用LoginContextlogin()方法來進(jìn)行驗(yàn)證,此處會(huì)回調(diào)所有傳入CallBackHandlerhandle()方法,該方法通常會(huì)從外部收集一些被驗(yàn)證信息。

          5)??????? 通過使用login()方法返回的Subject對(duì)象實(shí)現(xiàn)一些特殊的功能(假設(shè)登錄成功)。

          下面是一個(gè)簡(jiǎn)單的例子用來說明以上的情況:

          LoginContext lc = new LoginContext("MyExample");

          try {

          lc.login();

          } catch (LoginException) {

          // Authentication failed.

          }

          // Authentication successful, we can now continue.

          // We can use the returned Subject if we like.

          Subject sub = lc.getSubject();

          Subject.doAs(sub, new MyPrivilegedAction());

            在運(yùn)行這段代碼時(shí),后臺(tái)進(jìn)行了以下的工作。

          2)??????? 當(dāng)初始化時(shí),LoginContext對(duì)象首先在JAAS配置文件jaas.config中找到MyExample項(xiàng),然后更具該項(xiàng)的內(nèi)容決定該加載哪個(gè)LoginModule對(duì)象(參見圖二)。

          3)??????? 在登錄時(shí),LoginContext對(duì)象調(diào)用每個(gè)LoginModule對(duì)象的login()方法。

          4)??????? 每個(gè)login()方法進(jìn)行驗(yàn)證操作或獲得一個(gè)CallbackHandle對(duì)象。

          5)??????? CallbackHandle對(duì)象通過使用一個(gè)或多個(gè)CallBack方法同用戶進(jìn)行交互,獲得用戶輸入。

          6)??????? 向一個(gè)新的Subject對(duì)象中填入驗(yàn)證信息。

          要使上述例子順利運(yùn)行,還需要有相關(guān)的JAAS配置文件進(jìn)行配置。

           圖二描述了配置文件中各元素之間的關(guān)系

          圖二 JAAS的配置文件

            

          上面我已經(jīng)提到,JAAS的可擴(kuò)展性來源于它能夠進(jìn)行動(dòng)態(tài)配置,而配置信息通常是保存在文本。這些文本文件有很多個(gè)配置塊構(gòu)成,我們通常把這些配置塊稱作申請(qǐng)(Application)。每個(gè)申請(qǐng)對(duì)應(yīng)了一個(gè)或多個(gè)特定的LoginModule對(duì)象。

          ?

            當(dāng)你的代碼構(gòu)造一個(gè)LoginContext對(duì)象時(shí),你需要把配置文件中申請(qǐng)的名稱傳遞給它。LoginContext將會(huì)根據(jù)申請(qǐng)中的信息決定激活哪些LoginModule對(duì)象,按照什么順序激活以及使用什么規(guī)則激活。

            配置文件的結(jié)構(gòu)如下所示:

          Application {

          ModuleClass Flag ModuleOptions;

          ModuleClass Flag ModuleOptions;

          ...

          };

          Application {

          ModuleClass Flag ModuleOptions;

          ...

          };

          ...

          ?

          下面是一個(gè)名稱為Sample的申請(qǐng)

          Sample {

          com.sun.security.auth.module.NTLoginModule Rquired debug=true;

          }

            上面這個(gè)簡(jiǎn)單的申請(qǐng)指定了LoginContext對(duì)象應(yīng)該使用NTLoginModule進(jìn)行驗(yàn)證。類的名稱在ModuleClass中被指定。Flag控制當(dāng)申請(qǐng)中包含了多個(gè)LoginModule時(shí)進(jìn)行登錄時(shí)的行為:Required、SufficientRequisiteOptional。最常用的是Required,使用它意味著對(duì)應(yīng)的LoginModule對(duì)象必須被調(diào)用,并且必須需要通過所有的驗(yàn)證。由于Flag本身的復(fù)雜性,本文在這里不作深究。

            ModuleOption允許有多個(gè)參數(shù)。例如你可以設(shè)定調(diào)試參數(shù)為Truedebug=true),這樣診斷輸出將被送到System.out中。

            配置文件可以被任意命名,并且可以被放在任何位置。JAAS框架通過使用java.securty.auth.long.config屬性來確定配置文件的位置。例如當(dāng)你的應(yīng)用程序是JaasTest,配置文件是當(dāng)前目錄下的jaas.config,你需要在命令行中輸入:

          java -Djava.security.auth.login.config=jass.config JavaTest

          通過命令行方式進(jìn)行登錄驗(yàn)證

          ?

            為了說明JAAS到底能干什么,我在這里編寫了兩個(gè)例子。一個(gè)是簡(jiǎn)單的由命令行輸入調(diào)用的程序,另一個(gè)是服務(wù)器端的JSP程序。這兩個(gè)程序都通過用戶名/密碼的方式進(jìn)行登錄,然后使用關(guān)系數(shù)據(jù)庫對(duì)其進(jìn)行驗(yàn)證。

          1)??????? 控制臺(tái)方式實(shí)現(xiàn)JAAS認(rèn)證

          具體實(shí)現(xiàn)流程如下,其實(shí)只是稍微擴(kuò)展了一下上面的簡(jiǎn)單的例子:

            1. 實(shí)現(xiàn)RdbmsLoginModule類,該類可以對(duì)輸入的信息進(jìn)行驗(yàn)證。

            2. 編輯一個(gè)配置文件,告訴LoginContext如何使用RdbmsLoginModule。

            3. 實(shí)現(xiàn)ConsoleCallbackHandler類,通過該類可以獲取用戶的輸入。

            4. 編寫應(yīng)用程序代碼。

          ?

            在RdbmsLoginModule類中,我們必須實(shí)現(xiàn)LoginModule接口中的五個(gè)方法。首先是initialize()方法:

          ?

          public void initialize(Subject subject, CallbackHandler callbackHandler, Map sharedState, Map options)

          {

          this.subject = subject;

          this.callbackHandler = callbackHandler;

          this.sharedState = sharedState;

          this.options = options;

          ?

          url = (String)options.get("url");

          driverClass = (String)options.get("driver");

          debug = "true".equalsIgnoreCase((String)options.get("debug"));

          }

          ?

            LoginContext在調(diào)用login()方法時(shí)會(huì)調(diào)用initialize()方法。RdbmsLoginModule的第一個(gè)任務(wù)就是在類中保存輸入?yún)?shù)的引用。在驗(yàn)證成功后將向Subject對(duì)象中送入Principal對(duì)象和憑證。

          ?

            CallbackHandler對(duì)象將會(huì)在login()方法中被使用到。sharedState可以使數(shù)據(jù)在不同的LoginModule對(duì)象之間共享,但是在這個(gè)例子中我們不會(huì)使用它。最后是名為optionsMap對(duì)象。optionsLgoinModule對(duì)象傳遞在配置文件ModuleOption域中定義的參數(shù)的值。配置文件simple.conf如下所示:

          SimpleLogin {

          ?? com.lemon.jaas.RdbmsLoginModule required

          ?? driver="org.gjt.mm.mysql.Driver"

          ?? url="jdbc:mysql://localhost/jaasdb?user=root"

          ?? debug="true";???

          ?

          };

            在配置文件中,RdbmsLoginModule包含了五個(gè)參數(shù),其中driver、urluserpassword是必需的,而debug是可選闡述。driver、urluserpassword參數(shù)告訴我們?nèi)绾潍@得JDBC連接。當(dāng)然你還可以在ModuleOption域中加入數(shù)據(jù)庫中的表或列的信息。使用這些參數(shù)的目的是為了能夠?qū)?shù)據(jù)庫進(jìn)行操作。在LoginModule類的initialize()方法中我們保存了每個(gè)參數(shù)的值。

          ??????

            我們前面提到一個(gè)LoginContext對(duì)應(yīng)的配置文件告訴它應(yīng)該使用文件中的哪一個(gè)申請(qǐng)。這個(gè)信息是通過LgoinContext的構(gòu)造函數(shù)傳遞的。下面是初始化客戶端的代碼,在代碼中創(chuàng)建了一個(gè)LoginContext對(duì)象并調(diào)用了login()方法。

          ?

          ?????? LoginContext ctx = new LoginContext("SimpleLogin", new ConsoleCallbackHandler());

          ??????? ctx.login();

          ??????? Subject subj = ctx.getSubject();

          ?

          ??????? System.out.println("Login assigned these principals: ");

          ??????? Iterator it = subj.getPrincipals().iterator();

          ???? ???while (it.hasNext()) {

          ??????????? Principal pl = (Principal) it.next();

          ??????????? System.out.println("\t" + pl.getName());

          ??????? }

          ??????? Subject.doAsPrivileged(subj, new MyAction(), null);

          ?

          ?

          ??????? ctx.logout();

          ?

            當(dāng)LgoinContext.login()方法被調(diào)用時(shí),它調(diào)用所有加載了的LoginModule對(duì)象的login()方法。在我們的這個(gè)例子中是RdbmsLoginModule中的login()方法。

          ?

            RdbmsLoginModule中的login()方法進(jìn)行了下面的操作:

          ?

            1. 創(chuàng)建兩個(gè)Callback對(duì)象。這些對(duì)象從用戶輸入中獲取用戶名/密碼。程序中使用了JAAS中的兩個(gè)Callback:NameCallbackPasswordCallback(這兩個(gè)類包含在javax.security.auth.callback包中)。

            2. 通過將callbacks作為參數(shù)傳遞給CallbackHandlerhandle()方法來激活Callback。

            3. 通過Callback對(duì)象獲得用戶名/密碼。

            4. rdbmsValidate()方法中通過JDBC在數(shù)據(jù)庫中驗(yàn)證獲取的用戶名/密碼,這里省略。

            下面是RdbmsLoginModule中的login()方法的代碼

          ?

          public boolean login() throws LoginException {

          if (callbackHandler == null)

          throw new LoginException("no handler");

          NameCallback nameCb = new NameCallback("user: ");

          PasswordCallback passCb = new PasswordCallback("password: ", true);

          callbacks = new Callback[] { nameCb, passCb };

          callbackHandler.handle(callbacks);

          String username = nameCb.getName();

          String password = new String(passCb.getPassword());

          success = rdbmsValidate(username, password);

          return(true);

          }

          ?

            在ConsoleCallbackHandler類的handle()方法中你可以看到Callback對(duì)象是如何同用戶進(jìn)行交互的:

          ?

          ?

          ?

          ?

          ? public void handle(Callback[] callbacks) throws IOException, UnsupportedCallbackException {

          ??????? for (int i = 0; i < callbacks.length; i++) {

          ??????????? if (callbacks[i] instanceof NameCallback) {

          ??????????????? NameCallback nc = (NameCallback) callbacks[i];

          ??????????????? System.err.print(nc.getPrompt());

          ?? ?????????????System.err.flush();

          ??????????????? String name = (new BufferedReader(new InputStreamReader(System.in))).readLine();

          ??????????????? nc.setName(name);

          ??????????? } else if (callbacks[i] instanceof PasswordCallback) {

          ??????????????? PasswordCallback pc = (PasswordCallback) callbacks[i];

          ??????????????? System.err.print(pc.getPrompt());

          ??????????????? System.err.flush();

          ??????????????? String name = (new BufferedReader(new InputStreamReader(System.in))).readLine();

          ??????????????? pc.setPassword(name.toCharArray());

          ??????????? } else {

          ??????????????? throw(new UnsupportedCallbackException(callbacks[i], "Callback handler not support"));

          ??????????? }

          ??????? }

          ??? }???????????

            

          現(xiàn)在啟動(dòng)這個(gè)例子:

          ?

          ?

          2)??????? web環(huán)境下進(jìn)行JAAS驗(yàn)證

          現(xiàn)在應(yīng)用的最廣泛的系統(tǒng)為B/S系統(tǒng),這些系統(tǒng)一般使用關(guān)系型數(shù)據(jù)庫來存儲(chǔ)應(yīng)用數(shù)據(jù)。所以討論在Web環(huán)境下的JAAS使用方法,尤其有現(xiàn)實(shí)意義。

          我們使用通用的Web技術(shù)來實(shí)現(xiàn)這個(gè)輸入用戶信息的過程,然后通過RdbmsLoginModule類驗(yàn)證它。我們將會(huì)使用到JSPServlet以及HTML表單等技術(shù)來說明這個(gè)用例。

            首先我們寫一個(gè)Servlet類代替之前的應(yīng)用程序登錄入口類。此類可以命名為LoginServlet 下面是該類的代碼,下面代碼包括了JAAS認(rèn)證和授權(quán)功能:

          ?

          public class LoginServlet extends HttpServlet {

          ??? protected void service(HttpServletRequest httpServletRequest, HttpServletResponse httpServletResponse) throws ServletException, IOException {

          ??????? String userName = httpServletRequest.getParameter("username");

          ??????? String password = httpServletRequest.getParameter("password");

          ??????? System.out.println("userName = " + userName);

          ??????? System.out.println("password = " + password);

          ??????? LoginContext ctx = null;

          ??????? try {

          ??????????? ctx = new LoginContext("SimpleLogin", new PassiveCallbackHandler(userName,password));

          ??????????? ctx.login();

          ??????? } catch (LoginException e) {

          ??????????? e.printStackTrace();? //To change body of catch statement use File | Settings | File Templates.

          ??????? }

          ??????? Subject subj = ctx.getSubject();

          ??????? System.out.println("Login assigned these principals: ");

          ??????? Iterator it = subj.getPrincipals().iterator();

          ??????? while (it.hasNext()) {

          ??????????? Principal pl = (Principal) it.next();

          ??????????? System.out.println("\t" + pl.getName());

          ??????? }

          ??????? Subject.doAsPrivileged(subj, new MyAction(), null);

          ??????? try {

          ??????????? ctx.logout();

          ??????? } catch (LoginException e) {

          ??????????? e.printStackTrace();? //To change body of catch statement use File | Settings | File Templates.

          ??????? }

          ??? }

          }  

          從此處就可以看出我們把Servlet完全代替了應(yīng)用程序的登錄入口功能,下面是很簡(jiǎn)單的JSP表單輸入代碼:

          Index.jsp

          <%@ page contentType="text/html;charset=UTF-8" language="java" %>

          <html>

          ? <head><title>Simple jsp page</title></head>

          ? <body>

          ??? <form action="/login.do" method="post">

          ??????? user:<input type="text" name="username"/>

          ??????? password:<input type="password" name="password"/>

          ??????? <input type="submit"/>

          ??? </form>

          ? </body>

          </html>

          下面是web.xml配置文件代碼:

          <?xml version="1.0" encoding="UTF-8"?>

          ?

          <!DOCTYPE web-app PUBLIC "-//Sun Microsystems, Inc.//DTD Web Application 2.3//EN" "http://java.sun.com/dtd/web-app_2_3.dtd">

          <web-app>

          ?

          ??? <servlet>

          ??????? <servlet-name>loginServlet</servlet-name>

          ??????? <servlet-class>com.lemon.jaas.web.LoginServlet</servlet-class>

          ??? </servlet>

          ?

          ??? <servlet-mapping>

          ??????? <servlet-name>loginServlet</servlet-name>

          ??????? <url-pattern>/login.do</url-pattern>

          ??? </servlet-mapping>

          ?

          ??? <welcome-file-list>

          ??????? <welcome-file>index.jsp</welcome-file>

          ??? </welcome-file-list>

          ?

          因?yàn)?/span>Web端所有響應(yīng)客戶端的工作都是由servlet以及jsp完成得,所以PassiveCallbackHandler代替了ConsoleCallbackHandler 直接傳入構(gòu)造函數(shù)的參數(shù)包含了用戶名和密碼,取消了和客戶端交互的動(dòng)作。因此它可以在Callback對(duì)象中設(shè)定正確的值。下面是PassiveCallbackHandler類的代碼:

          ?????? ?public class PassiveCallbackHandler implements CallbackHandler {

          ??? private String userName; // external login info input handler? , like password input and some device input

          ??? private String password;

          ?

          ??? public PassiveCallbackHandler(String userName, String password) {

          ??????? this.userName = userName;

          ??????? this.password = password;

          ??? }

          ?

          ??? public void handle(Callback[] callbacks) throws IOException, UnsupportedCallbackException { //do some

          ??????? for (int i = 0; i < callbacks.length; i++) {

          ??????????? if (callbacks[i] instanceof NameCallback) {

          ??????????????? NameCallback nc = (NameCallback) callbacks[i];

          ?

          ??????????????? nc.setName(this.userName);

          ??????????? } else if (callbacks[i] instanceof PasswordCallback) {

          ??????????????? PasswordCallback pc = (PasswordCallback) callbacks[i];

          ?

          ??????????????? pc.setPassword(this.password.toCharArray());

          ?????????? ?} else {

          ??????????????? throw(new UnsupportedCallbackException(callbacks[i], "Callback handler not support"));

          ??????????? }

          ??????? }

          ??? }

          }

          ?

          RdbmsLoginModule無需改動(dòng)。

          以上就完成了整個(gè)Web程序的調(diào)用JAAS的機(jī)制。

          ?

          ?

            和在應(yīng)用程序運(yùn)行JAAS不同的是,配置JAAS方法會(huì)有很不一樣。我們使用的是Tomcat 5.0.x 應(yīng)用服務(wù)器,它的JAAS配置方法有數(shù)種,分別是

          u?????? JAASRealm

          u?????? JDBCRealm

          u?????? DataSourceRealm

          u?????? JNDIRealm

          u?????? MemoryRealm

          JAASRealm

          ?

          1.???????? 把自定義 LoginModuleUser、Role等相關(guān)類放入Tomcat classpath

          2.???????? 把自定義login.config JAAS配置文件配置進(jìn)JVM環(huán)境,例如:?????? JAVA_OPTS=-DJAVA_OPTS=-Djava.security.auth.login.config==$CATALINA_HOME/conf/jaas.config

          3.???????? 設(shè)置 web.xml里的security-constraints 標(biāo)簽設(shè)定需要保護(hù)的資源

          4.???????? $CATALINA_HOME/conf/server.xmlengine標(biāo)簽里設(shè)置JAASRealm標(biāo)簽

          以下是JAASRealm標(biāo)簽的詳細(xì)說明

          屬性

          描述

          className

          只需要指定org.apache.catalina.realm.JAASRealm

          debug

          設(shè)置debug級(jí)別,默認(rèn)為不設(shè)置0

          appName

          JAAS配置文件應(yīng)用名

          userClassNames

          自定義user Principals

          roleClassNames

          自定義role Principals

          useContextClassLoader

          默認(rèn)為true, 為了向后兼容類裝載方式,使用Tomcat4以上版本ContextLoader裝載方式

          ?

          以下是完整的tomcat服務(wù)器配置例子:

          %TOMCAT_HOME%/config/server.xml 中添加以下段落

          <Realm className="org.apache.catalina.realm.JAASRealm"????????????

          ??????????????? appName="MyFooRealm"??????

          ??? userClassNames="org.foobar.realm.FooUser"??????

          ???? roleClassNames="org.foobar.realm.FooRole"

          ????????????????????? debug="99"/>

          注意Realm標(biāo)簽所在位置將會(huì)使JAAS作用域不同

          父標(biāo)簽

          作用

          <Engine>

          Tomcat下面所有的web application 以及所有的host

          <Host>

          作用于該虛擬主機(jī)上的所有web application

          <Context>

          僅作用于當(dāng)前web application

          ?

          ?

          ?

          ?

          特別感謝:

          本文是在眾多前人的文章基礎(chǔ)上改編并且添加內(nèi)容而來,本文的版權(quán)屬于 hk2000c 以及以下列出的所有參考文檔作者以及相關(guān)譯者,僅此感謝 JAVA 先驅(qū)者們的不懈努力與研究工作。如果您對(duì)本文有任何意見請(qǐng)?jiān)谖业募夹g(shù) Blog http://blog.csdn.net/hk2000c 中和我聯(lián)絡(luò),謝謝。

          ?

          參考資料:

          JAAS: 靈活的 Java 安全機(jī)制》 John Musser/Paul Feuer 馮睿編譯

          《擴(kuò)展 JAAS 實(shí)現(xiàn)類實(shí)例級(jí)授權(quán)》 Carlos A. Fonseca, 軟件工程師 , IBM http://www-128.ibm.com/developerworks/cn/java/j-jaas/

          《用 JAAS JSSE 實(shí)現(xiàn) Java 安全性》 Kyle Gabhart, 顧問 , Gabhart Communications

          ?

          ?

          本文沒有對(duì)如何在Web Container 中具體實(shí)施JAAS作進(jìn)一步闡述,這部分將會(huì)在中篇整理刊出。

          ?

          posted on 2006-03-23 08:07 hk2000c 閱讀(6214) 評(píng)論(0)  編輯  收藏 所屬分類: Java 技術(shù)
          主站蜘蛛池模板: 开封市| 神农架林区| 巍山| 邹平县| 巴东县| 福安市| 凤台县| 牟定县| 大连市| 哈密市| 宁津县| 五寨县| 兴国县| 凤庆县| 栾城县| 胶南市| 娄烦县| 华亭县| 潼南县| 太湖县| 图片| 东乡族自治县| 巴彦县| 科技| 泾阳县| 怀安县| 巍山| 泗洪县| 五大连池市| 靖安县| 临湘市| 阿克苏市| 康马县| 苍南县| 垦利县| 田林县| 双柏县| 怀仁县| 通辽市| 罗源县| 宁波市|