JAAS 是個什么梗


參考資料##

該文中的內容來源於 Oracle 的官方文檔。Oracle 在 Java 方面的文檔是非常完善的。對 Java 8 感興趣的朋友,可以從這個總入口 Java SE 8 Documentation 開始尋找感興趣的內容。本博客不定期從 Oracle 官網搬磚。這一篇主要講 JAAS,官方文檔在這里 Java Authentication and Authorization Service (JAAS) Reference Guide

前言##

說實話,我用 Java 這么多年基本上就沒有用到過 JAAS,所以它在我心中永遠都是一個梗。和我一樣一直沒有跨過這個梗的 Java 程序員多嗎?今天,就讓我們一起揭開它的面紗,看看 JAAS 究竟是個什么鬼。

從定義上看,JAAS 是一個 Java 程序專用的認證和授權框架。認證和授權我們並不陌生,比如我們登錄系統的時候需要輸入用戶名和密碼,遠程連接 SSH 的時候需要提供密鑰,網絡支付的時候需要提供手機號和驗證碼等等,這些都是對用戶進行認證的方式。我們陌生的是,在我們實現用戶的認證和授權這樣的功能時,我們是否需要用到 JAAS,以及 JAAS 是否如商家宣傳的那樣好用,再以及 JAAS 這個框架是否解決了用戶認證和授權過程中那些千奇百怪的用戶交互方式的統一抽象。

JAAS 的主要優點是什么?官方文檔說它是一個可插拔的用戶認證和授權框架,當我們需要更改用戶的認證方式和授權的時候,只需要修改少量的配置文件即可,無需更改程序代碼。和前面講過的 使用 SecurityManager 和 Policy File 管理 Java 程序的權限 相比,JAAS 的區別又是什么?可以這樣理解,SecurityManager 和 Policy File 是針對程序本身的授權,比如程序從哪個路徑運行,程序由誰簽名等,而 JAAS 則是從用戶的角度進行授權,它關注的是:哪一個用戶在運行這個程序?哪一個用戶在訪問需要授權的資源?

下面,我們看看實際的 JAAS 程序長什么樣。

JAAS 示例程序的運行效果##

對於陌生的領域,如果能直接看到運行效果,應該可以加快我們對它的理解速度。下圖顯示了一個 JAAS 示例程序的運行:

可以看到,要使用 JAAS,除了程序之外,還需要准備額外的配置文件。在本例中,有兩個配置文件,一個 youxia_jaas.policy 文件,這是一個 Policy File,在 使用 SecurityManager 和 Policy File 管理 Java 程序的權限 中講過,另一個文件是 youxia_jaas.config 文件,它就是 JAAS 必須用到的配置文件。另外,要使用 JAAS,還必須以 -Djava.security.auth.login.config=... 這個參數運行 java 命令。這個在上圖中都有展示。

示例程序運行的時候,會跳出一個對話框讓我們輸入 keystore 中的條目的別名以及 keystore 的密碼。這就是一種和用戶的交互方式。JAAS 需要解決的一個問題就是如何將和用戶交互的千奇百怪的方式進行統一,對話框是一種常見的方式,從標准輸入讀取用戶輸入也是一種常見的方式,還有其它一些我們想得到和想不到的方式等等,這個后面會講到。在示例程序中,我們輸入的別名會被用來認證我們的身份,然后決定我們是否有權訪問相應的資源。比如,當我們輸入的是 youxia,則可以順利讀取 System Property,可以順利統計 src/com/xkland/sample/JaasDemo.java 中的字符數。當我們輸入的是 stranger 時,則顯示該用戶沒有相應的權限。如下圖:

官方對 JAAS 的宣傳說它可以不用更改代碼、只需要更改配置文件就能更改認證方式,是這樣的嗎?在本示例中,我用的認證方式是通過訪問 keystore 中的條目來的。下面,我把它改成通過當前 Linux 的用戶進行認證。如下圖:

