“Java 認證和授權服務”(Java Authentication and Authorization Service,JAAS)是對 Java 2 SDK 的擴展。在 JAAS 下,可以給予用戶或服務特定的許可權來執行 Java 類中的代碼。在本文中,軟件工程師 Carlos Fonseca 向您展示如何為企業擴展 JAAS 框架。向 JAAS 框架添加類實例級授權和特定關系使您能夠構建更動態、更靈活并且伸縮性更好的企業應用程序。請單擊文章頂部或底部的 討論,在 討論論壇與作者和其他讀者分享您對本文的想法。
大多數 Java 應用程序都需要某種類實例級的訪問控制。例如,基于 Web 的、自我服務的拍賣應用程序的規范可能有下列要求:
任何已注冊(經過認證)的用戶都可以創建一個拍賣,但只有創建拍賣的用戶才可以修改這個拍賣。
這意味著任何用戶都可以執行被編寫用來創建 Auction
類實例的代碼,但只有擁有該實例的用戶可以執行用來修改它的代碼。通常情況下,創建 Auction
實例的用戶就是所有者。這被稱為 類實例所有者關系(class instance owner relationship)。
該應用程序的另一個要求可能是:
任何用戶都可以為拍賣創建一個投標,拍賣的所有者可以接受或拒絕任何投標。
再一次,任何用戶都可以執行被編寫用來創建 Bid
類實例的代碼,但只有擁有該實例的用戶會被授予修改該實例的許可權。而且, Auction
類實例的所有者必須能夠修改相關的 Bid
類實例中的接受標志。這意味著在 Auction
實例和相應的 Bid
實例之間有一種被稱為 特定關系(special relationship)的關系。
不幸的是,“Java 認證和授權服務”(JAAS)― 它是 Java 2 平臺的一部分 ― 沒有考慮到類實例級訪問控制或者特定關系。在本文中,我們將擴展 JAAS 框架使其同時包含這兩者。推動這種擴展的動力是允許我們將訪問控制分離到一個通用的框架,該框架使用基于所有權和特定關系的策略。然后管理員可以在應用程序的生命周期內更改這些策略。
在深入到擴展 JAAS 框架之前,我們將重溫一下 Java 2 平臺的訪問控制機制。我們將討論策略文件和許可權的使用,并討論 SecurityManager
和 AccessController
之間的關系。
Java 2 平臺中的訪問控制
在 Java 2 平臺中,所有的代碼,不管它是本地代碼還是遠程代碼,都可以由策略來控制。 策略(policy)由不同位置上的代碼的一組許可權定義,或者由不同的簽發者定義、或者由這兩者定義。 許可權允許對資源進行訪問;它通過名稱來定義,并且可能與某些操作關聯在一起。
抽象類 java.security.Policy
被用于表示應用程序的安全性策略。缺省的實現由 sun.security.provider.PolicyFile
提供,在 sun.security.provider.PolicyFile
中,策略被定義在一個文件中。清單 1 是一個典型策略文件示例:
清單 1. 一個典型的策略文件
// Grant these permissions to code loaded from a sample.jar file
// in the C drive and if it is signed by XYZ
grant codebase "file:/C:/sample.jar", signedby "XYZ" {
// Allow socket actions to any host using port 8080
permission java.net.SocketPermission "*:8080", "accept, connect,
listen, resolve";
// Allows file access (read, write, execute, delete) in
// the user's home directory.
Permission java.io.FilePermission "${user.home}/-", "read, write,
execute, delete";
};
|
SecurityManager 對 AccessController
在標準 JDK 分發版中,控制代碼源訪問的機制缺省情況下是關閉的。在 Java 2 平臺以前,對代碼源的訪問都是由 SecurityManager
類管理的。 SecurityManager
是由 java.security.manager
系統屬性啟動的,如下所示:
java -Djava.security.manager
|
在 Java 2 平臺中,可以將一個應用程序設置為使用 java.lang.SecurityManager
類或者 java.security.AccessController
類管理敏感的操作。 AccessController
在 Java 2 平臺中是新出現的。為便于向后兼容, SecurityManager
類仍然存在,但把自己的決定提交 AccessController
類裁決。 SecurityManager
和 AccessController
都使用應用程序的策略文件確定是否允許一個被請求的操作。清單 2 顯示了 AccessController
如何處理 SocketPermission
請求:
清單 2. 保護敏感操作
Public void someMethod() {
Permission permission =
new java.net.SocketPermission("localhost:8080", "connect");
AccessController.checkPermission(permission);
// Sensitive code starts here
Socket s = new Socket("localhost", 8080);
}
|
在這個示例中,我們看到 AccessController
檢查應用程序的當前策略實現。如果策略文件中定義的任何許可權暗示了被請求的許可權,該方法將只簡單地返回;否則拋出一個 AccessControlException
異常。在這個示例中,檢查實際上是多余的,因為缺省套接字實現的構造函數也執行相同的檢查。
在下一部分,我們將更仔細地看一下 AccessController
如何與 java.security.Policy
實現共同合作安全地處理應用程序請求。
運行中的 AccessController
AccessController
類典型的 checkPermission(Permission p)
方法調用可能會導致下面的一系列操作:
-
AccessController
調用 java.security.Policy
類實現的 getPermissions(CodeSource codeSource)
方法。
-
getPermissions(CodeSource codeSource)
方法返回一個 PermissionCollection
類實例,這個類實例代表一個相同類型許可權的集合。
-
AccessController
調用 PermissionCollection
類的 implies(Permission p)
方法。
- 接下來,
PermissionCollection
調用集合中包含的單個 Permission
對象的 implies(Permission p)
方法。如果集合中的當前許可權對象暗示指定的許可權,則這些方法返回 true
,否則返回 false
。
現在,讓我們更詳細地看一下這個訪問控制序列中的重要元素。
PermissionCollection 類
大多數許可權類類型都有一個相應的 PermissionCollection
類。這樣一個集合的實例可以通過調用 Permission
子類實現定義的 newPermissionCollection()
方法來創建。 java.security.Policy
類實現的 getPermissions()
方法也可以返回 Permissions
類實例 ― PermissionCollection
的一個子類。這個類代表由 PermissionCollection
組織的不同類型許可權對象的一個集合。 Permissions
類的 implies(Permission p)
方法可以調用單個 PermissionCollection
類的 implies(Permission p)
方法。
CodeSource 和 ProtectionDomain 類
許可權組合與 CodeSource
(被用于驗證簽碼(signed code)的代碼位置和證書)被封裝在 ProtectionDomain
類中。有相同許可權和相同 CodeSource
的類實例被放在相同的域中。帶有相同許可權,但不同 CodeSource
的類被放在不同的域中。一個類只可屬于一個 ProtectionDomain
。要為對象獲取 ProtectionDomain
,請使用 java.lang.Class
類中定義的 getProtectionDomain()
方法。
許可權
賦予 CodeSource
許可權并不一定意味著允許所暗示的操作。要使操作成功完成,調用棧中的每個類必須有必需的許可權。換句話說,如果您將 java.io.FilePermission
賦給類 B,而類 B 是由類 A 來調用,那么類 A 必須也有相同的許可權或者暗示 java.io.FilePermission
的許可權。
在另一方面,調用類可能需要臨時許可權來完成另一個擁有那些許可權的類中的操作。例如,當從另一個位置加載的類訪問本地文件系統時,我們可能不信任它。但是,本地加載的類被授予對某個目錄的讀許可權。這些類可以實現 PrivilegedAction
接口來給予調用類許可權以便完成指定的操作。調用棧的檢查在遇到 PrivilegedAction
實例時停止,有效地將執行指定操作所必需的許可權授予所有的后繼類調用。
使用 JAAS
顧名思義,JAAS 由兩個主要組件組成:認證和授權。我們主要關注擴展 JAAS 的授權組件,但開始我們先簡要概述一下 JAAS 認證,緊接著看一下一個簡單的 JAAS 授權操作。
JAAS 中的用戶認證
JAAS 通過添加基于 subject 的策略加強了 Java 2 中定義的訪問控制安全性模型。許可權的授予不僅基于 CodeSource
,還基于執行代碼的用戶。顯然,要使這個模型生效,每個用戶都必須經過認證。
JAAS 的認證機制建立在一組可插登錄模塊的基礎上。JAAS 分發版包含幾個 LoginModule
實現。 LoginModules
可以用于提示用戶輸入用戶標識和密碼。 LoginContext
類使用一個配置文件來確定使用哪個 LoginModule
對用戶進行認證。這個配置可以通過系統屬性 java.security.auth.login.config
指定。一個示例配置是:
java -Djava.security.auth.login.config=login.conf
|
下面是一個登錄配置文件的樣子:
Example {
com.ibm.resource.security.auth.LoginModuleExample required
debug=true userFile="users.xml" groupFile="groups.xml";
};
|
認識您的主體
Subject
類被用于封裝一個被認證實體(比如用戶)的憑證。一個 Subject
可能擁有一個被稱為 主體(principal)的身份分組。例如,如果 Subject
是一個用戶,用戶的名字和相關的社會保險號可能是 Subject
的某些身份或主體。主體是與身份名關聯在一起的。
Principal
實現類及其名稱都是在 JAAS 策略文件中指定的。缺省的 JAAS 實現使用的策略文件與 Java 2 實現的策略文件相似 ― 除了每個授權語句必須與至少一個主體關聯在一起。 javax.security.auth.Policy
抽象類被用于表示 JAAS 安全性策略。它的缺省實現由 com.sun.security.auth.PolicyFile
提供,在 com.sun.security.auth.PolicyFile
中策略定義在一個文件中。清單 3 是 JAAS 策略文件的一個示例:
清單 3. 示例 JAAS 策略文件
// Example grant entry
grant codeBase "file:/C:/sample.jar", signedby "XYZ",
principal com.ibm.resource.security.auth.PrincipalExample "admin" {
// Allow socket actions to any host using port 8080
permission java.net.SocketPermission
"*:8080", "accept, connect, listen, resolve";
// Allows file access (read, write, execute, delete) in
// the user's home directory.
Permission java.io.FilePermission
"${user.home}/-", "read, write, execute, delete";
};
|
這個示例與清單 1 中所示的標準 Java 2 策略文件相似。實際上,唯一的不同是主體語句,該語句聲明只有擁有指定主體和主體名字的 subject(用戶)被授予指定的許可權。
再一次,使用系統屬性 java.security.auth.policy
指出 JAAS 策略文件駐留在何處,如下所示:
java -Djava.security.auth.policy=policy.jaas
|
Subject
類包含幾個方法來作為特殊 subject 執行工作;這些方法如下所示:
public static Object
doAs(Subject subject, java.security.PrivilegedAction action)
public static Object
doAs(Subject subject, java.security.PrivilegedAction action)
throws java.security.PrivilegedActionException
|
注意,用來保護敏感代碼的方法與“Java 2 代碼源訪問控制”(Java 2 CodeSource Access Control)概述中描述的方法相同。請參閱 參考資料部分以了解更多關于 JAAS 中代碼源訪問控制和認證的信息。
JAAS 中的授權
清單 4 顯示一個授權請求的結果,該請求使用清單 3 中顯示的 JAAS 策略文件。假設已經安裝了 SecurityManager
,并且 loginContext
已經認證了一個帶有名為“admin”的 com.ibm.resource.security.auth.PrincipalExample
主體的 Subject
。
清單 4. 一個簡單的授權請求
public class JaasExample {
public static void main(String[] args) {
...
// where authenticatedUser is a Subject with
// a PrincipalExample named admin.
Subject.doAs(authenticatedUser, new JaasExampleAction());
...
}
}
public class JaasExampleAction implements PrivilegedAction {
public Object run() {
FileWriter fw = new FileWriter("hi.txt");
fw.write("Hello, World!");
fw.close();
}
}
|
這里,敏感代碼被封裝在 JaasExampleAction
類中。還要注意,調用類不要求為 JaasExampleAction
類代碼源授予許可權,因為它實現了一個 PrivilegedAction
。
擴展 JAAS
大多數應用程序都有定制邏輯,它授權用戶不僅僅在類上執行操作,而且還在該類的實例上執行操作。這種授權通常建立在用戶和實例之間的關系上。這是 JAAS 的一個小缺點。然而,幸運的是,這樣設計 JAAS 使得 JAAS 可以擴展。只要做一點工作,我們將可以擴展 JAAS,使其包含一個通用的、類實例級的授權框架。
在文章開頭處我已經說明了,抽象類 javax.security.auth.Policy
被用于代表 JAAS 安全性策略。它的缺省實現是由 com.sun.security.auth.PolicyFile
類提供。 PolicyFile
類從 JAAS 格式的文件(象清單 3 中顯示的那個一樣)中讀取策略。
我們需要向這個文件添加一個東西為類實例級授權擴展策略定義:一個與許可權語句相關的可選關系參數。
缺省 JAAS 許可權語句的格式如下:
permission <permission implementation class> [name], [actions];
|
我們在這個許可權語句的末尾添加一個可選的關系參數來完成策略定義。下面是新許可權語句的格式:
permission <permission implementation class>
[name], [actions], [relationship];
|
在為類實例級授權擴展 JAAS 時要注意的最重要的一點是:許可權實現類必須有一個帶三個參數的構造函數。第一個參數是名稱參數,第二個是行為參數,最后一個是關系參數。
解析新文件格式
既然文件格式已經改變,就需要一個新的 javax.security.auth.Policy
子類來解析文件。
為簡單起見,我們的示例使用了一個新的 javax.security.auth.Policy
子類 com.ibm.resource.security.auth.XMLPolicyFile
,來從 XML 文件讀取策略。在實際的企業應用程序中,關系數據庫更適合執行這個任務。
使用 XMLPolicyFile
類代替缺省的 JAAS 訪問控制策略實現的最容易的方法是向 java.security
屬性文件添加 auth.policy.provider=com.ibm.resource.security.auth.XMLPolicyFile
條目。 java.security
屬性文件位于 Java 2 平臺運行時的 lib/security 目錄下。清單 5 是與 XMLPolicyFile
類一起使用的樣本 XML 策略文件:
清單 5. 一個 XML 策略文件
<?xml version="1.0"?>
<policy>
<grant codebase="file:/D:/sample_actions.jar">
<principal classname=
"com.ibm.resource.security.auth.PrincipalExample" name="users">
<permission classname=
"com.ibm.resource.security.auth.ResourcePermission"
name="com.ibm.security.sample.Auction"
actions="create" />
<permission classname=
"com.ibm.resource.security.auth.ResourcePermission"
name="com.ibm.security.sample.Auction"
actions="read" />
<permission classname=
"com.ibm.resource.security.auth.ResourcePermission"
name="com.ibm.security.sample.Auction"
actions="write"
relationship="owner" />
<permission classname=
"com.ibm.resource.security.auth.ResourcePermission"
name="com.ibm.security.sample.Bid"
actions="create" />
<permission classname=
"com.ibm.resource.security.auth.ResourcePermission"
name="com.ibm.security.sample.Bid"
actions="read" />
<permission classname=
"com.ibm.resource.security.auth.ResourcePermission"
name="com.ibm.security.sample.Bid"
actions="write"
relationship="owner" />
<permission classname=
"com.ibm.resource.security.auth.ResourcePermission"
name="com.ibm.security.sample.Bid"
actions="accept"
relationship="actionOwner" />
</principal>
</grant>
</policy>
|
在這個示例策略文件中,任何與名為 PrincipalExample
的用戶有關的用戶( Subject
)都可以創建并讀取一個 Auction.class
實例。但是,只有創建該實例的用戶才可以更新(寫)它。這是第三個 permission 元素定義的,該元素包含值為 owner 的 relationship 屬性。 Bid.class
實例也是一樣,除了相應 Auction.class
實例的所有者可以更改投標接受標志。
Resource 接口
要求類實例級訪問控制的類必須實現 Resource
接口。該接口的 getOwner()
方法返回類實例的所有者。 fulfills(Subject subject, String relationship)
方法被用于處理特定關系。另外,這些類使用 com.ibm.resource.security.auth.ResourcePermission
類保護敏感代碼。例如, Auction
類擁有下列構造函數:
public Auction() {
Permission permission =
new ResourcePermission("com.ibm.security.sample.Auction", "create");
AccessController.checkPermission(permission);
}
|
所有者關系
ResourcePermission
類的 implies(Permission p)
方法是這個框架的關鍵。 implies()
方法就等同性比較名稱和行為屬性。如果定義了一個關系,那么必須把受保護的類實例( Resource
)傳遞到 ResourcePermission
構造函數中。 ResourcePermission
類理解所有者關系。它將類實例的所有者與執行代碼的 subject(用戶)進行比較。特定關系被委托給受保護類的 fulfills()
方法。
例如,在清單 5 中所示的 XML 策略文件中,只有 Auction
類實例的所有者可以更新(寫)文件。該類的 setter 方法使用清單 6 中顯示的保護代碼:
清單 6. 運行中的 implies(Permission) 方法
public void setName(String newName) {
Permission permission =
new ResourcePermission("com.ibm.security.sample.Auction", "write", this);
AccessController.checkPermission(permission);
// sensitive code
this.name = newName;
}
|
被傳遞到 ResourcePermission
構造函數中的 this
引用代表 Auction
類實現的 Resource
接口。由于策略文件中列出的關系是 owner,所以 ResourcePermission
類使用這個引用檢查當前 Subject
(用戶)是否擁有與實例所有者相匹配的主體。如果指定了另一個關系,那么 ResourcePermission
類調用 Auction
類的 fulfills(Subject subject, String relationship)
方法。由 Resource
實現類提供 fulfills()
方法中的邏輯。
XML 策略文件中列出的 Bid
類擁有清單 7 中所示的方法(假設 Bid
類實例有一個對相應 Auction
類實例的引用 ― auction)。
清單 7. 處理特定關系
public void setAccepted(boolean flag) {
Permission permission =
new ResourcePermission("com.ibm.security.sample.Auction", "accept", this);
AccessController.checkPermission(permission);
// sensitive code
this.accepted = flag;
}
public boolean fulfills(Subject user, String relationship) {
if( relationship.equalsIgnoreCase("auctionOwner") ) {
String auctionOwner = auction.getOwner();
Iterator principalIterator = user.getPrincipals().iterator();
while(principalIterator.hasNext()) {
Principal principal = (Principal) principalIterator.next();
if( principal.getName().equals(auctionOwner) )
return true;
}
}
return false;
}
|
傳遞到 fulfills()
方法中的關系字符串是策略文件中列出的關系。在這個案例中,我們使用了“ auctionOwner
”字符串。
缺省情況下, XMLPolicyFile
類在當前工作目錄中查找名為 ResourcePolicy.xml
的文件。系統屬性 com.ibm.resource.security.auth.policy
可以用于指定另一個不同的文件名和位置。
一個可運行的示例
綜合這些信息,我們將運行一個簡單的命令行示例。該示例程序包含三個 jar 文件:
- resourceSecurity.jar
- example.jar
- exampleActions.jar
resourceSecurity.jar 文件包含允許實例級訪問控制的 JAAS 擴展框架。它還包含一個 LoginModuleExample
類,這個類從 XML 文件讀取用戶認證信息。用戶標識和密碼存儲在 users.xml 文件中。用戶組存儲在 groups.xml 文件中。關于 LoginModuleExample
的更多信息,請參閱 參考資料部分。
該示例包含四個附加的文件:
- login.conf
- policy
- resourcePolicy.xml
- run.bat
在試圖運行這個示例程序之前,請確保更新了 run.bat、policy 和 resourcePolicy.xml 文件中的路徑。缺省情況下,所有的密碼都是“passw0rd”。
示例如何工作
該示例程序提示輸入用戶標識和密碼。它用 users.xml 文件中的條目核對所提供的用戶標識和密碼。在認證了用戶之后,程序設法創建一個 UserProfile
類實例,修改它并從中讀取。缺省情況下, UserProfile
類的所有者是 Jane(jane)。當 Jane 登錄時,三個操作全部成功。當 John(john)登錄時,只有創建操作成功。當 Jane 的經理 Lou(lou)登錄時,只有第一個和最后一個操作成功。當系統管理員(admin)登錄時,操作全部成功。當然,只有當提供的 ResourcePolicy.xml 文件未被修改時,上述這些才都是真的。
示例安裝
下面的安裝指導假設您正在使用 JDK 1.3 并且已經把文件解壓縮到 d:\JaasExample 目錄。通過將文件解壓縮到這個目錄,您可以省去一些工作;否則您就必須使用正確的路徑名修改 policy 和 ResourceSecurity.xml 策略文件。
下面是運行該示例需要做的工作:
- 下載這個示例的 源文件。
- 把 jaas.jar 和 jaasmod.jar 復制到 JDK jre\lib\ext 目錄(即 D:\JDK1.3\jre\lib\ext)。
- 向位于 JDK 的 jre\lib\security 目錄(即 D:\JDK1.3\jre\lib\security)中的 java.security 文件的末尾添加下面的字符串:
auth.policy.provider=com.ibm.resource.security.auth.XMLPolicyFile
。
- 執行 run.bat 文件。
結束語
類實例級授權把訪問控制分離到一個通用框架(該框架使用基于所有權和特定關系的策略)中。然后管理員可以在應用程序的生命周期內更改這些策略。用這種方法擴展 JAAS 減少了您或另一個程序員必須在應用程序生命周期內業務規則發生更改時重寫代碼的可能性。
通過將關系字符串抽象為類可以進一步擴展特定關系這個概念。不調用 Resource
實現類的 fulfills(Subject user, String relationship)
方法,而只要調用 Relationship
實現類中定義的新 fulfills(Subject user, Resource resource)
方法。這樣就會允許許多 Resource
實現類使用相同的關系邏輯。