J2EE 中的安全第一部分
現在越來越多的企業應用構建在j2ee平臺上,這得益于j2ee為企業應用的開發提供了良好的框架和服務的支持.j2ee為企業應用提供了多方面的服務
(Security、Transaction、Naming等).本文將介紹j2ee提供的安全服務.作者首先介紹j2ee中的安全概念和j2ee的安全
體系架構.然后結合具體的實例向讀者展示如何在自己的程序中應用j2ee提供的安全特性。
一.簡介
現
在越來越多的企業應用構建在j2ee平臺上,這得益于j2ee為企業應用的開發提供了良好的框架和服務的支持.j2ee為企業應用提供了多方面的服務
(Security、Transaction、Naming等).本文將介紹j2ee提供的安全服務.作者首先介紹j2ee中的安全概念和j2ee的安全
體系架構.然后結合具體的實例向讀者展示如何在自己的程序中應用j2ee提供的安全特性。本文所介紹的內容是基于j2ee1.3版本的。
二.j2ee中的安全概念
主體(Principal):主
體(Principal)是被在企業安全服務驗證了的實體。主體(Principal)用主體名作為它的標識,通過與主體相關的驗證數據進行驗證。通常情
況下主體名就是用戶的登陸名,驗證數據就是登陸的密碼。J2EE規范中并沒有限定J2EE
產品提供商使用怎樣的認證方法,因此主體名和驗證數據的內容和格式依不同的認證協議而不同。
安全策略域(Security Policy Domain):也
稱安全域(security domain)或
realm,它是一個邏輯范圍或區域,在這一范圍或區域中安全服務的管理員定義和實施通用的安全策略。它是從安全策略的角度劃分的區域。比如可以將企業應
用系統劃分為企業員工、供應商、合作伙伴等不同的安全域,對這些安全區域采用不同的安全策略。
安全技術域(Security Technology Domain):它是從安全技術的角度劃分的區域,在一個安全技術域中使用同樣的安全機制來執行安全策略。一個安全技術域可以包括多個安全策略域。
安全屬性(Security Attributes):每
個主體(Principal)都有一系列與之相關的安全屬性。安全屬性可用來訪問被保護的資源,檢查用戶的身份和完成其他一些安全相關的用途。J2EE產
品提供商或具體的驗證服務的實現來決定怎樣將安全屬性與一個主體聯系起來。J2EE規范并沒有限定什么樣的安全屬性將與主體相聯系。
憑證(Credential):憑證包含或引用為J2EE 系統驗證一個主體的驗證信息(安全屬性)。如果成功的通過了驗證,主體將獲得一個包括安全屬性的憑證。如果被允許的話,一個主體也可能獲取另一個主體的憑證。在這種情況下兩個主體在同一安全域中具有相同的安全屬性。
三.j2ee的安全體系結構
1. 基于容器的安全
在j2ee
的環境中,組件的安全是由他們各自的容器來負責的,組件的開發人員幾乎可以不用或者很少在組件中添加有關安全的代碼。這種安全邏輯和業務邏輯相對獨立的架
構,使得企業級應用系統有更好的靈活性和擴展性。J2ee規范要求j2ee
產品必須為應用程序開發者提供兩種形式的基于容器的安全性-說明性的安全性和可編程的安全性。
a. 說明性的安全性
說
明性的安全性通過安全結構描述的方式來代表應用程序的安全需求,安全結構一般包括安全角色,訪問控制和驗證要求等。在j2ee平臺中部署描述符充當了說明
的安全性的主要工具。部署描述符是組件開發者和應用程序部署者或應用程序組裝者之間的交流工具。應用程序的開發者用它來表示應用中的安全需求,應用程序部
署者或應用程序組裝者將安全角色與部署環境中的用戶和組映射起來。
在程序運行時容器從部署描述符中提取出相應的安全策略,然后容器根據安全策略執行安全驗證。說明的安全性不需要開發人員編寫任何安全相關的代碼,一切都是通過配置部署描述符來完成的。
b. 可編程的安全性
可
編程的安全性在說明性的安全性的基礎上,使安全敏感的應用可以通過調用被容器提供的API來對安全作出決斷。這在說明性的安全性不足以滿足企業的安全模型
的情況是非常有用的。J2ee在EJB EjbConext interface和servlet HttpServletRequest
interface中各提供兩個方法:
isCallerInRole (EJBContext) getCallerPrincipal (EJBContext) isUserInRole (HttpServletRequest) getUserPrincipal (HttpServletRequest)
|
這些方法允許組件根據調用者或遠程用戶的安全角色來作出商業判斷。在文章的后面部分將有這些方法的詳細介紹和例程,以便讀者更好的理解可編程的安全性的用途。
2.J2ee的驗證模型
身份驗證是用戶或組件調用者向系統證明其身份的過程。用戶通過某種方式向系統提交驗證信息(通常是用戶名和密碼或者是用戶的數字證書),系統用用戶提供的驗證信息和系統的安全策略來驗證用戶的身份。
圖一 初始驗證過程
圖一 初始驗證過程
圖二 驗證URL
圖三 驗證EJB方法調用
用戶的驗證
用戶的驗證根據其客戶端類型不同分為兩種:Web 客戶端的驗證和Application客戶端的驗證
a. Web 客戶端的驗證
Web
客戶端通常通過http協議來請求web服務器端的資源,這些web資源通常包括html網頁、jsp(java server
page)文件、java
servlet和其他一些二進制或多媒體文件。在企業環境中,企業的某些資源往往要求只允許某些人訪問,有些資源甚至是機密的或安全敏感的。因此對企業中
各種web資源進行訪問控制是十分必要的。為了滿足企業中的不同安全級別和客戶化的需求,j2ee提供了三種基于web客戶端的驗證方式:
HTTP基本驗證(HTTP Basic Authentication)
HTTP基本驗證
是HTTP協議所支持的驗證機制。這種驗證機制使用用戶的用戶名和密碼作為驗證信息。Web客戶端從用戶獲取用戶名和密碼,然后傳遞他們給web服務器,
web服務器在指定的區域(realm)中驗證用戶。但需要注意的是,這種驗證方法是不夠安全的。因為這種驗證方法并不對用戶密碼進行加密,而只是對密碼
進行基本的base64的編碼。而且目標web服務器對用戶來說也是非驗證過的。不能保證用戶訪問到的web服務器就是用戶希望訪問的。可以采用一些安全
措施來克服這個弱點。例如在傳輸層上應用SSL或者在網絡層上使用IPSEC或VPN技術。
基于表單的驗證(Form-Based Authentication)
基于表單的驗證
使系統開發者可以自定義用戶的登陸頁面和報錯頁面。這種驗證方法與基本HTTP的驗證方法的唯一區別就在于它可以根據用戶的要求制定登陸和出錯頁面。基于
表單的驗證方法同樣具有與基本HTTP驗證類似的不安全的弱點。用戶在表單中填寫用戶名和密碼,而后密碼以明文形式在網路中傳遞,如果在網路的某一節點將
此驗證請求截獲,在經過反編碼很容易就可以獲取用戶的密碼。因此在使用基本HTTP的驗證方式和基于表單的驗證方法時,一定確定這兩種方式的弱點對你的應
用是可接受的。
基于客戶端證書的驗證(Client-Certificate Authentication)
基于客戶端證書的驗證方式要比上面兩種方式更安全。它通過HTTPS(HTTP over SSL)來保證驗證的安全性。安全套接層(Secure
Sockets
Layer)為驗證過程提供了數據加密,服務器端認證,信息真實性等方面的安全保證。在此驗證方式中,客戶端必須提供一個公鑰證書,你可以把這個公鑰證書
看作是你的數字護照。公鑰證書也稱數字證書,它是被稱作證書授權機構(CA)-一個被信任的組織頒發的。這個數字證書必須符合X509公鑰體系結構
(PKI)的標準。如果你指定了這種驗證方式,Web服務器將使用客戶端提供的數字證書來驗證用戶的身份。
b. 應用程序客戶端的驗證(Application Client User Authentication)
java客戶端程序是執行在用戶本地java虛擬機上的java程序,它擁有main方法,通常由用戶可通過java.exe或javaw.exe直接啟
動執行。J2ee應用程序客戶端與java客戶端程序相似,也擁有main方法,但他們在運行時存在一定的差別。J2ee應用程序客戶端和其他j2ee組
件一樣運行在自己的容器中。用戶通過容器來執行J2ee應用程序客戶端。這樣J2ee應用程序客戶端容器就有機會在J2ee應用程序客戶端被執行之前完成
用戶身份的驗證。J2ee提供了一種可自定義的方式來獲取用戶的驗證信息。可以選擇使用容器提供的缺省的方式來獲取j2ee應用客戶端程序的用戶的驗證信
息,也可以選擇自定義的方式來獲取用戶的驗證信息。當選擇自定義方式時,應用程序開發者必須提供一個實現了
javax.security.auth.callback.CallbackHandler
interfce的類,并且在j2ee部署描述文件application-client.xml中的元素callback-handler中加入這個類
的類名。這樣,當系統需要驗證用戶身份時,客戶端程序的容器將部署描述文件中的CallbackHandler實現類的類名傳遞給系統的登陸模塊(驗證模
塊),登陸模塊再實例化這個實現類。這個類的實例負責收集用戶驗證信息,并將收集到的用戶驗證信息傳遞給登陸模塊,登陸模塊用這些驗證信息來驗證用戶。這
個實現類可以是具有用戶界面的,或是通過要求用戶輸入來收集用戶驗證信息,也可以是通過命令行來獲取用戶驗證信息,還可能是通過讀取本地或在線的用戶證書
庫來獲取用戶的電子證書。選取哪種方式取決于驗證信息的存儲方式。
有些j2ee產品廠商把容器的驗證服務和本地系統的驗證服務或其他應用系統產品的驗證服務集成起來,從而在一定的應用系統的范圍內實現單點登陸的能力。
單點登陸 (Single Sign-On)
單點登從用戶的視角是指用戶在特定的邏輯安全區域中,只需進行一次登陸即可在訪問在此邏輯安全區域中不同應用系統中的被授權的資源,只有超越了安全區域邊
緣時才要求再次登陸。這種能力對多種IT應用系統共存的企業顯得尤為有價值。隨著企業信息化建設程度的不斷提高,企業中的應用系統也越來越多。在傳統的應
用系統中,各系統各自維護自己的安全策略,這些安全策略典型的包括組織結構定義,安全角色定義,用戶身份驗證,資源訪問控制等。由于各系統互相獨立,一個
用戶在使用每一應用系統之前,都必須按照相應的系統身份進行系統登陸。這對于用戶來說必須記住每一個系統的用戶名和密碼,給用戶帶來了不小的麻煩。針對于
這種情況,單點登陸的概念隨之產生,并不斷的應用到企業的應用系統的集成當中。J2ee1.3也在規范中建議j2ee產品應為應用系統提供單點登陸的能
力。但j2ee1.3規范并沒有規定j2ee產品應遵循何種標準,因此不同的廠商的產品在單點登陸上的實現和應用各不相同。有的j2ee產品實現了在本產
品環境范圍內的單點登陸,有的實現了特定系統環境之間的單點登陸(如IBM WebSphere Application 4.0 AE
實現了WebSphere Application Server與WebSphere Application Server、WebSphere
Application Server與Lotus Domino server、WebSphere Application
Server與Lotus Domino
server之間的單點登陸能力)。在j2ee中單點登陸是通過傳遞憑證(Credential)來實現的.當用戶進行系統登陸時,客戶端容器(包括
WEB客戶端和應用程序客戶端)根據用戶的憑證(Credential)為用戶建立一個安全上下文(security
Context),安全上下文包含用于驗證用戶的安全信息,系統用這個安全上下文和安全策略來判斷用戶是否有訪問系統資源的權限。遺憾的時j2ee規范并
沒有規定安全上下文的格式,因此不能在不同廠商的j2ee產品之間傳遞安全上下文。到目前為止還很少有在不同的j2ee產品間互相共享安全上下文,因此在
不同j2ee產品間實現單點登陸只能通過第三方產品(如LDAP server等)集成的方式。
惰性驗證(Lazy Authentication)
身份驗是有代價的。例如,一次驗證過程也許包括多次通過網絡信息交換。因此惰性驗證就非常有用了。惰性驗證使當用戶訪問受保護的資源時才執行驗證過程,而不是在用戶第一次發起請求時就執行驗證過程。
3. J2ee的授權模型
代碼授權(Code Authorization)
j2ee產品通過java 2 安全模型來限制特定J2SE的類和方法的執行,以保護和確保操作系統的安全。詳細描述請參閱《J2SE規范文檔》。
調用者授權(Caller Authorization)
安全角色:安全角色是具有相同安全屬性的邏輯組。它由應用程序的裝配者(Application Assembler)或應用程序的部署者(Application Deployer)分配的。
安全角色引用:安全角色引用是應用程序提供者(Application Provider)用來引用安全角色的標識。應用程序提供者(Application Provider)可以用安全角色引用來為安全角色分配資源訪問的權限。也在安全相關的程序代碼中引用安全角色。
用戶和組:用戶和組是在實際系統環境下的用戶和用戶的集合。它們對應者現實當中的人和群體。
訪問控制:訪問控制可以確保安全角色只能訪問已授予它安全權限的授權對象。授權對象包括EJB的遠程方法、web資源(html網頁,jsp/servlet和多媒體或二進制文件)等。在j2ee中訪問控制在應用程序描述文件中與安全角色關聯起來。
映射:通過映射應用程序的系統管理員將實際系統環境中的用戶和角色與安全角色聯系起來,從而是實際的用戶擁有對企業資源訪問的適當授權。
被傳播的調用者身份標識(Propagated Caller Identities)
在j2ee
1.3中可以選擇用傳播調用者標識作為web組件和ejb組件調用者的標識來進行驗證。在這種方式下,整個ejb組件的調用鏈中interface
EJBContext的方法getCallerPrincipal返回相同的主體名(principal
name)。如果調用鏈中的第一個ejb是被jsp/servlet調用的,interface
EJBContext的方法getCallerPrincipal返回的主體名(principal name)應與interface
HttpServletRequest的方法getUserPrincipal的返回值相同。要注意的是在調用鏈中傳遞的是用戶的標識,而不是憑證
(credentials),這一點非常重要,因為在調用鏈的每個節點上用戶可能使用不同的安全屬性。
Run As Identities
J2ee 1.3中提供了允許組件開發者和部署這來指定組件以什么身份運行的方法。符合j2ee1.3規范的產品會提供將組件設置成Run As
Identities方式的方法。如果Run As Identities方式被選中,在運行中被設置為Run As
Identities的組件的調用者不再是調用鏈中第一個節點的調用者了,而是在部署時被指定的調用者。而調用鏈中隨后節點的調用者也變為與被設置為
Run As Identities的組件的調用者相同。
圖四 用戶標識傳遞
這一部分介紹了j2ee的安全概念,意在使讀者能夠對j2ee在安全方面有一定的了解,后面還會有應用這些概念的具體例子。
J2EE 中的安全第二部分--j2ee安全應用
在本系列文章的第一部分作者介紹了j2ee的安全概念、驗證模型和授權模型,這部分更偏重于理論的介紹。本文的第二部分作者將通過具體的例子向讀者展示如何在開發中應用j2ee提供的安全服務。本部分的重點在于應用與實踐。
注
釋:本文的目的是介紹如何應用j2ee提供的安全服務,而并不針對特定的產品。因此作者選擇sun的
j2ee參考實現(j2sdkee)作為演示平臺。因為j2sdkee是完全遵照j2ee規范開發的,雖然它不像IBM WebSphere 、BEA
WebLogic等j2ee產品那么產品化和商業化,但它絕對是學習j2ee的理想平臺。你可以通過 http://java.sun.com/j2ee/獲取sun 的j2ee參考實現的最新版本。本文選擇的是Sun的j2sdkee1.3.1。
本文將包括以下內容:
- 一個采用HTTP基本的驗證的例子
- 一個采用基于表單的驗證的例子
- 一個ejb方法授權的例子
- 一個可編程安全性和傳播調用者身份標識的例子
采用HTTP基本的驗證的例子
http基本驗證是Web客戶端驗證的一種,它和系統的授權機制一起控制受保護資源的訪問。
步驟:
1. 創建一個j2ee應用
在應用程序部署工具的File菜單選中New子菜單中的Application菜單項(見圖1)。會彈出新建應用程序對話框。填寫應用程序文件名和應用程序顯示名(見圖2)。
圖1
圖2
2. 創建一個web組件
在
應用程序部署工具的File菜單選中New子菜單中的Web Compent菜單項,會彈出新建web組件向導對話框(見圖3)。選擇Create
New WAR File in Application,在下拉框中選擇步驟1創建的應用test,在WAR Display
Name框中填寫WebAppTest.點擊Content欄中
的Eidt按鈕選擇此Web組件包含的文件。在這個例子中只有一個webtest.jsp文件。然后點擊Next,進入下一個對話框(見圖4)。由于我們
的web組件是一個jsp文件,因此在組件類型中選擇JSP。然后一直按Next直到結束。此時我們已經創建了一個只包含一個jsp文件的web組件。接
下來是配置安全屬性的步驟。
圖3
圖4
3. 配置安全屬性
3.1創建安全角色
在部署工具的左導航欄中點中步驟2創建的web組件WebAppTest,在右邊的屬性頁中選擇Roles屬性頁(見圖5)。點擊Add按鈕,在Name欄中填寫安全角色名user,Description欄填寫描述信息。安全角色代表具有相同安全權限用戶的集合。
圖5
3.2 配置安全策略
創建了安全角色后,應該對安全角色配置相應的安全策略。點擊Security屬性頁(見圖6)。
圖6
首
先選擇你想用的驗證方式,從User Authentication
Method下拉框中選擇Basic。這意味著你將通過基本的HTTP驗證方式驗證用戶。下面我們進行web資源的授權。點擊Security
Constraint欄中的Add按鈕添加一條安全約束,約束名可以自定。接下來對創建好的約束添加Web資源。首先在Web Resource
Collections中添加資源集合,然后選取資源集合包含的資源。此例中WRCollection資源集合中包含webtest.jsp文件,也可以
包含各種屬于這個web組件的文件。接下來選擇哪些web操作要收到約束,j2sdkee1.3.1中只包含兩種操作(GET和POST),不同的產品支
持的操作有所不同,在開發是應結合具體產品提供的操作來選取。現在應該指定安全角色了,點擊Authorized
Roles欄中的Edit按鈕,會彈出安全角色列表對話框,從中選取已定義的安全角色。本例中選擇user。至此安全策略已經配置完畢,下面的步驟是將實
際環境中的用戶和用戶組映射與安全角色進行映射。
4. 映射
在
左導航欄中選中應用程序test在右邊選擇Security屬性頁(見圖7),在Role Name
Reference欄中選中user,點擊正下方的Add按鈕,會彈出用戶和用戶組列表對話框,從中選擇要映射成安全角色user的用戶或組。此例中我們
將用戶j2ee映射為安全角色user。這樣用戶J2ee將具有為安全角色user分配的訪問授權。
圖7
5. 部署應用
選中Web Context屬性頁,在Context Root文本框中填寫test,右鍵點擊左導航欄的應用test,在彈出菜單中選擇deploy完成應用程序的發布。至此我們完成了第一個例子的全部步驟。
部署描述文件
這個例子使用了說明性的安全服務,因此我們不需要編寫任何的安全相關的代碼,而是完全通過配置組件的部署描述文件來實現的。下面是這個web組件的部署描述文件。
<?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> <display-name>WebAppTest</display-name> //Web組件名稱 <servlet> <servlet-name>webtest</servlet-name> <display-name>webtest</display-name> <jsp-file>/webtest.jsp</jsp-file> //組件中包含的jsp文件 </servlet> <session-config> <session-timeout>30</session-timeout> </session-config> <security-constraint> //安全約束部分 <web-resource-collection> //受約束的web資源集 <web-resource-name>WRCollection</web-resource-name> //資源集名 <url-pattern>/webtest.jsp</url-pattern> //資源的url表達式 <http-method>GET</http-method> //受約束的資源操作方法 <http-method>POST</http-method> </web-resource-collection> <auth-constraint> //對安全角色授權 <role-name>user</role-name> //安全角色名 </auth-constraint> <user-data-constraint> <transport-guarantee>NONE</transport-guarantee> </user-data-constraint> </security-constraint> <login-config> //驗證方式設置 <auth-method>BASIC</auth-method> //使用基本的HTTP驗證方式 <realm-name></realm-name> </login-config> <security-role> //定義安全角色 <description>this is a user</description> <role-name>user</role-name> </security-role> </web-app>
|
從部署描述文件可以知道這是
一個名為WebAppTest的web組件,包含一個名為webtest.jsp的文件,只有被賦予user安全角色的用戶或用戶組才有權對
webtest.jsp進行GET或POST操作。這里并沒有包含安全角色對實際用戶的映射,j2ee部署描述文件的DTD中并沒有定義用于安全角色和實
際用戶的映射的元素,因為實際環境中有多種不同的用戶系統(如關系數據庫,系統文件形式和LDAP系統等)。因此安全角色和實際用戶的映射方式是由
j2ee產品廠商制定的。
測試運行結果
打
開ie,在導航欄輸入http://localhost:8000/test/webtest.jsp回車,會彈出驗證對話框,要求用戶提供用戶名和密碼
(見圖8),輸入用戶名j2ee和密碼j2ee。通過用戶驗證后執行jsp文件,webtest.jsp打印出"hello!"(見圖9)。
圖8
圖9
注釋:在第一個例子中已經詳細的描述了各個步驟,在接下來的例子中會有一些與第一個例子相同的操作,因此對下面的例子只描述與第一個例子不同的步驟。
基于表單的驗證的例子
基于表單的驗證與基本的HTTP驗證的唯一區別是基本的HTTP驗證用瀏覽器提供的驗證信息對話框收集用戶驗證信息,而基于表單的驗證允許自定義登陸頁面來收集用戶驗證信息。本例子與第一個例子的步驟基本相同,不同的地方在于此例子要提供登陸頁面和出錯頁面。
登陸頁面login.html
<form method="POST" action="j_security_check"> <input type=text name="j_username"> <input type=password name="j_password"> <input type=submit name="login" value="login"> </form>
|
此文件有幾個地方值得注意:
- Action的值必須為"j_security_check"
- 獲取用戶名的域名必須是"j_username"
- 獲取用戶密碼的域必須是" j_password"
出錯頁面 error.html
出錯頁面只是簡單的顯示出錯信息。
配置基于表單的驗證
首先將login.html和error.html加入到WebAppTest組件中。
然后見圖10選擇Security屬性頁,在User Authentication Method下拉框中選擇Form
Based選項。點擊Settings…彈出用戶驗證設置對話框,在Login Page下拉框選login.html,在Error
Page下拉框選error.html。
圖10
重新部署應用,再一次訪問http://localhost:8000/test/webtest.jsp 會出現login頁面(見圖11),如果用戶名或密碼錯誤,error.html將顯示給用戶。
圖11
部署描述文件
<?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> <display-name>WebAppTest</display-name> . . . <transport-guarantee>NONE</transport-guarantee> </user-data-constraint> </security-constraint> <login-config> <auth-method>FORM</auth-method> //使用基于表單的驗證方式 <realm-name>Default</realm-name> //使用缺省的安全域 <form-login-config> <form-login-page>/login.html</form-login-page> //定義登陸頁面 <form-error-page>/error.html</form-error-page> //定義出錯頁面 </form-login-config> </login-config> <security-role> <description>this is a user</description> <role-name>user</role-name> </security-role> </web-app>
|
ejb方法授權的例子
從j2ee1.3
開始便提供了對ejb的方法進行授權的安全服務,這種授權服務由ejb容器實現。當調用者調用ejb的方法時,ejb容器用調用者的身份來查找授予此調用
者的訪問權限條目,如果調用者調用的方法屬于授權條目,那么ejb容器調用方法。否則,ejb容器拒絕調用此方法,并向調用者返回拒絕訪問異常。可以對遠
程方法和home接口方法進行授權。本例中我們將對一個遠程方法和一個home接口方法進行授權。
首先創建一個session bean CountEjb 遠程接口 Count.java import javax.ejb.*; import java.rmi.RemoteException;
public interface Count extends EJBObject {
/** * 遠程方法count */ public int count() throws RemoteException; }
Home接口 CountHome.java import javax.ejb.*; import java.rmi.RemoteException;
/** * This is the home interface for CountBean. * One create() method is in this Home Interface, which * corresponds to the ejbCreate() method in the CountBean file. */ public interface CountHome extends EJBHome {
/* * This method creates the EJB Object. * * @param val Value to initialize counter to * * @return The newly created EJB Object. */ Count create(int val) throws RemoteException, CreateException; }
實現類 CountBean.java import javax.ejb.*; import java.security.Principal;
/** public class CountBean implements SessionBean { // The current counter is our conversational state. public int val; private SessionContext sessionCtx; // // 遠程商業方法實現 public int count() { System.out.println("count()"); return ++val; }
// // home接口Create方法的實現 //
public void ejbCreate(int val) throws CreateException { this.val = val; System.out.println("ejbCreate()"); }
public void ejbRemove() { System.out.println("ejbRemove()"); }
public void ejbActivate() { System.out.println("ejbActivate()"); }
public void ejbPassivate() { System.out.println("ejbPassivate()"); }
public void setSessionContext(SessionContext ctx) { sessionCtx=ctx; } } 客戶端程序 CountClient.java import javax.ejb.*; import javax.naming.*; import java.util.Properties;
/** * This class is a simple example of client code. */ public class CountClient {
public static void main(String[] args) {
try { InitialContext ctx = new InitialContext(); CountHome home = (CountHome) javax.rmi.PortableRemoteObject.narrow( ctx.lookup("java:comp/env/CountHome"), CountHome.class);
int countVal = 0; Count count=null;
/* 創建并執行遠程方法 */ System.out.println("Instantiating beans..."); count = home.create(countVal); countVal = count.count();
System.out.println(countVal);
/* remove Count對象 */ count.remove(); } catch (Exception e) { System.out.println(e.getMessage()); e.printStackTrace(); } } }
|
這個ejb包括一個遠程商業方法count(),我們將將此方法授權給某個安全角色。此外還將home接口的Create()方法授權給安全角色。
步驟1
編譯以上源程序并用j2sdkee1.3.1的組裝發布工具(deploytool.bat)進行組裝(如圖12)。
圖12
步驟2:配置安全角色
在對方法進行授權前,必須創建將被授權的安全角色。創建安全角色的步驟前邊已經介紹過了,此處不再重復。本例中我們創建名為admin的安全角色。
步驟3:方法授權
方法授權的過程是確定那些安全角色可以訪問特定方法的過程。方法授權一般是應用程序組裝或應用程序部署者的責任。他們根據企業特定的需求創建不同的安全角色,并授予這些安全角色特定的訪問權限。
用
鼠標選中CountBean,在右端的窗口選擇Security屬性頁(如圖13),在Security Identity選項中選擇Use
Caller ID,這意味著ejb容器將用方法調用者的身份來驗證方法調用權限。
Run As Specified
Role選項將在"傳播調用者身份標識的例子"進行介紹。由于在前面創建了admin安全角色,因此你可以看到Method
Permissions欄中出現admin列。首先對遠程方法count()進行授權。選擇Remote選項,并在count()方法的
Availability列中選擇Sel
Roles,然后選中count()方法的admin列。到此為止我們已對遠程方法count()進行了授權。接下來對home接口的create()方
法進行授權。在Show欄中選擇Remote
Home,剩下的步驟與count()方法授權相同。我們已經將count()方法和create()方法授權給了admin安全角色。但安全角色這是一
個邏輯的集合,并不代表具體的用戶或用戶組,因此結下來我們要做的就是將安全角色與實際的用戶映射起來。
步驟4:角色映射
首先我們需要在我們的j2ee環境中創建一個用戶,用戶起名為Tony,密碼為1。
圖13
這
里我們使用用戶名和密碼的方式進行身份驗證。我們在default Realm中創建此用戶。可以使用命令行方式:"realmtool -add
Tony 1
eng"。詳細的使用方法參見j2sdk1.3.1文檔的工具部分。接下來映射安全角色到用戶。選中ejb應用CountEjb,在右邊窗口中選擇
Security屬性頁(如圖14),點擊Edit
Roles按鈕,選擇安全角色admin。再點擊Add按鈕選擇Tony用戶。這樣已經將安全角色admin和用戶Tony映射起來了。
步驟5:部署應用
部署應用到本地機,右鍵點擊ejb應用CountEjb,選擇彈出菜單的deploy項,按要求配置各項。
步驟6:創建客戶端
創建客戶端將客戶端程序的主類和其他輔助類打包。創建端將客的過程比較簡單,這里就不作描述了。
步驟7:運行程序
現在可以運行客戶端程序來驗證方法的授權了。通過命令runclient.bat -client客戶端jar包文件名 -name 主類名來執行客戶端程序。客戶端程序的容器將顯示一個對話框來提示用戶輸入用戶名和密碼(如圖15),填寫用戶名和密碼,按OK。
圖15
若用戶名或密碼與授權的方法不符,則會拋出沒有權限異常(如圖16)。
圖16
Countejb的部署描述文件ejb.xml
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE ejb-jar PUBLIC '-//Sun Microsystems, Inc.//DTD Enterprise JavaBeans 2.0//EN' 'http://java.sun.com/dtd/ejb-jar_2_0.dtd'>
<ejb-jar> <display-name>count</display-name> <enterprise-beans> <session> // CountBean屬于session bean <display-name>CountBean</display-name> //ejb組件的顯示名 <ejb-name>CountBean</ejb-name> //ejb組件名
<home>CountHome</home> //Home接口 <remote>Count</remote> //遠程接口 <ejb-class>CountBean</ejb-class> //實現類 <session-type>Stateful</session-type> // CountBean屬于Stateful Bean <transaction-type>Container</transaction-type> //CountBean事務類型為容器管理的 <security-identity> //安全標識 <description></description> <use-caller-identity></use-caller-identity> //CountBean使用調用者的身份標識 </security-identity> </session> </enterprise-beans> <assembly-descriptor> <security-role> <role-name>admin</role-name> //定義安全角色admin </security-role> <method-permission> //將方法count和remove授權給安全角色admin <role-name>admin</role-name>
<method> //方法定義 <ejb-name>CountBean</ejb-name> <method-intf>Remote</method-intf> <method-name>count</method-name> <method-params /> </method> <method> <ejb-name>CountBean</ejb-name> <method-intf>Home</method-intf> <method-name>remove</method-name> <method-params> <method-param>java.lang.Object</method-param> </method-params> </method> </method-permission> <method-permission> <unchecked /> //不檢查以下方法的授權 <method> <ejb-name>CountBean</ejb-name> <method-intf>Remote</method-intf> <method-name>getHandle</method-name> <method-params /> </method> . . . . <method> <ejb-name>CountBean</ejb-name> <method-intf>Remote</method-intf> <method-name>getEJBHome</method-name> <method-params /> </method> </method-permission> <container-transaction> // CountBean的事務屬性 <method> <ejb-name>CountBean</ejb-name> <method-intf>Remote</method-intf> <method-name>count</method-name> <method-params /> </method> <trans-attribute>Required</trans-attribute> </container-transaction> </assembly-descriptor> </ejb-jar>
|
 |