可以看到,我只修改了 youxia_jaas.config 文件,將 LoginModel 從 KeyStoreLoginModel 改成了 UnixLoginModel,沒有修改程序代碼。更改 LoginModel 后,程序輸出的 Principal 的格式和內容都變了,但是功能仍然正常執行。而且可以看到,可以有很多個不同格式的 Principal 代表一個用戶,比如本例中就有的 Principal 代表用戶名,有的 Principal 代表用戶 ID,還有的 Principal 代表 Group ID。

LoginModel 控制的只是認證,認證成功后,我們的程序中就有了代表用戶的 Principal,哦不,是 Principal(s)。而授權靠的依然是 Policy File,恰好,policytool 中有專用的基於 Principal 進行授權的按鈕和文本框,我們終於知道 Principal 是什么了(中文版是“主用戶”,英文版是“Principal”),如下圖:

解析 JAAS 的架構##

看完示例程序的運行,我們對 JAAS 已經有了一個比較直觀的了解。下面,我們在 Eclipse 的輔助下來解析一下 JAAS 的架構。隨着探索的領域越來越深,是時候上 IDE 了,關於幾種 IDE 的體驗,請看這里 Java 開發主流 IDE 環境體驗

首先,JAAS 認證的核心是 LoginModel,它代表了認證的方式。認證的方式有很多種,但 JDK 自帶的幾種實現不一定能滿足我們的需求。所以,如果我們真的要在產品中使用 JAAS 的話,一定要學會實現自己的 LoginModel。先來看看 JDK 中自帶了哪幾種 LoginModel 的實現,通過 Eclipse 的 Type Hierarchy 功能可以查看,如下圖:

可以看到,JDK 自帶了 JndiLoginModel、UnixLoginModel、KeyStoreLoginModel、Krb5LoginModel、LdapLoginModel 等實現,看名字就能猜到它們使用的認證方式。很顯然,在實際的產品種,我們必須實現自己的 LoginModel,比如說,在 Web 開發中流行的都是把用戶名和密碼儲存在關系型數據庫中,那么要在 Web 開發中使用 JAAS,就要實現一個 RdbmsLoginModel。在本例中,我就不實現自己的 LoginModel 了,就用 JDK 自帶的 UnixLoginModel 和 KeyStoreLoginModel 吧,做示范已經足夠了。

而且從上圖中可以看出, UnixLoginModel 使用了好幾個不同類型的 Principal,這和示例程序的輸出是一致的。KeyStoreLoginModel 就只使用了一種 Principal,那就是 X500Principal,如下圖:

JDK 自帶的 Principal 有多少種呢?看下圖:

每一個 Principal 都有不同的格式和存儲數據的方式,但它們都有一個 getName() 方法,返回代表該 Principal 的字符串。在實際應用中,我們可以實現自己的 Principal。

其次,JAAS 和用戶交互的核心是 CallbackHandler,它代表了和用戶交互的方式。JDK 自帶了哪幾種 CallbackHandler 呢?看下圖:

從名字可以看出,DialogCallbackHandler 就是使用對話框和用戶交互,而 TextCallbackHandler 就是使用標准輸入輸出和用戶交互,其它幾種 CallbackHandler 我也不熟。但是很顯然,在實際的產品中,我們必須實現自己的 CallbackHandler,比如在 Web 開發中, DialogCallbackHandler 和 TextCallbackHandler 都是用不到的,可能需要實現自己的 HttpCallbackHandler。在本例中,我也不實現自己的 CallbackHandler 了,就用 JDK 自帶的 DialogCallbackHandler 和 TextCallbackHandler 吧。不知道為什么 DialogCallbackHandler 還是 Deprecated 的,也就是說在下個版本中可能就沒有了。為什么了,難道是 Swing 已死?要是果真如此,NetBeans 死期也不遠了。

最后,LoginModel 和 CallbackHandler 之間溝通的橋梁是 Callback。Callback 就那么幾種,如下圖:

