JAAS:靈活的Java安全機制

來源:互聯網
上載者:User

JAAS:靈活的Java安全機制



(來源:轉載自yesky網站,http://www.yesky.com/)

  摘要:

  Java Authentication Authorization Service(JAAS,Java驗證和授權API)提供了靈活和可伸縮的機制來保證用戶端或伺服器端的Java程式。Java早期的安全架構強調的是通過驗證代碼的來源和作者,保護使用者避免受到下載下來的代碼的攻擊。JAAS強調的是通過驗證誰在運行代碼以及他/她的許可權來保護系統面受使用者的攻擊。它讓你能夠將一些標準的安全機制,例如Solaris NIS(網路資訊服務)、Windows NT、LDAP(輕量目錄存取協議),Kerberos等通過一種通用的,可配置的方式整合到系統中。本文首先向你介紹JAAS驗證中的一些核心部分,然後通過例子向你展示如何開發登入模組。

  你是否曾經需要為一個應用程式實現登入模組呢?如果你是一個比較有經驗的程式員,相信你這樣的工作做過很多次,而且每次都不完全一樣。你有可能把你的登入模組建立在Oracle資料庫的基礎上,也有可能使用的是NT的使用者驗證,或者使用的是LDAP目錄。如果有一種方法可以在不改變應用程式級的代碼的基礎上支援上面提到的所有這一些安全機制,對於程式員來說一定是一件幸運的事。

  現在你可以使用JAAS實現上面的目標。JAAS是一個比較新的的Java API。在J2SE 1.3中,它是一個擴充包;在J2SE 1.4中變成了一個核心包。在本文中,我們將介紹JAAS的一些核心概念,然後通過例子說明如何將JAAS應用到實際的程式中。本文的例子是根據我們一個基於Web的Java應用程式進行改編的,在這個例子中,我們使用了關聯式資料庫儲存使用者的登入資訊。由於使用了JAAS,我們實現了一個健壯而靈活的登入和身分識別驗證模組。

  Java驗證和授權:概論

  在JAAS出現以前,Java的安全模型是為了滿足跨平台的網路應用程式的需要而設計的。在Java早期版本中,Java通常是作為遠程代碼被使用,例如Applet,。因此最初的安全模型把注意力放在通過驗證代碼的來源來保護使用者上。早期的Java安全機制中包含的概念,如SercurityManager,沙箱概念,程式碼簽署,策略檔案,多是為了保護使用者。

  JAAS的出現反映了Java的演變。傳統的伺服器/用戶端程式需要實現登入和存取控制,JAAS通過對運行程式的使用者的進行驗證,從而達到保護系統的目的。雖然JAAS同時具有驗證和授權的能力,在這篇文章中,我們主要介紹驗證功能。

  通過在應用程式和底層的驗證和授權機制之間加入一個抽象層,JAAS可以簡化涉及到Java Security包的程式開發。抽象層獨立於平台的特性使開發人員可以使用各種不同的安全機制,而且不用修改應用程式級的代碼。和其他Java Security API相似,JAAS通過一個可擴充的架構:服務提供者介面(Service Provider Interface,SPI)來保證程式獨立於安全機制。服務提供者介面是由一組抽象類別和介面組成的。圖一中給出了JAAS程式的整體架構圖。應用程式級的代碼主要處理LoginContext。在LoginContext下面是一組動態配置的LoginModules。LoginModule使用正確的安全機制進行驗證。

  圖一給出了JAAS的概覽。應用程式層的代碼只需要和LoginContext打交道。在LoginContext之下是一組動態配置的LoginModule對象,這些對象使用相關的安全基礎結構進行驗證操作。


圖一 JAAS概覽

  JAAS提供了一些LoginModule的參考實現代碼,比如JndiLoginModule。開發人員也可以自己實現LoginModule介面,就象在我們例子中的RdbmsLonginModule。同時我們還會告訴你如何使用一個簡單的設定檔來安裝應用程式。

  為了滿足可插接性,JAAS是可堆疊的。在單一登入的情況下,一組安全模組可以堆疊在一起,然後被其他的安全機制按照堆疊的順序被調用。

  JAAS的實現者根據現在一些流行的安全結構模式和架構將JASS模型化。例如可堆疊的特性同Unix下的可堆疊驗證模組(PAM,Pluggable Authentication Module)架構就非常相似。從事務的角度看,JAAS類似於雙步提交(Two-Phase Commit,2PC)協議的行為。JAAS中安全配置的概念(包括策略檔案(Police File)和許可(Permission))來自於J2SE 1.2。JAAS還從其他成熟的安全架構中借鑒了許多思想。

  用戶端和伺服器端的JAAS

  開發人員可以將JAAS應用到用戶端和伺服器端。在用戶端使用JAAS很簡單。在伺服器端使用JAAS時情況要複雜一些。目前在應用伺服器市場中的JAAS產品還不是很一致,使用JAAS的J2EE應用伺服器有一些細微的差別。例如JBossSx使用自己的結構,將JAAS整合到了一個更大的安全架構中;而雖然WebLogic 6.x也使用了JAAS,安全架構卻完全不一樣。

  現在你能夠理解為什麼我們需要從用戶端和伺服器端的角度來看JAAS了。我們將在後面列出兩種情況下的例子。為了使伺服器端的例子程式更加簡單,我們使用了Resin應用伺服器。

  核心JAAS類

  在使用JAAS之前,你首先需要安裝JAAS。在J2SE 1.4中已經包括了JAAS,但是在J2SE 1.3中沒有。如果你希望使用J2SE 1.3,你可以從SUN的官方網站上下載JAAS。當正確安裝了JAAS後,你會在安裝目錄的lib目錄下找到jaas.jar。你需要將該路徑加入Classpath中。(註:如果你安裝了應用伺服器,其中就已經包括了JAAS,請閱讀應用伺服器的協助文檔以獲得更詳細的資訊)。在Java安全屬性檔案java.security中,你可以改變一些與JAAS相關的系統屬性。該檔案儲存在<jre_home>/lib/security目錄中。

  在應用程式中使用JAAS驗證通常會涉及到以下幾個步驟:

  1. 建立一個LoginContext的執行個體。

  2. 為了能夠獲得和處理驗證資訊,將一個CallBackHandler對象作為參數傳送給LoginContext。

  3. 通過調用LoginContext的login()方法來進行驗證。

  4. 通過使用login()方法返回的Subject對象實現一些特殊的功能(假設登入成功)。

  下面是一個簡單的例子:

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項,然後更具該項的內容決定該載入哪個LoginModule對象(參見圖二)。

  2. 在登入時,LoginContext對象調用每個LoginModule對象的login()方法。

  3. 每個login()方法進行驗證操作或獲得一個CallbackHandle對象。

  4. CallbackHandle對象通過使用一個或多個CallBack方法同使用者進行互動,獲得使用者輸入。

  5. 向一個新的Subject對象中填入驗證資訊。

  我們將對代碼作進一步的解釋。但是在這之前,讓我們先看代碼中涉及到的核心JAAS類和介面。這些類可以被分為三種類型:

  普通類型 Subject,Principal,憑證

  驗證 LoginContext,LoginModule,CallBackHandler,Callback

  授權 Policy,AuthPermission,PrivateCredentialPermission

  上面列舉的類和介面大多數都在javax.security.auth包中。在J2SE 1.4中,還有一些介面的實作類別在com.sun.security.auth包中。

  普通類型:Subject,Principal,憑證

  Subject類代表了一個驗證實體,它可以是使用者、管理員、Web服務,裝置或者其他的過程。該類包含了三中類型的安全資訊:

   身份(Identities):由一個或多個Principal對象表示

   公用憑證(Public credentials):例如名稱或公用秘鑰

   私人憑證(Private credentials):例如口令或私人密鑰

  Principal對象代表了Subject對象的身份。它們實現了java.security.Principal和java.io.Serializable介面。在Subject類中,最重要的方法是getName()。該方法返回一個身份名稱。在Subject對象中包含了多個Principal對象,因此它可以擁有多個名稱。由於登入名稱稱、社會安全號碼和Email地址都可以作為使用者的身份標識,可見擁有多個身份名稱的情況在實際應用中是非常普遍的情況。

  在上面提到的憑證並不是一個特定的類或借口,它可以是任何對象。憑證中可以包含任何特定安全系統需要的驗證資訊,例如標籤(ticket),密鑰或口令。Subject對象中維護著一組特定的私人和公有的憑證,這些憑證可以通過getPrivateCredentials()和getPublicCredentials()方法獲得。這些方法通常在應用程式層中的安全子系統被調用。

  驗證:LoginContext

  在應用程式層中,你可以使用LoginContext對象來驗證Subject對象。LoginContext對象同時體現了JAAS的動態可插入性(Dynamic Pluggability),因為當你建立一個LoginContext的執行個體時,你需要指定一個配置。LoginContext通常從一個文字檔中載入配置資訊,這些配置資訊告訴LoginContext對象在登入時使用哪一個LoginModule對象。

  下面列出了在LoginContext中經常使用的三個方法:

  login () 進行登入操作。該方法啟用了配置中制定的所有LoginModule對象。如果成功,它將建立一個經過了驗證的Subject對象;否則拋出LoginException異常。

  getSubject () 返回經過驗證的Subject對象

  logout () 登出Subject對象,刪除與之相關的Principal對象和憑證

  驗證:LoginModule

  LoginModule是調用特定驗證機制的介面。J2EE 1.4中包含了下面幾種LoginModule的實作類別:

  JndiLoginModule 用於驗證在JNDI中配置的目錄服務

  Krb5LoginModule 使用Kerberos協議進行驗證

  NTLoginModul 使用目前使用者在NT中的使用者資訊進行驗證

  UnixLoginModule 使用目前使用者在Unix中的使用者資訊進行驗證

  同上面這些模組綁定在一起的還有對應的Principal介面的實作類別,例如NTDomainPrincipal和UnixPrincipal。這些類在com.sun.security.auth包中。

  LoginModule介面中包含了五個方法:

  initialize () 當建立一LoginModule執行個體時會被建構函式調用

  login () 進行驗證

  commit () 當LgoninContext對象接受所有LoginModule對象傳回的結果後將調用該方法。該方法將Principal對象和憑證賦給Subject對象。

  

  abort () 當任何一個LoginModule對象驗證失敗時都會調用該方法。此時沒有任何Principal對象或憑證關聯到Subject對象上。

  logout () 刪除與Subject對象關聯的Principal對象和憑證。

  在應用程式的代碼中,程式員通常不會直接調用上面列出的方法,而是通過LigonContext間接調用這些方法。

  驗證:CallbackHandler和Callback

  CallbackHandler和Callback對象可以使LoginModule對象從系統和使用者那裡收集必要的驗證資訊,同時獨立於實際的收集資訊時發生的互動過程。

  

  JAAS在javax.sevurity.auth.callback包中包含了七個Callback的實作類別和兩個CallbackHandler的實作類別:ChoiceCallback、ConfirmationCallback、LogcaleCallback、NameCallback、PasswordCallback、TextInputCallback、TextOutputCallback、DialogCallbackHandler和TextCallBackHandler。Callback介面只會在用戶端會被使用到。我將在後面介紹如何編寫你自己的CallbackHandler類。

  設定檔

  上面我已經提到,JAAS的可擴充性來源於它能夠進行動態配置,而配置資訊通常是儲存在文本。這些文字檔有很多個配置塊構成,我們通常把這些配置塊稱作申請(Application)。每個申請對應了一個或多個特定的LoginModule對象。

  當你的代碼構造一個LoginContext對象時,你需要把設定檔中申請的名稱傳遞給它。LoginContext將會根據申請中的資訊決定啟用哪些LoginModule對象,按照什麼順序啟用以及使用什麼規則啟用。

  設定檔的結構如下所示:

Application {

ModuleClass Flag ModuleOptions;

ModuleClass Flag ModuleOptions;

...

};

Application {

ModuleClass Flag ModuleOptions;

...

};

...

  下面是一個名稱為Sample的申請

Sample {

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

}

  上面這個簡單的申請指定了LoginContext對象應該使用NTLoginModule進行驗證。類的名稱在ModuleClass中被指定。Flag控制當申請中包含了多個LoginModule時進行登入時的行為:Required、Sufficient、Requisite和Optional。最常用的是Required,使用它意味著對應的LoginModule對象必須被調用,並且必須需要通過所有的驗證。由於Flag本身的複雜性,本文在這裡不作深究。

  ModuleOption允許有多個參數。例如你可以設定調試參數為True(debug=true),這樣診斷輸出將被送到System.out中。

  設定檔可以被任意命名,並且可以被放在任何位置。JAAS架構通過使用java.securty.auth.long.config屬性來確定設定檔的位置。例如當你的應用程式是JaasTest,設定檔是目前的目錄下的jaas.config,你需要在命令列中輸入:

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

  圖二描述了設定檔中各元素之間的關係


圖二 JAAS的設定檔

  通過命令列方式進行登入驗證

  為了說明JAAS到底能幹什麼,我在這裡編寫了兩個例子。一個是簡單的由命令列輸入調用的程式,另一個是伺服器端的JSP程式。這兩個程式都通過使用者名稱/密碼的方式進行登入,然後使用關聯式資料庫對其進行驗證。

  為了通過資料庫進行驗證,我們需要:

  1. 實現RdbmsLoginModul類,該類可以對輸入的資訊進行驗證。

  2. 編輯一個設定檔,告訴LoginContext如何使用RdbmsLoginModule。

  3. 實現ConsoleCallbackHandler類,通過該類可以擷取使用者的輸入。

  4. 編寫應用程式代碼。

  在RdbmsLoginModul類中,我們必須實現LgoinModule介面中的五個方法。首先是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在調用login()方法時會調用initialize()方法。RdbmsLoginModule的第一個任務就是在類中儲存輸入參數的引用。在驗證成功後將向Subject對象中送入Principal對象和憑證。

  CallbackHandler對象將會在login()方法中被使用到。sharedState可以使資料在不同的LoginModule對象之間共用,但是在這個例子中我們不會使用它。最後是名為options的Map對象。options向LgoinModule對象傳遞在設定檔ModuleOption域中定義的參數的值。設定檔如下所示:

Example {

RdbmsLoginModule required

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

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

debug="true";

};

  在設定檔中,RdbmsLoginModule包含了五個參數,其中driver、url、user和password是必需的,而debug是可選闡述。driver、url、user和password參數告訴我們如何獲得JDBC串連。當然你還可以在ModuleOption域中加入資料庫中的表或列的資訊。使用這些參數的目的是為了能夠對資料庫進行操作。在LoginModule類的initialize()方法中我們儲存了每個參數的值。

  我們前面提到一個LoginContext對應的設定檔告訴它應該使用檔案中的哪一個申請。這個資訊是通過LgoinContext的建構函式傳遞的。下面是初始化用戶端的代碼,在代碼中建立了一個LoginContex對象並調用了login()方法。

ConsoleCallbackHandler cbh = new ConsoleCallbackHandler();

LoginContext lc = new LoginContext("Example", cbh);

lc.login();

  當LgoinContext.login()方法被調用時,它調用所有載入了的LoginModule對象的login()方法。在我們的這個例子中是RdbmsLoginModule中的login()方法。

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

  1. 建立兩個Callback對象。這些對象從使用者輸入中擷取使用者名稱/密碼。程式中使用了JAAS中的兩個Callback類:NameCallback和PasswordCallback(這兩個類包含在javax.security.auth.callback包中)。

  2. 通過將callbacks作為參數傳遞給CallbackHandler的handle()方法來啟用Callback。

  3. 通過Callback對象獲得使用者名稱/密碼。

  4. 在rdbmsValidate()方法中通過JDBC在資料庫中驗證擷取的使用者名稱/密碼。

  下面是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對象是如何同使用者進行互動的:

  使用JSP和關聯式資料庫進行登入驗證

  現在我們希望將通過命令列調用的程式一直到Web應用程式中。由於Web應用程式與一般的應用程式的互動方式有區別不同,我們將不能使用JAAS提供的標準Callback和CallbackHandler類。因為我們不能在Web程式中開啟一個命令視窗讓使用者輸入資訊。也許你會想到我們也可以使用基於HTTP的驗證,這樣我們可以從瀏覽器彈出的使用者名稱/密碼視窗中獲得使用者輸入。但是這樣做也有一些問題,它要求建立起雙向的HTTP串連(在login()方法很難實現雙向串連)。因此在我們的例子中我們會使用利用表單進行登入,從表單中擷取使用者輸入的資訊,然後通過RdbmsLoginModule類驗證它。

  由於我們沒有在應用程式層同LoginModule直接打交道,而是通過LgoinContext來調用其中的方法的,我們如何將獲得使用者名稱和密碼放入LoginModule對象中呢?我們可以使用其它的方法來繞過這個問題,例如我們可以在建立LoginContext對象前先初始化一個Subject對象,在Subject對象的憑證中儲存使用者名稱和密碼。然後我們可以將該Subject對象傳遞給LoginContext的建構函式。這種方法雖然從技術上來說沒有什麼問題,但是它在應用程式層增加了很多與安全機制相關的代碼。而且通常是在驗證後向Subject送入憑證,而不是之前。

前面我們提到可以實現一個CallbackHandler類,然後將它的執行個體傳遞給LoginContext對象。在這裡我們可以採用類似的方法來處理使用者名稱和密碼。我們實現了一個新的類PassiveCallbackHandler。下面是在JSP中使用該類的代碼:


String user = request.getParameter("user");

String pass = request.getParameter("pass");

PassiveCallbackHandler cbh = new PassiveCallbackHandler(user, pass);

LoginContext lc = new LoginContext("Example", cbh);

  PassiveCallbackHandler中建構函式的參數包含了使用者名稱和密碼。因此它可以在Callbick對象中設定正確的值。下面是PassiveCallbackHandler類的handle()方法的代碼:

public void handle(Callback[] callbacks)

throws java.io.IOException, UnsupportedCallbackException {

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

if (callbacks[i] instanceof NameCallback) {

NameCallback nameCb = (NameCallback)callbacks[i];

nameCb.setName(user);

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

PasswordCallback passCb = (PasswordCallback)callbacks[i];

passCb.setPassword(pass.toCharArray());

} else {

throw(new UnsupportedCallbackException(callbacks[i],

"Callback class not supported"));

}

}

}

}

  從上面的代碼中可以發現實際上我們只是從ConsoleCallbackHandler中去除了那些提示使用者輸入的代碼。

  在運行這個JSP例子的時候,我們需要設定系統屬性,這樣LgoinContext對象才知道如何尋找名稱為"Example"的配置。我們使用的是Resin伺服器。在resin.conf中,我們增加了一個<caucho.com>節點:

<system-property

java.security.auth.login.config="/resin/conf/jaas.config"/>

  小結

  JAAS通過提供動態、可擴充的模型來進行使用者驗證和控制許可權,從而使應用程式有更加健壯的安全機制。同時它還能夠讓你能夠很輕鬆地建立自己的登入機制。JAAS可以同時在在用戶端和伺服器端應用程式上工作。雖然在伺服器端的JAAS到目前還不是很穩定,但是隨著技術的發展,相信會很好地解決這個問題。

聯繫我們

該頁面正文內容均來源於網絡整理,並不代表阿里雲官方的觀點,該頁面所提到的產品和服務也與阿里云無關,如果該頁面內容對您造成了困擾,歡迎寫郵件給我們,收到郵件我們將在5個工作日內處理。

如果您發現本社區中有涉嫌抄襲的內容,歡迎發送郵件至: info-contact@alibabacloud.com 進行舉報並提供相關證據,工作人員會在 5 個工作天內聯絡您,一經查實,本站將立刻刪除涉嫌侵權內容。

A Free Trial That Lets You Build Big!

Start building with 50+ products and up to 12 months usage for Elastic Compute Service

  • Sales Support

    1 on 1 presale consultation

  • After-Sales Support

    24/7 Technical Support 6 Free Tickets per Quarter Faster Response

  • Alibaba Cloud offers highly flexible support services tailored to meet your exact needs.