可編程安全性和傳播調用者身份標識的例子
此例程包括兩個部分,分別演示可編程安全性和調用者身份傳播。
可編程安全性例程
可
編程安全性可應用在web層和EJB層,分別是通過javax.servlet.http.HttpServletRequest接口的
isUserInRole ()、getUserPrincipal
()方法和javax.ejb.EJBContext接口的isCallerInRole ()、getCallerPrincipal
()方法來實現的。
public boolean isUserInRole(java.lang.String role)方法
此方法用來判斷調用者是否屬于某一特定的安全角色,如果屬于返回true,否則返回false。
參數role指定某一安全角色。通過此方法開發者可以在程序代碼中加入自己的安全邏輯判斷,從而增強了J2EE在安全方面的靈活性。
public
java.security.Principal getUserPrincipal()方法
調用此方法可以得到一個java.security.Principal對象,此對象包含了調用者的用戶名,通過Principal.getName()
方法可以得到用戶名。通過調用getUserPrincipal()方法開發者可以得到調用者的用戶名,然后對調用者的用戶名進行特定的邏輯判斷。
public java.security.Principal getCallerPrincipal()方法 和public boolean isCallerInRole(java.lang.String roleName)方法的作用和方法同上。
下面我們通過例程來演示這些方法的用法
程序清單:
webtest.jsp
<%@page contentType="text/html"%> <html> <head><title>JSP Page</title></head> <body>
<%-- <jsp:useBean id="beanInstanceName" scope="session" class="package.class" /> --%> <%-- <jsp:getProperty name="beanInstanceName" property="propertyName" /> --%> Hello! the caller is <%=request.getUserPrincipal().getName()%><br/> <%--得到調用者的用戶名--%> <% if (request.isUserInRole("admin")){%> <%--判斷調用者是否屬于"admin"安全角色--%> the caller is admin Role; <%} %> </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> <display-name>WebApp</display-name> <servlet> <servlet-name>webtest</servlet-name> <display-name>webtest</display-name> <jsp-file>/webtest.jsp</jsp-file> <security-role-ref> <role-name>adminref</role-name> <role-link>admin</role-link> </security-role-ref> <security-role-ref> <role-name>guestref</role-name> <role-link>guest</role-link> </security-role-ref> </servlet> <session-config> <session-timeout>30</session-timeout> </session-config> <welcome-file-list> <welcome-file>webtest.jsp</welcome-file> </welcome-file-list> <security-constraint> <web-resource-collection> <web-resource-name>WRCollection</web-resource-name> <url-pattern>/webtest.jsp</url-pattern> <http-method>GET</http-method> <http-method>POST</http-method> </web-resource-collection> <auth-constraint> <role-name>admin</role-name> <role-name>guest</role-name> </auth-constraint> <user-data-constraint> <transport-guarantee>NONE</transport-guarantee> </user-data-constraint> </security-constraint> <login-config> <auth-method>BASIC</auth-method> <realm-name></realm-name> </login-config> <security-role> <role-name>admin</role-name> </security-role> <security-role> <role-name>guest</role-name> </security-role> </web-app>
|
從web.xml文件的內容可以看出,只有安全角色為"admin"和"guest"的用戶才有權對webtest.jsp文件進行POST和GET操作。
運行結果:
創
建一個web應用,將webtest.jsp作為一個web組件,并按照web.xml的內容配置web應用,在運行環境中將用戶j2ee分配為
admin安全角色,將用戶Tony分配為guest角色。發布web應用到本地j2ee Server上。用ie訪問webtest.jsp
如圖17用用戶j2ee的身份進行驗證,用戶j2ee屬于admin安全角色。顯示結果見圖18
圖17
圖18
如果用用戶Tony進行驗證,結果見圖19
圖19
ejb中應用可編程的安全性與在web中的方法相似,本文不再進行介紹
傳播調用者身份標識例程
本例程將演示調用者身份標識如何在調用鏈中傳遞的,并且介紹如何應用"Run As"來實現在調用鏈中更改調用者的身份。本例將用一個web組件(一個jsp文件)和兩個ejb組件來形成一個調用鏈。
程序清單:
webtest.jsp
<%@page contentType="text/html"%> <%@page import="andy.*"%> <%@page import="javax.naming.*"%> <html> <head><title>JSP Page</title></head> <body>
<%-- <jsp:useBean id="beanInstanceName" scope="session" class="package.class" /> --%> <%-- <jsp:getProperty name="beanInstanceName" property="propertyName" /> --%> Hello! the caller is <%=request.getUserPrincipal().getName()%> <br/> <% if (request.isUserInRole("admin")){%> the caller is admin Role; <%} %> <% try { Context ctx = new InitialContext(); andy.CountHome home = (andy.CountHome)javax.rmi.PortableRemoteObject.narrow( ctx.lookup("java:comp/env/CountHome"), andy.CountHome.class); andy.Count count = home.create(1); count.count(); }catch (Exception e) { e.printStackTrace(); } %> </body> </html>
|
CountBean.java
package andy; import javax.ejb.*; import javax.naming.*; public class CountBean implements SessionBean { public int val; private SessionContext EjbCxt = null; public int count() { int temp = 0; System.out.println("CountBean.count()"); //打印調用者名 System.out.println("the caller is "+EjbCxt.getCallerPrincipal().getName()); //判斷調用者的安全角色 if(EjbCxt.isCallerInRole("adminref")) // adminref為安全角色admin的引用名 { System.out.println("the caller is admin Role"); } if(EjbCxt.isCallerInRole("guestref")) // guestref為安全角色guest的引用名 { System.out.println("the caller is guest Role"); } if(EjbCxt.isCallerInRole("userref")) // userref為安全角色user的引用名 { System.out.println("the caller is user Role"); } //調用另一個ejb的遠程方法 try { Context ctx = new InitialContext(); CountHome1 home = (CountHome1)javax.rmi.PortableRemoteObject.narrow( ctx.lookup("java:comp/env/CountHome1"), CountHome1.class); Count1 count = home.create(1); temp = count.count(); }catch (Exception e) { e.printStackTrace(); } return ++temp; } public void ejbCreate(int val) throws CreateException { this.val = val; } public void ejbRemove() { } public void ejbActivate() { } public void ejbPassivate() { } public void setSessionContext(SessionContext ctx) { EjbCxt = ctx; //獲取EjbContext對象 } }
CountBean1.java package andy; import javax.ejb.*;
public class CountBean1 implements SessionBean { public int val; private SessionContext EjbCxt = null; public int count() { System.out.println("CountBean1.count()"); System.out.println("the caller is "+EjbCxt.getCallerPrincipal().getName()); if(EjbCxt.isCallerInRole("adminref")) { System.out.println("the caller is admin Role"); } if(EjbCxt.isCallerInRole("guestref")) { System.out.println("the caller is guest Role"); } if(EjbCxt.isCallerInRole("userref")) { System.out.println("the caller is user Role"); } return ++val; } public void ejbCreate(int val) throws CreateException { this.val = val; } public void ejbRemove() { } public void ejbActivate() { } public void ejbPassivate() { } public void setSessionContext(SessionContext ctx) { EjbCxt = ctx; } }
|
以上的三個文件分別是一個
web組件和兩個ejb組件的源代碼,這三個組件構成了一個調用鏈.webtest.jsp中,首先通過HttpServletRequest..
getUserPrincipal
()方法來得到調用webtest.jsp的用戶的Principal對象,在通過Principal.getName()方法得到調用者的用戶名.
然后通過HttpServletRequest..isUserInRole()方法來判斷調用這是否屬于特定的安全角色.CountBean是一個
stateful
SessoinBean,它擁有一個count()遠程方法,在這個遠程方法中寫了用于得到調用者用戶名和判斷調用這安全角色的代碼,還包括調用
CountBean1對象的代碼,用于展示調用者身份標識是如何在調用鏈中傳遞和調用者的安全角色如何被改變的.CountBean1也是一個
stateful SessoinBean,它的代碼內容與CountBean基本相同,只不過它不包含調用其他Bean的代碼.
現
在我們應該配置各組件的安全屬性了.我們在組件webtest中創建安全角色admin,引用名為adminref,在組件CountBean和
CountBean1中分別創建安全角色admin和user,
引用名分別為adminref和userref.將webtest組件配置為"HTTP Basic
Authentication",給安全角色admin賦予訪問webtest.jsp的權限.把CountBean設置為Run As
Specified
Role,選擇user安全角色.在運行環境中將用戶j2ee賦予admin安全角色和user安全角色.然后發布應用到服務器.用用戶j2ee訪問
webtest.jsp.
執行結果:
客戶端見圖20
圖20
服務器端:
CountBean.count()
the caller is j2ee
the caller is admin Role
CountBean1.count()
the caller is j2ee
the caller is user Role
從
運行結果看,訪問webtest.jsp的用戶為j2ee,其安全角色為admin,訪問CountBean的用戶名為j2ee,安全角色為admin.
可以看到用戶身份標識從web容器傳遞到了ejb容器.再看組件CountBean1的輸出結果,由于CountBean被設置成了Run As
Specified
Role,因此在CountBean向下調用其他組件對象時,用戶安全角色已經被改為指定的安全角色,這里是user.所以我們會看到,調用組件
CountBean1的用戶名為j2ee,安全角色為user.j2ee的這種安全特性滿足了同一用戶在不同應用中具有不同安全角色的需求.開發人員也可
以利用這種特性進行靈活的安全邏輯判斷.