當 LoginModel 需要從用戶獲取 username 時,就向 CallbackHandler 發一個 NameCallback, CallbackHandler 和用戶交互,獲得 username 后再發回去。當 LoginModel 需要密碼的時候就向 CallbackHandler 發一個 PasswordCallback,CallbackHandler 和用戶交互,獲得 password 后再發回去。當 LoginModel 需要給用戶發送一些消息時,就向 CallbackHandler 發一個 TextOutputCallback,讓 CallbackHandler 將消息顯示給用戶。以此類推,總之,和用戶進行交互的永遠是 CallbackHandler。

編寫使用 JAAS 的程序##

通過前面的解析,我們已經了解了 JAAS 中的幾個核心接口。但是在實際程序中如何使用呢?那就要看這里的示例程序了。下圖是 JaasDemo.java 的代碼的前半截:

可以看到,用戶認證需要用到 LoginContext 類。在上圖的代碼中,先是使用 lc = new LoginContext("JaasDemo", new DialogCallbackHandler()); 來創建一個 LoginContext 類的對象 lc。就是這個語句將 LoginModel 和 CallbackHandler 聯系到了一起。LoginModel 在哪?在配置文件中呢!這里的字符串 "JaasDemo" 代表的就是配置文件中的一個條目,而配置文件是在運行程序的時候通過 -Djava.security.auth.login.config= 參數指定的。然后,調用 lc.login() 就完成了用戶的認證。就是這么簡單。

用戶認證失敗程序就退出了。用戶認證成功了,怎么才能讓該用戶去執行需要授權的功能呢?請看示例程序 JaasDemo.java 的后半截,如下圖:

這里用到了一個新類 Subject,它代表的才是通過認證的用戶。所以代碼 Subject mySubject = lc.getSubject(); 就是獲得當前通過認證的用戶。前面提到過,一個用戶可以有很多個 Principal,所以通過 mySubject.getPrincipals(); 獲得所有的 Principal,然后把它們打印出來。怎么樣才能讓用戶執行需要授權的任務呢?我們必須把任務代碼寫到另外一個類中,這個類必須從 PrivilegedAction<T> 或 PrivilegedExceptionAction<T> 繼承,如下圖,我的 SampleAction 類的代碼:

PrivilegedExceptionAction<T> 是一個泛型類,其中的模板參數 T 代表的是 run() 方法返回的值的類型。在本例中我不關心它返回什么,所以用 Object 作為模板參數就行了。然后在 JaasDemo.java 中使用 Subject.doAsPrivileged(mySubect, new SampleAction(), null); 調用我的任務即可。這里的示例任務很簡單,就是獲取幾個 System Property,再統計一個文件的字符數,這個示例任務在上上篇博客中出現過。

為示例程序授權##

授權還是要使用 Policy File。關於 policytool 工具的使用我就不展示了,直接展示 youxia_jaas.policy 文件的內容,如下圖:

可以看到,這個 Policy File 中有三個條目。第一個條目中不涉及到 Principal,而且它授權的兩個 Permission 我們之前也沒有碰到過。其實很簡單,這個條目就是對我們代碼中的 new LoginContext()Subject.doAsPrivileged() 這兩個語句進行授權,如果沒有這個條目,SecurityManager 是不會允許我們的程序運行的。而后面兩個條目分別針對 X500Principal 和 UnixPrincipal 進行授權,讓我們的程序能夠訪問相應的 System Property 和相應的文件。就是這么簡單。

在 IDE 中運行示例程序##

示例程序的運行效果本文一開篇就已經給出了。其實除了從控制台執行程序外,我們還能從 IDE 中直接運行程序。從 IDE 運行程序需要修改程序運行的參數,如下圖:

運行效果如下圖:

總結##

這就是我對 JAAS 的理解。雖然以后的工作中可能用不到,但是至少我知道了 JAAS 怎么用。這個示例程序是很簡單的,僅僅使用了 JDK 自帶的 KeyStoreLoginModel、UnixLoginModel 和 DialogCallbackHandler,而實際工作中需要實現自己的 LoginModel 和 CallbackHandler。只要理解了 JAAS 的本質,實現自己的 LoginModel 和 CallbackHandler 也不難。具體細節請大家自己查看官方文檔吧。


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM