什么是JAAS 以及靈活的Java安全機制

字號:

Java Authentication Authorization Service(JAAS,Java驗證和授權(quán)API)提供了靈活和可伸縮的機制來保證客戶端或服務器端的Java程序。Java早期的安全框架強調(diào)的是通過驗證代碼的來源和作者,保護用戶避免受到下載下來的代碼的攻擊。JAAS強調(diào)的是通過驗證誰在運行代碼以及他/她的權(quán)限來保護系統(tǒng)面受用戶的攻擊。它讓你能夠?qū)⒁恍藴实陌踩珯C制,例如Solaris NIS(網(wǎng)絡信息服務)、Windows NT、LDAP(輕量目錄存取協(xié)議),Kerberos等通過一種通用的,可配置的方式集成到系統(tǒng)中。
    你是否曾經(jīng)需要為一個應用程序?qū)崿F(xiàn)登錄模塊呢?如果你是一個比較有經(jīng)驗的程序員,相信你這樣的工作做過很多次,而且每次都不完全一樣。你有可能把你的登錄模塊建立在Oracle數(shù)據(jù)庫的基礎(chǔ)上,也有可能使用的是NT的用戶驗證,或者使用的是LDAP目錄。如果有一種方法可以在不改變應用程序級的代碼的基礎(chǔ)上支持上面提到的所有這一些安全機制,對于程序員來說一定是一件幸運的事。
    現(xiàn)在你可以使用JAAS實現(xiàn)上面的目標。JAAS是一個比較新的的Java API。在J2SE 1.3中,它是一個擴展包;在J2SE 1.4中變成了一個核心包。在本文中,我們將介紹JAAS的一些核心概念,然后通過例子說明如何將JAAS應用到實際的程序中。本文的例子是根據(jù)我們一個基于Web的Java應用程序進行改編的,在這個例子中,我們使用了關(guān)系數(shù)據(jù)庫保存用戶的登錄信息。由于使用了JAAS,我們實現(xiàn)了一個健壯而靈活的登錄和身份驗證模塊。
    客戶端和服務器端的JAAS
    開發(fā)人員可以將JAAS應用到客戶端和服務器端。在客戶端使用JAAS很簡單。在服務器端使用JAAS時情況要復雜一些。目前在應用服務器市場中的JAAS產(chǎn)品還不是很一致,使用JAAS的J2EE應用服務器有一些細微的差別。例如JBossSx使用自己的結(jié)構(gòu),將JAAS集成到了一個更大的安全框架中;而雖然WebLogic 6.x也使用了JAAS,安全框架卻完全不一樣。
    現(xiàn)在你能夠理解為什么我們需要從客戶端和服務器端的角度來看JAAS了。我們將在后面列出兩種情況下的例子。為了使服務器端的例子程序更加簡單,我們使用了Resin應用服務器。
    核心JAAS類
    在使用JAAS之前,你首先需要安裝JAAS。在J2SE 1.4中已經(jīng)包括了JAAS,但是在J2SE 1.3中沒有。如果你希望使用J2SE 1.3,你可以從SUN的官方站點上下載JAAS。當正確安裝了JAAS后,你會在安裝目錄的lib目錄下找到jaas.jar。你需要將該路徑加入Classpath中。(注:如果你安裝了應用服務器,其中就已經(jīng)包括了JAAS,請閱讀應用服務器的幫助文檔以獲得更詳細的信息)。在Java安全屬性文件java.security中,你可以改變一些與JAAS相關(guān)的系統(tǒng)屬性。該文件保存在<jre_home>/lib/security目錄中。
    在應用程序中使用JAAS驗證通常會涉及到以下幾個步驟:
    1. 創(chuàng)建一個LoginContext的實例。
    2. 為了能夠獲得和處理驗證信息,將一個CallBackHandler對象作為參數(shù)傳送給LoginContext。
    3. 通過調(diào)用LoginContext的login()方法來進行驗證。
    4. 通過使用login()方法返回的Subject對象實現(xià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());
    在運行這段代碼時,后臺進行了以下的工作。
    1. 當初始化時,LoginContext對象首先在JAAS配置文件中找到MyExample項,然后更具該項的內(nèi)容決定該加載哪個LoginModule對象(參見圖二)。
    2. 在登錄時,LoginContext對象調(diào)用每個LoginModule對象的login()方法。
    3. 每個login()方法進行驗證操作或獲得一個CallbackHandle對象。
    4. CallbackHandle對象通過使用一個或多個CallBack方法同用戶進行交互,獲得用戶輸入。
    5. 向一個新的Subject對象中填入驗證信息。
    我們將對代碼作進一步的解釋。但是在這之前,讓我們先看代碼中涉及到的核心JAAS類和接口。這些類可以被分為三種類型:
    普通類型 Subject,Principal,憑證
    驗證 LoginContext,LoginModule,CallBackHandler,Callback
    授權(quán) Policy,AuthPermission,PrivateCredentialPermission
    上面列舉的類和接口大多數(shù)都在javax.security.auth包中。在J2SE 1.4中,還有一些接口的實現(xiàn)類在com.sun.security.auth包中。
    普通類型:Subject,Principal,憑證
    Subject類代表了一個驗證實體,它可以是用戶、管理員、Web服務,設備或者其他的過程。該類包含了三中類型的安全信息:
    身份(Identities):由一個或多個Principal對象表示
    公共憑證(Public credentials):例如名稱或公共秘鑰
    私有憑證(Private credentials):例如口令或私有密鑰
    Principal對象代表了Subject對象的身份。它們實現(xiàn)了java.security.Principal和java.io.Serializable接口。在Subject類中,最重要的方法是getName()。該方法返回一個身份名稱。在Subject對象中包含了多個Principal對象,因此它可以擁有多個名稱。由于登錄名稱、身份證號和Email地址都可以作為用戶的身份標識,可見擁有多個身份名稱的情況在實際應用中是非常普遍的情況。
    在上面提到的憑證并不是一個特定的類或借口,它可以是任何對象。憑證中可以包含任何特定安全系統(tǒng)需要的驗證信息,例如標簽(ticket),密鑰或口令。Subject對象中維護著一組特定的私有和公有的憑證,這些憑證可以通過getPrivateCredentials()和getPublicCredentials()方法獲得。這些方法通常在應用程序?qū)又械陌踩酉到y(tǒng)被調(diào)用。
    驗證:LoginContext
    在應用程序?qū)又校憧梢允褂肔oginContext對象來驗證Subject對象。LoginContext對象同時體現(xiàn)了JAAS的動態(tài)可插入性(Dynamic Pluggability),因為當你創(chuàng)建一個LoginContext的實例時,你需要指定一個配置。LoginContext通常從一個文本文件中加載配置信息,這些配置信息告訴LoginContext對象在登錄時使用哪一個LoginModule對象。
    下面列出了在LoginContext中經(jīng)常使用的三個方法:
    login () 進行登錄操作。該方法激活了配置中制定的所有LoginModule對象。如果成功,它將創(chuàng)建一個經(jīng)過了驗證的Subject對象;否則拋出LoginException異常。
    getSubject () 返回經(jīng)過驗證的Subject對象
    logout () 注銷Subject對象,刪除與之相關(guān)的Principal對象和憑證
    驗證:LoginModule
    LoginModule是調(diào)用特定驗證機制的接口。J2EE 1.4中包含了下面幾種LoginModule的實現(xiàn)類:
    JndiLoginModule 用于驗證在JNDI中配置的目錄服務
    Krb5LoginModule 使用Kerberos協(xié)議進行驗證
    NTLoginModul 使用當前用戶在NT中的用戶信息進行驗證
    UnixLoginModule 使用當前用戶在Unix中的用戶信息進行驗證
    同上面這些模塊綁定在一起的還有對應的Principal接口的實現(xiàn)類,例如NTDomainPrincipal和UnixPrincipal。這些類在com.sun.security.auth包中。
    LoginModule接口中包含了五個方法:
    initialize () 當創(chuàng)建一LoginModule實例時會被構(gòu)造函數(shù)調(diào)用
    login () 進行驗證
    commit () 當LgoninContext對象接受所有LoginModule對象傳回的結(jié)果后將調(diào)用該方法。該方法將Principal對象和憑證賦給Subject對象。
    abort () 當任何一個LoginModule對象驗證失敗時都會調(diào)用該方法。此時沒有任何Principal對象或憑證關(guān)聯(lián)到Subject對象上。
    logout () 刪除與Subject對象關(guān)聯(lián)的Principal對象和憑證。
    在應用程序的代碼中,程序員通常不會直接調(diào)用上面列出的方法,而是通過LigonContext間接調(diào)用這些方法。
    驗證:CallbackHandler和Callback
    CallbackHandler和Callback對象可以使LoginModule對象從系統(tǒng)和用戶那里收集必要的驗證信息,同時獨立于實際的收集信息時發(fā)生的交互過程。
    JAAS在javax.sevurity.auth.callback包中包含了七個Callback的實現(xiàn)類和兩個CallbackHandler的實現(xiàn)類:ChoiceCallback、ConfirmationCallback、LogcaleCallback、NameCallback、PasswordCallback、TextInputCallback、TextOutputCallback、DialogCallbackHandler和TextCallBackHandler。Callback接口只會在客戶端會被使用到。我將在后面介紹如何編寫你自己的CallbackHandler類。