日韩黑丝制服一区视频播放|日韩欧美人妻丝袜视频在线观看|九九影院一级蜜桃|亚洲中文在线导航|青草草视频在线观看|婷婷五月色伊人网站|日本一区二区在线|国产AV一二三四区毛片|正在播放久草视频|亚洲色图精品一区

分享

定制自己的WebLogic LDAP Authentication Provider

 smoking_boy 2005-09-13
目錄

1 J2EE Security 和 LDAP Security
2 JAAS和WebLogic Security Framework
3 了解WebLogic LDAP Authentication Provider
4 定制自己的Custom LDAP Authentication Provider
5 部署中的注意事項
6 結束語
7 參考資料

  從WebLogic Server 7.0開始,WebLogic Server的安全機制有了全面的改變,實現(xiàn)了一個更加規(guī)范的基于JAAS的Security Framework,以及提供了一系列設計良好的Security Service Provider Interface。這樣我們可以根據(jù)自己的具體需求,通過Custom Security Authentication Provider來實現(xiàn)安全上的定制功能。

  本文將以WebLogic(WebLogic Server 8.1) Security和 LDAP為基礎,介紹Custom LDAP Authentication Provider如何給我們帶來更多的靈活性,和系統(tǒng)安全設計上更多的空間;以及討論如何實現(xiàn)一個Custom LDAP Authentication Provider和部署過程中的一些良好經(jīng)驗。

  由于本文涉及到的范圍太廣,不可能一一詳細討論;為了使沒有相關基礎的讀者也能夠閱讀理解本文,因此我將在文章前半部分,試圖通過最簡潔扼要的描述,來使大家對于J2EE Security,WebLogic Security Framework以及LDAP 等有一個初步的清晰認識;進而可以開發(fā)出自己的LDAP Authentication Provider。因此很多地方做了比較有限的描述或者介紹,更多詳細的內容可以參考文后附帶的參考資料或者文中給出的鏈接。

1 J2EE Security 和 LDAP Security
  Sun J2EE推出以來,其安全部分的規(guī)范就一直倍受關注。我們最常見到安全規(guī)范的兩個方面分別是Servlet Security 和 EJB Security。目前絕大多數(shù)的Servlet容器,J2EE容器都能很好的支持這些安全規(guī)范。

  WebLogic Server作為業(yè)界領先的J2EE服務器對J2EE Security的支持是非常優(yōu)秀的。我們這里將結合WebLogic Security和使用越來越廣泛的LDAP做一個簡要的介紹,這些是設計開發(fā)Custom LDAP Authentication Provider的技術基礎。

1.1 Authentication 和Authorization
  這里需要大家先明確安全上的兩個重要名詞:一個是認證(Authentication),一個是授權(Authorization)。認證是回答這個人是誰的問題,即完成用戶名和密碼的匹配校驗;授權是回答這個人能做什么的問題。我們討論的J2EE Security包括Declarative Authorization和Programmatic Authorization,即一個是通過web.xml,ejb-jar.xml等部署描述符中的安全聲明通過容器提供的服務來完成權限控制的;一個是通過HttpServletRequest.isUserInRole()和EJBContext.isCallerInRole()這樣的編程接口在應用中自己完成權限控制的。

1.2 資源(Resource)和Security Role
  
資源原本只包括 Web Resource和EJB Resource,但在WebLogic Security中擴展到幾乎任何一個WebLogic Platform中的資源,具體可以參考http://e-docs./wls/docs81/secwlres/types.html#1213777。授權就是針對資源的訪問控制。

  J2EE Security是基于Security Role的。我們可以將一組資源與一個Security Role進行關聯(lián)來達到控制的目的——只有擁有該Role權限的用戶才能夠訪問這些資源。簡單的說,我們可以通過給用戶分配不同的Security Role來完成權限的控制。復雜的情況下包括用戶/用戶組,以及Principal和Role的映射關系等等。下面是一個聲明性安全在web application(war包中WEB-INF/web.xml)中的示例:

<web-app>
 <security-constraint>
 <web-resource-collection>
  <web-resource-name>Success</web-resource-name>
  <url-pattern>/welcome.jsp</url-pattern>
   <http-method>GET</http-method>
   <http-method>POST</http-method>
 </web-resource-collection>
 <auth-constraint>
  <role-name>webuser</role-name>
 </auth-constraint>
 </security-constraint>
 <login-config>
  <auth-method>BASIC</auth-method>
  <realm-name>default</realm-name>
 </login-config>
 <security-role>
  <role-name>webuser</role-name>
 </security-role>
</web-app>

  只有擁有角色webuser的用戶才能夠訪問welcome.jsp頁面,否則容器會返回401無權訪問的錯誤。更多信息請參考http://e-docs./wls/docs81/security/index.html。

  同時我們需要在weblogic.xml(war包中WEB-INF/weblogic.xml)中對security role和principal進行映射關系的配置:

<weblogic-web-app>
 <security-role-assignment>
  <role-name>PayrollAdmin</role-name>
  <principal-name>Tanya</principal-name>
 </security-role-assignment>
</weblogic-web-app>

  這樣擁有Principal “Tanya”的用戶(Principal將封裝到Subject中,用戶將和Subject關聯(lián))將會擁有PayrollAdmin的權限。

  注意:一般情況下為了簡化設計,本文中將假設security role即是principal name(如果不配置security-role-assignment,WebLogic會默認做此假設)。即上例中Principal-name也為PayrollAdmin。

1.3 LDAP Security
  
LDAP是輕量級目錄服務(Lightweight Directory Access Protocol)。越來越多的應用開始采用LDAP作為后端用戶存儲。在安全上,LDAP Security是基于ACL(Access Control List)的,它通過給一個用戶組分配LDAP 操作資源(比如對一個子樹的查詢,修改等)來最終完成權限的控制。因此在LDAP中,授權工作是以用戶組為單位進行的。一個用戶組一般來說是擁有如下一組屬性的LDAP Entry:


圖1-3-1

  其中objectclass可以為groupOfUniqueNames或者groupOfNames,它們對應的組成員屬性分別是uniquemember和member。如果是動態(tài)組,objectclass為groupOfURLs。動態(tài)組一般應用在成員可以通過某種業(yè)務邏輯運算來決定的情況下。比如,經(jīng)理為ZHANGSAN的全部員工。下面是一個典型的動態(tài)組,memberURL屬性定義了哪些entry屬于該組:


圖1-3-2

  從圖1-3-1中我們可以看出,用戶WANTXIAOMING,ZHANGSAN,LISI屬于組HR Managers。這種組和成員的關系是通過屬性uniquemember來決定的。同時LADP Group 支持嵌套,即一個組可以是另外一個組的成員,比如我們將Accounting Managers組分配給HR Managers組作為其成員:


圖1-3-3

  這樣將表示Accounting Managers中的成員,同時也是組HR Managers的成員。通過這種層級關系可以使權限分配變的更加靈活。

  下面是一些名詞的解釋,希望大家對LDAP有更好的理解:
  a) Objectclass —— LDAP對象類,抽象上的概念類似與一般我們理解的class。根據(jù)不同的objectclass,我們可以判斷這個entry是否屬于某一個類型。比如我們需要找出LDAP中的全部用戶:(objectclass=person)再比如我們需要查詢全部的LDAP組:(objectclass=groupOfUniqueNames)

  b) Entry —— entry可以被稱為條目,或者節(jié)點,是LDAP中一個基本的存儲單元;可以被看作是一個DN和一組屬性的集合。 屬性可以定義為多值或者單值。

  c) DN —— Distinguished Name,LDAP中entry的唯一辨別名,一般有如下的形式:uid=ZHANGSAN, ou=staff, ou=people, o=examples。LDAP中的entry只有DN是由LDAP Server來保證唯一的。

  d) LDAP Search filter ——使用filter對LDAP進行搜索。 Filter一般由 (attribute=value) 這樣的單元組成,比如:(&(uid=ZHANGSAN)(objectclass=person)) 表示搜索用戶中,uid為ZHANGSAN的LDAP Entry.再比如:(&(|(uid= ZHANGSAN)(uid=LISI))(objectclass=person)),表示搜索uid為ZHANGSAN, 或者LISI的用戶;也可以使用*來表示任意一個值, 比如(uid=ZHANG*SAN),搜索uid值以 ZHANG開頭SAN結尾的Entry。更進一步,根據(jù)不同的LDAP屬性匹配規(guī)則,可以有如下的Filter: (&(createtimestamp>=20050301000000)(createtimestamp<=20050302000000)),表示搜索創(chuàng)建時間在20050301000000和20050302000000之間的entry。
  Filter中 “&” 表示“與”;“!”表示“非”;“|”表示“或”。根據(jù)不同的匹配規(guī)則,我們可以使用“=”,“~=”,“>=”以及“<=”,更多關于LDAP Filter讀者可以參考LDAP相關協(xié)議:http://www./rfc/rfc2254.txt。

  e) Base DN —— 執(zhí)行LDAP Search時一般要指定basedn,由于LDAP是樹狀數(shù)據(jù)結構,指定basedn后,搜索將從BaseDN開始,我們可以指定Search Scope為:只搜索basedn(base),basedn直接下級(one level),和basedn全部下級(sub tree level)。

  下面是一個典型的LDAP Tree結構,右側顯示Entry uid=ZHANGSAN, ou=staff, ou=people, o=examples的屬性,該entry代表了一個名字叫張三的用戶:


圖1-3-4

2 JAAS和WebLogic Security Framework
  現(xiàn)在越來越多的人開始了解JAAS,使用JAAS。WebLogic Security Framework就是基于JAAS的。因此我們需要對此有一個非常準確的理解才能夠設計開發(fā)Custom Authentication Provider。

  下面我們從幾個名詞入手,了解JAAS和 WebLogic Security Framework的關鍵之處:

2.1 Principal,Subject和LoginModule
  
a) Principal
  當用戶成功驗證后,系統(tǒng)將會生成與該用戶關聯(lián)的各種Principal。我們這里將Principal進行簡化的設計,認為一個Principal就是用戶登錄賬號和它所屬于的組(LDAP Group)。這樣當用戶登錄成功后,我們將會在LDAP中執(zhí)行搜索,找出用戶屬于哪些組,并將這些組的名字,或者其標識作為Principal返回。這樣,當用戶在LDAP中屬于某一個組,并且這個組的名字對應到 web.xml (或者ejb-jar.xml)中的Security role,那么這個用戶就可以看作擁有訪問這個Security Role定義的資源的權限。
  在WebLogic Security Framework中,這個LDAP Group的名字(Principal)和Security Role的映射關系,可以通過一個 Role Mapping Provider來實現(xiàn)動態(tài)的匹配,即用戶的動態(tài)權限控制。比如在運行時根據(jù)某一個業(yè)務邏輯來決定用戶是否擁有某一個權限。關于Role Mapping Provider,讀者可以參考下面鏈接的內容:http://e-docs./wls/docs81/dvspisec/rm.html#1145542。

  b) Subject
  JAAS規(guī)定由Subject封裝用戶以及用戶認證信息,其中包括Principals。下面是WebLogic Security Framework中Subject的組成圖示:


圖2-1-1

  這樣當用戶試圖訪問一個受限的J2EE資源時,比如一個web URL,或者一個 EJB Method(可以在web.xml或者ejb-jar.xml中定義,由Security Role控制),WebLogic Security Framework將會通過 Authorization Provider檢查用戶當前的Subject中是否包含有是否可以訪問受限資源的Principals。由于Principals將和J2EE Security Role在weblogic.xml中定義一個映射關系(或者通過其他業(yè)務邏輯來確定這種關系),因此通過這樣的關系,可以最終知道用戶是否有某一個J2EE Resource的訪問權限。

  c) LoginModule
  JAAS LoginModule是一個Authentication Provider必須的組成部分。LoginModule是認證的核心引擎,它負責對用戶身份進行驗證,同時將返回與用戶關聯(lián)的Principals(用戶登錄賬號,以及LDAP Groups),然后放入Subject中,供后續(xù)的訪問控制使用。
  我們將在LoginModules中完成LDAP的相關認證,查詢操作,將用戶在LDAP中所屬于的組搜索出來,作為認證后的結果封裝到Subject中返回。

2.2 WebLogic Authentication認證過程
  下面我們了解一下WebLogic的認證過程。以下圖片來自http://e-docs./wls/docs81/dvspisec/atn.html 我將其中主要部分進行說明。


圖2-2-1

  Security Framework在WebLogic Server啟動時初始化Authentication Provider(5)。當有認證請求進入時,Security Framework首先將通過AuthenticationProvider.getLoginModuleConfiguration()來獲取一個AppConfigurationEntry對象。通過AppConfigurationEntry(詳見http://java./security/jaas/apidoc/javax/security/auth/login/AppConfigurationEntry.html )可以初始化一個LoginModule。初始化LoginModule的方法為:public void initialize(Subject subject, CallbackHandler callbackHandler, Map sharedState, Map options),可以看到里面有Subject, CallbackHandler等等重要參數(shù)。
  被實例化的JAAS LoginModule將完成用戶的一系列驗證任務。驗證完成后,在(6a)中principals將被Principal Validation Provider簽名,在( 6b)中存放到與用戶關聯(lián)的Subject里面。Security Framework最后將拿著該用戶的Subject去完成其他權限控制等任務。

3 了解WebLogic LDAP Authentication Provider
  現(xiàn)在我們有信心了解一下WebLogic LDAP Authentication Provider的工作原理了。這里將以WebLogic提供的iPlanet Authentication Provider的配置為例進行說明。在這里也需要明確說明一下,為了方便進行描述,我們將實際屬于LoginModule的行為也一并歸結到Provider中。沒有單獨將兩個的行為分開,目的是為了突出整個完整的過程。

3.1 iPlanet Authentication Provider配置


圖3-1-1

  從上圖可以看出我們需要指定LDAP服務器的地址,端口,連接LDAP使用的Principal(不同于前面討論的Principal,這個Principal實際是一個連接LDAP的用戶,也就是一個LDAP 中用戶Entry的 DN,它必須要有相關的LDAP 搜索等權限)和Credential(一般來說就是口令)。
  再看下面關于Users的配置:


圖3-1-2

3.1.1 User Object Class —— 前面已經(jīng)對objectclass進行過說明,指明LDAP Entry屬于哪一類
3.1.2 User Name Attribute —— 用戶登錄賬號在LDAP Entry中的屬性,一般為UID或者cn
3.1.3 User Base DN —— 所有的用戶將會放置到這個子樹下面,因此在Provider中對用戶進行的搜索將會從這個basedn開始
3.1.4 User Search Scope —— 指定搜索范圍為Basedn的直接一級或者全部下級
3.1.5 User From Name Filter —— 使用這個filter可以搜索出用戶信息。其中%u將會被用戶輸入的登錄賬號替換,從而查詢中LDAP中的用戶信息


圖3-1-3

3.1.6 Group Base DN —— 從該Base DN開始搜索用戶組
3.1.7 Group From Name Filter —— %g將會被組的名字替換,通過該filter可以搜索出符合條件的LDAP Group
3.1.8 Static group name attribute —— 組名字的屬性,屬性cn對應的值就是組的名字


圖3-1-4

3.1.9 Static Member DN Attribute —— 靜態(tài)成員屬性,通過該屬性可以判斷一個Entry是否屬于一個組
3.1.10 Static Group DNs From Member DN Filter —— 通過該filter可以找出用戶屬于哪些組
3.1.11 Dynamic Group —— 動態(tài)組是在運行時根據(jù)某種業(yè)務邏輯,來決定成員隸屬關系的LDAP Group

3.2 iPlanet Authentication Provider的工作原理
  從上面配置的介紹中可以看出,后端存儲為LDAP的情況下,在Console中我們需要配置的參數(shù)已經(jīng)清楚的表明它所要完成工作的內容。
  首先,它使用配置的User BaseDN和Filter,來根據(jù)用戶輸入的登錄賬號進行搜索,找出存放在LDAP中的用戶Entry。如果找到一個用戶,那么Provider就使用該用戶的DN和用戶輸入的口令,進行驗證。驗證可以使用LDAP Bind和LDAP Compare,這需要根據(jù)不同LDAP的特點來進行選擇。

  A. LDAP Bind —— 將當前的LDAP Connection綁定到一個用戶身份上。這樣后續(xù)的使用該Connection的LDAP Operation都將以該身份進行。LDAP Bind需要兩個重要參數(shù),一個是用戶Entry的DN,一個是該用戶的口令。

  B. LDAP Compare —— LDAP Compare是一個為兼容X.500的古老操作,它用于檢查一個屬性值是否包含在指定Entry中的屬性里。這樣我們可以在知道用戶password存放在哪個屬性的前提下,對該屬性進行compare。如果成功,表明口令正確。如果屬性值為散列后的口令(絕大多數(shù)情況),有的LDAP Server支持這樣的驗證,有的不支持,比如iPlanet LDAP Server 5。

  驗證成功后,Provider將使用Console中關于Groups和Memberships中的配置,查找用戶屬于哪些LDAP Group,而且由于這些組本身可能會有一些嵌套,因此對于搜索到的組還需要進行查詢。即使用filter: (&(uniquemember=uid=ZHANGSAN,ou=staff,ou=people,o=examples)(objectclass=groupOfUniqueNames))從Group Base DN開始搜索,將返回用戶所屬的第一層次Group;然后對于這些返回的組DN,仍然需要使用上面的Filter進行搜索(uniquemember值替換為組的DN),找出嵌套關系,直到查詢完成沒有組嵌套為止(此處需要防止陷入嵌套的循環(huán)中,比如Group A 包含了Group B; Group B又包含了Group A,有的LDAP Server可以自動檢測出,有的需要我們程序來判斷)。

  然后將用戶登錄的賬號,用戶所屬組的名字(屬性CN的值或其他),放入Subject中。最后調用Principal Validator Provider,對Subject中的principals進行簽名,來表明該Subject是這個Provider生成的。這樣防止其他攻擊者偽造Subject以及Principal進行欺騙。此處也可以解釋為何在不同的WLS Domain間不能夠傳遞Subject,我們可以通過設置域信任來完成這種Subject的傳遞。設置域信任使用的Credential就是簽名用的Key。

  圖3-1-5表明了如何設置WebLogic Domain Credential,默認情況下WebLogic Server會在啟動的時候隨即生成一個Credentials(在WLS6.1時,這個值就是system用戶的口令):

  可以想見如果我們實現(xiàn)自己的Principal Validator Provider,讓它去一個集中的驗證服務器中對Subject進行簽名,或者驗證Subject,這樣就可以實現(xiàn)域信任,進而完成Application(EJB Tier)層的SSO。

  通過以上的討論,我們對于實現(xiàn)自己的LDAP Authentication Provider是不是又增加了一份信心?

4 定制自己的Custom LDAP Authentication Provider

  為何要定制自己的Authentication Provider? 由于WebLogic Server已經(jīng)提供了很多默認的Authentication Provider在一般情況下我們確實沒有必要實現(xiàn)自己的Provider。但是面對某些針對安全方面的復雜需求時,WebLogic Server提供的Provider很有可能不滿足這些需求,此時就需要我們定制自己的Provider。

  在這一章的開頭部分中我需要簡要討論關于WebLogic MBean Types,以及WebLogic Console擴展等內容,目的在于讓讀者了解到我們通過WebLogic Console可以完成對Custom Security Provider的配置和部署,我將以WebLogic 提供的Sample Security Provider為示例進行說明。詳細的信息可以參考以下的一些資源:

http://e-docs./wls/docs81/dvspisec/atn.html#1106272
http://e-docs./wls/docs81/dvspisec/atn.html#1106241

  上面兩個鏈接描述了如何創(chuàng)建MBean Types以及在控制臺上配置Custom Authentication Provider.下面這個鏈接中專門介紹了WebLogic Console的擴展:

http://dev2dev./techdoc/webser/2005012102.html

  讀者可以從http://dev2dev./codelibrary/code/security_prov81.jsp下載WLS提供的Sample Security Provider。

4.1 MBean Types和WebLogic Console
  一般情況下,人們可能更習慣通過WebLogic Console對Security Provider進行配置。這里我將簡要描述這個過程,以及可以到達的一個效果。限于篇幅就不詳細討論了。

  從weblogic.management.security.authentication.Authenticator擴展MBean Types。 MBean Types是MBean(http://java./products/JavaManagement/wp/)的工廠,我們擴展SampleSecurityProviders81 包中的SimpleSampleAuthenticator.xml(MBean Definition File),增加一個我們自定義的參數(shù)LDAP Server IP:

<MBeanAttribute
  Name = "LDAPServerIP"
  Type = "java.lang.String"
  Writeable = "true"
  Default = ""127.0.0.1""
/>

  這樣在Provider中我們將通過MbeanMaker(WLS提供)生成的SimpleSampleAuthenticatorMBean中取到這個屬性:SimpleSampleAuthenticatorMBean.getLDAPServerIP()。MBean將在初始化Provider的時候作為參數(shù)傳入。這樣我們就可以通過MBean中的參數(shù)控制Provider的行為。

  當然這個參數(shù)是可以在WebLogic Console中設置的,通過對MBean Types的擴展,在WebLogic Console上看到的畫面如下:


圖4-1-1

  這樣我們可以通過Console修改配置參數(shù)(修改的Security Provider參數(shù)將保存在config.xml中,默認的值將保存在MBean Jar File中)。

4.2 為何定制LDAP Authentication Provider
  
當我們面臨越來越復雜的安全方面的業(yè)務需求時,或者面臨較高的性能要求,需要根據(jù)目標LDAP做針對性的優(yōu)化時,或者需要將我們已有的認證,或授權模塊集成到WebLogic平臺時,WebLogic提供的現(xiàn)成的Provider往往不能滿足我們的需求。

4.2.1 復雜的業(yè)務需求
  當系統(tǒng)要求用戶不僅僅輸入用戶名(j_username),口令(j_password),還需要輸入其他信息,比如登錄的地點,系統(tǒng)的名字,用戶的類型等等。如果是采用基于J2EE Form的驗證方式, 登錄信息需要提交到j_security_check(Servlet規(guī)范定義由容器負責實現(xiàn)的Servlet),導致我們沒法處理更多的信息。

  這個時候,如果能夠實現(xiàn)我們自己的 Authentication Provider,那么我們就可以通過TextInputCallback來獲取登錄表單中更多的信息了;進而通過這些信息在Provider中完成符合我們需要的處理。

  比如搜狐的登錄頁面上需要選擇用戶的類型:


圖4-2-1

4.2.2 性能需求或者調優(yōu)
  有時,有的用戶會比較困惑為何WebLogic LDAP Security Provider在壓力測試中的表現(xiàn)不很理想,用戶需要較長時間的等待,才能夠登錄到系統(tǒng)中。由于這些Provider是 WebLogic產(chǎn)品的一部分,因此缺乏對不同目標LDAP Server的有針對性的優(yōu)化。這樣就使得我們無法充分發(fā)揮具體LDAP Server的性能調優(yōu)。

  比如,有的LDAP Server支持動態(tài)組(LDAP Dynamic Group,成員關系是運行時根據(jù)ldap server,basedn,filter等動態(tài)決定的),可以使用如下的Filter查詢用戶屬于哪些動態(tài)組:

  (uniquemember=uid=MAXQ,ou=staff,ou=people,o=examples)

  有的LDAP Server雖然支持動態(tài)組,但是支持的有限,不能使用上面的Filter獲取用戶屬于哪些動態(tài)組。在WebLogic iPlanet Authentication Provider的實現(xiàn)中,它先是搜索出全部的動態(tài)組,然后再遍歷這些動態(tài)組,依次去LDAP中檢查用戶是否屬于一個組;很明顯,這樣雖然最大程度的滿足了不同LDAP Server的要求(從產(chǎn)品的角度講可能是必須的),但是與LDAP交互的次數(shù)大增,并發(fā)用戶量一大性能下降的比較明顯。

  此時,如果系統(tǒng)中的LDAP支持上面的Filter或者有更好的搜索方式,那么完全可以通過定制Provider完成對性能的優(yōu)化。

4.2.3 已有權限控制的集成
  如果系統(tǒng)中已經(jīng)存在了現(xiàn)成的滿足需求的認證模塊,并且已經(jīng)很好的工作;在系統(tǒng)轉向J2EE架構,并使用WebLogic Server做J2EE容器時,我們可能會更愿意直接在Provider中加入這個認證模塊。

  綜上,我只是列舉了一些可以驅動我們開發(fā)自己Provider的需求,相信在讀者實際工作中可能會面臨更復雜的情況,開發(fā)自己的Provider將是一個非常好的選擇。

4.3 LDAP Authentication Provider實現(xiàn)
  本文之前為了表述的方便沒有單獨提到LoginModule,認為LoginModule的行為就是LDAP Authentication Provider的行為。到了目前的具體實現(xiàn)階段,我們必須分開Authentication Provider和JAAS LoginModule。最終部署到WebLogic上的實際只是LDAP AuthenticationProvider Implements。

  WebLogic SecurityFramework通過Authentication Provider獲取具體的JAAS LoginModule。通過LoginModule完成最終登錄的工作。因此我們必須先實現(xiàn)一個AuthenticationProvider。

  我們一般通過weblogic.security.spi.AuthenticationProvider 來實現(xiàn)自己的AuthenticationProvider。這里介紹其中的幾個重要方法:

  a) public void initialize(ProviderMBean mbean, SecurityServices services)
初始化一個Provider。通過參數(shù)MBean我們可以獲取到在WebLogic Console中配置的各項參數(shù)。進而初始化我們的Provider,然后通過Provider傳遞到LoginModule中。

  b) public void shutdown()
釋放一些與Provider,LoginModule等相關的資源。

  c) public AppConfigurationEntry getLoginModuleConfiguration()
這個方法非常重要,通過該方法,WebLogic Security Framework可以獲取用于初始化LoginModule的AppConfigurationEntry。AppConfigurationEntry中存放了LoginModule的類名等信息,比如使用如下代碼返回一個AppConfigurationEntry:

  return new AppConfigurationEntry(
    "examples.security.providers.authentication.SampleLoginModuleImpl",
    controlFlag,
  options);

  其中LoginModule Name就

是"examples.security.providers.authentication.SampleLoginModuleImpl",我們通過它就可以實例化一個LoginModule并通過LoginModule.initialize()方法進行初始化。

  d) public AppConfigurationEntry getAssertionModuleConfiguration()
該方法將返回一個與Identity Assertion Provider關聯(lián)的LoginModule。這個Assertion LoginModule,將只會驗證用戶是否存在,以及如果存在返回用戶的Principals。 該方法也比較重要,需要正確實現(xiàn),比如我們使用CLIENT-CERT這種WEB認證方式,該方法就會被調用。

  Provider的實現(xiàn)比較簡單,讀者可以在http://dev2dev./codelibrary/code/security_prov81.jsp下載WebLogic提供的Samples,查看SampleAuthenticationProviderImpl的代碼。

4.4 LDAP LoginModule 邏輯流程
  實現(xiàn)了Provider后,必須擁有我們自己的LDAP LoginModule。下面是一個簡單的用于演示的驗證邏輯流程圖。實際的一個LoginModule由于不同的業(yè)務需求,情況可能會復雜得多。這里只是描述了最核心最基本的邏輯,使讀者能有一個清晰的思路。后面我將以這個流程為例進行實現(xiàn)。

4.5 LoginModule代碼示例和講解
  這里我將使用Netscape LDAP SDK for java作為開發(fā)工具實現(xiàn)LDAP相關的操作,讀者可以到http://docs./db/doc/816-6402-10 下載開發(fā)手冊,從http://www.mozilla.org/directory/ 下載SDK 包。一般來說還可以通過JNDI來操作LDAP,我個人認為Sun LDAP JNDI Provider中關于Connection Pool的實現(xiàn)非常優(yōu)秀。但不管使用哪種SDK,對LDAP的編程原理上基本都是相同的(因為基于同樣的LDAP協(xié)議),不同的可能僅僅是接口,類的名字而已。

4.5.1 初始化Connection Pool
  為了有效使用寶貴,并且有限的LDAP連接,必須使用連接池。下面的代碼初始化了一個LDAP連接池:

/**
*
*/
static ConnectionPool pool;
/** * * LDAPException
*/ public LdapClient()
  throws LDAPException
    {
      pool= new ConnectionPool(
        1, 150, "127.0.0.1", 389, "cn=Directory Manager", "88888888");
}

  Sun JNDI LDAP Service Provider(JDK1.4)中可以通過在環(huán)境變量中設置具體的參數(shù)來啟用連接池,達到復用連接的目的。具體可以參考鏈接:http://java./products/jndi/tutorial/ldap/connect/index.html,下面是示例代碼:

Hashtable env= new Hashtable();

env.put("com.sun.jndi.ldap.connect.pool", "true");
DirContext ctx= new InitialDirContext( env);

4.5.2 根據(jù)用戶輸入的登錄賬號,搜索用戶Entry
  下面這個方法實現(xiàn)了從LDAP中搜索用戶Entry DN的最簡單的過程。實際上我們可以在其中實現(xiàn)很多定制的功能。比如允許用戶使用多于一個的賬號登錄,只要這些賬號能夠通過LDAP Searche最終返回一個唯一的用戶DN即可。

  *
   * LDAPException
   */
  public String getUserDN( String uid) throws LDAPException{
    LDAPConnection conn= pool.getConnection();
    try {
      String[] attrs= new String[]
{};
      LDAPSearchResults sr= conn.search("o=examples", 2, "(uid="+ uid +")", attrs, false);
      if ( sr.hasMoreElements()) {
        LDAPEntry entry= sr.next();
        return entry.getDN();
}
    throw new LDAPException("No Such Object:"+ uid,
        LDAPException.NO_SUCH_OBJECT);
    }catch ( LDAPException ex) {
      throw ex;
    }finally {
      try {
        if (conn!= null) pool.close(conn);
      }catch ( Exception ex)
{}
    }
  }

  首先需要從池中獲取一個LDAP連接,然后使用LDAPConnection.search方法進行搜索。我這里以一個典型的LDAP Search接口為例進行說明,其他API比如JNDI等,基于同樣的LDAP協(xié)議,接口中同樣參數(shù)的含義都是相同的。

public LDAPSearchResults search(java.lang.String base,
  int scope,
  java.lang.String filter,
  java.lang.String[] attrs,
  boolean attrsOnly)

  1) Base: 表明從該Basedn開始搜索,可以通過MBean獲取

  2) Scope: 搜索的范圍:

    a) LDAPv2.SCOPE_BASE, 只搜索basedn指定的entry
    b) LDAPv2.SCOPE_ONE, 在basedn的下一級entry中搜索,不包括basedn
    c) LDAPv3.SCOPE_SUB,在basedn的全部下級entry中搜索,包括basedn

  3) Filter:過慮條件。比如uid=ZHANGSAN,搜索UID為ZHANGSAN的用戶;再比如uid=ZHANG*,搜索 ZHANG開頭的賬號;或者uid=Z*S,搜索以Z開頭S結尾的賬號。前面已經(jīng)介紹過這里就不多說了。

  4) attrs:返回該attrs數(shù)組中指定的屬性,比如new String[]{“uid”},只返回屬性uid,其他屬性將會不在結果中返回。 一般來說我們會要求開發(fā)人員只將需要的屬性返回,這樣避免返回無用的屬性,降低網(wǎng)絡和Server等方面的資源開銷;而且如果存在一個有較大屬性集合的Entry,并且你并不使用到這個較大的屬性集合。舉個實際例子來說,比如你的系統(tǒng)中擁有很多成員數(shù)目超過2萬或者更多的LDAP Groups,并且你希望通過LDAP Search找出某一個用戶屬于的組CN,那么搜索結果只返回組的CN已經(jīng)可以滿足你的要求了,這時就沒有必要將全部member屬性也返回了。這在后面我會有代碼來說明。

  這里還有一點很重要的細節(jié),相信對讀者會有幫助。比如,你希望搜索到modifytimestamp等Operational Attributes。這些屬性(LDAP Server自己負責維護的,用戶無法修改的)必須要在參數(shù)attrs中指定,LDAP Server才會返回給客戶端。如果用戶希望返回全部User Attributes的同時,返回指定的 Operational Attributes那該怎么辦?不在attrs列表中的屬性將不會被返回,一旦我指定了attrs,是否需要將全部的屬性都列在attrs參數(shù)中?實際上此時只需要傳入 new String[]{ “*”, “createtimestamp”}這樣的參數(shù)即可;“*”號即代表了全部的User Attributes。在Sun JNDI LDAP Service Provider中也是這樣,盡管找遍Sun關于JNDI的文檔也找不到這樣的說明。LDAP協(xié)議對此的說明在 http://www./rfc/rfc2251.txt 第28頁。

  5) attrsOnly:只返回屬性名稱,不包含值。我們一般設置為false。

  我們下面看一下Sun JNDI中關于LDAP Search的接口:

public NamingEnumeration search(String name,
  String filter,
  SearchControls cons)
  throws NamingException
name參數(shù)就是上面的basedn,SearchControls中封裝更多的設置,比如:SearchControls. setSearchScope(int scope)其中的scope對應上面的2); SearchControls.setReturningAttributes(String[] attrs),設置指定返回的屬性,對應上面的4)。

  其他的參數(shù)還包括Size Limit,即限制搜索結果的數(shù)目(LDAP返回的Entry一般情況下是沒有排序的,除非使用一些Sort Control),當你的搜索可能會返回成千上萬的Entry時,限制搜索的數(shù)目是非常明智也是必須的。關于這些,讀者可以參考API文檔或者LDAP協(xié)議。

4.5.3 根據(jù)用戶的Entry DN,和用戶輸入的口令完成身份驗證
  
下面的方法實現(xiàn)了到LDAP的身份驗證。LDAP中的用戶實際上就是一個LDAP Entry,一次驗證實際是將Connection與這個Entry進行綁定,因此驗證時我們需要兩個必須的參數(shù),一個是Entry的DN,一個是口令。LDAP的身份驗證方式有多種,包括SASL以及基于證書的驗證等,我們這里只介紹Simple Authentication。關于SASL更多信息讀者可以參考:http://www./rfc/rfc2222.txt。

  /**
   *
   * dn
   * pwd
   *
   * LDAPException
   */
  public boolean authentication( String dn, String pwd) throws LDAPException {
    LDAPConnection conn= pool.getConnection();
    try {
      conn.authenticate( dn, pwd);
      return true;
    }catch ( LDAPException ex) {
      if ( ex.getLDAPResultCode()== LDAPException.INVALID_CREDENTIALS) {
        return false; // 用戶口令錯誤 }
      if ( ex.getLDAPResultCode()== LDAPException.NO_SUCH_OBJECT) {
        return false; // 用戶不存在 }
      throw ex;
    }finally {
      try {
        if ( conn!= null) pool.close(conn);
      }catch ( Exception ex) {
      }
    }
  }


  我們這里使用 LDAP Bind操作完成對用戶的身份驗證。本文前面曾提到也可以使用LDAP Compare,通過比較口令屬性(userpassword)中的值來完成驗證。我們需要根據(jù)不同的LDAP Server選擇合適的驗證方法。

  比如iPlanet LDAP Server 5,只能通過Bind完成認證;而Oracle Internet Directory可以通過LDAP Compare完成驗證,而且還支持口令策略。

  如果我們使用了連接池,連接池中的連接一般都是使用權限較大的用戶初始化的,這樣這些連接才可以完成對LDAP的搜索操作;而當通過這些連接對普通用戶進行身份驗證時,如果通過驗證,連接的身份將被改變?yōu)槠胀ǖ挠脩簦ɑ蚍Q為與普通用戶的身份關聯(lián))。普通用戶很可能沒有除了bind以外的任何權限,所以在連接被放入池中前,我們必須要恢復連接的身份。

  這樣我們必須執(zhí)行兩次LDAP Bind,一次用于對普通用戶驗證身份;一次用于恢復連接的較大權限的用戶身份。我們看到這樣效率是比較低的,可能你在LDAP Server端統(tǒng)計有2萬次bind請求,實際上只有1萬人次登錄。

  對于特定的LDAP Server,比如 Oracle Internet Directory,可以通過LDAP Compare對用戶身份進行驗證,并且不會改變連接關聯(lián)的用戶身份。這樣我在使用池的情況下只需要一次LDAP Compare即可。效率有很大提高。如果不通過定制LDAP Authentication Provider,這樣的調優(yōu)是沒法實現(xiàn)的。

  Netscape LDAP SDK的ConnectionPool的實現(xiàn)中,在連接放入池中前會檢查連接的身份,如果身份被改變,那么會重新進行bind。所以我們沒有必要在代碼中再做bind。

4.5.4 根據(jù)用戶的DN搜索用戶屬于的組列表
  
有了上面的基礎后,這個方法就很容易理解了。下面我們來看看如何返回用戶所屬的組。

/**
   *
   * groupbasedn
   * memberDn
   *
   * LDAPException
   */
  public List getGroupMembership( String groupbasedn, String memberDn) throws LDAPException {
    LDAPConnection conn= pool.getConnection();
    try {
      LDAPSearchResults sr= conn.search(
          groupbasedn,
          2,
          "(uniquemember="+ memberDn +")",
          new String[] {"cn"},
          false);
      List groups= new java.util.ArrayList();
      while ( sr.hasMoreElements()) {
        LDAPEntry entry= sr.next();
        LDAPAttribute attr= entry.getAttribute("cn");
        if ( attr!= null) {
          String[] values= attr.getStringValueArray();
          if ( values!= null && values.length>0) groups.add( values[0]);
        }
      }
      return groups;
    }catch ( LDAPException ex) {
      throw ex;
    }finally {
      try {
        if ( conn!= null) pool.close(conn);
      }catch ( Exception ex) {
      }
    }
  }

  成員和組的membership主要是通過uniquemember或者 member屬性來定義的。成員不僅僅可以使用用戶,也可以是組,因為組可以嵌套。

  a) 上面的方法中只實現(xiàn)了一個層次的搜索,即用戶——組的搜索,而組間的嵌套搜索沒有實現(xiàn)。讀者可以根據(jù)系統(tǒng)內的具體情況,在此處也可以做一些優(yōu)化。

  b) 組成員的數(shù)量可能比較大,為了避免不必要的開銷,我們指定只返回組的cn屬性。

  c) 動態(tài)組的優(yōu)化。在WebLogic默認的實現(xiàn)中,Provider(實際為LoginModule)會不斷的拿動態(tài)組中定義的URL中的filter和用戶的DN去LDAP做搜索,來看用戶是否屬于該組。本文前面也討論了,這樣效率很低,完全可以放在Provider本地實現(xiàn)URL和Entry的匹配。

4.5.5 LoginModule中的login()方法實現(xiàn)
  
一切準備就緒后,我們就可以完成LoginModule.login()這個最核心的方法了。下面我根據(jù)代碼中的注釋逐條說明。

  // A
  boolean loginSucceeded;
  // B
  List principals= new java.util.ArrayList();
  /**
   *
   *
   * LoginException
   */
  public boolean login() throws LoginException {
    // C
    Callback[] callbacks= new Callback[] {
        new NameCallback("username: "),
        new PasswordCallback("password: ",false)};
    try {
      callbackHandler.handle( callbacks);
    }catch (IOException e) {
      throw new LoginException(e.toString());
    }catch (UnsupportedCallbackException e) {
      throw new LoginException(e.toString() + " " +e.getCallback().toString());     }
    //
    String userName = ((NameCallback)callbacks[0]).getName();
    if ( userName== null || userName.length()== 0) {
      throw new LoginException("User login name is empty!");
    }
    //
    PasswordCallback passwordCallback= (PasswordCallback)callbacks[1];     char[] password = passwordCallback.getPassword();
    passwordCallback.clearPassword();
    if ( password== null || password.length== 0) {
      throw new LoginException("User password is empty!");
    }
    try {
      // D
      String dn= this.getUserDN( userName);
      if ( dn== null) {
        throw new LoginException("User "+ userName +" doesn‘t exist.");
      }
      // E
      boolean authResult= this.authentication( dn, String.valueOf( password));       if ( authResult== false) {
        throw new FailedLoginException("User login failed.");
      }
      // F
      principals.add( new WLSUserImpl( userName));
      // G
      List groups= this.getGroupMembership( "ou=groups,o=examples", dn);       for ( int i=0, n=groups.size(); i<n; i++) {
        String cn= String.valueOf(groups.get(i));
        // H
        principals.add( new WLSGroupImpl(cn));
      }
    }catch ( LDAPException ex) {
      java.io.StringWriter sw = new java.io.StringWriter();  
     ex.printStackTrace(new java.io.PrintWriter(sw));
      sw.flush();
      throw new LoginException( sw.toString());
    } 
       return loginSucceeded= true;
  }

  a) 用于保存驗證結果,在后續(xù)方法(commit等)中使用

  b) 用于保存和用戶關聯(lián)的Principal,在后續(xù)方法(commit等)中使用

  c) 在JAAS中通過CallbackHandler來完成用戶和底層安全認證系統(tǒng)間信息的交換,這里我們通過CallbackHandler獲取用戶輸入的登錄賬號和口令。CallbackHandler將在初始化LoginModule時由WebLogic Security Framework傳入。讀者可能會問,我是否可以在此要求WebLogic Security Framework傳入我自己實現(xiàn)的CallbackHandler,進而獲取更多的信息,支持更多的Callback?很遺憾,不可以。只有在一種情況下(本文前面也提到)有一個變通,就是在Web應用中,通過Form Based這種認證方式進行用戶身份驗證時,可以獲取更多的登錄表單中提交的cgi變量。

  下面的代碼將演示這種技術:

    Callback[] callbacks= new Callback[] {
        new NameCallback("username: "),
        new PasswordCallback("password: ",false),
        new TextInputCallback("my_hidden_field")};
    try {
      callbackHandler.handle( callbacks);
    }catch (IOException e) {
      throw new LoginException(e.toString());
    }catch (UnsupportedCallbackException e) {
      throw new LoginException(e.toString() + " " +e.getCallback().toString());     }
    String value= ((TextInputCallback)callbacks[2]).getText();

  這樣我們通過((TextInputCallback)callbacks[2]).getText()來獲取表單中更多的信息。其中TextInputCallback的Prompt必須要和表單中對應域的名字一致,否則取不到信息。如果有多個域,則需要傳入多個TextInputCallback。

  同時如果登錄表單中沒有這些對應TextInputCallback的域,或者使用JNDI方式驗證,那么會拋出UnsupportedCallbackException。

  下面是一個符合Servlet安全規(guī)范的登錄表單,其中域my_hidden_field的值將通過CallbackHandler傳遞到的TextInputCallback中:

<html>
  <form method=”POST” action=”j_security_check”>
    <input type=”text” name=”j_username”>
    <input type=”password” name=”j_password”>
    <input type=”hidden” name=”my_hidden_field”value=”Hello Callback!”>
  </form>
</html>

  d) 生成一個用于存放Principal的集合對象
  e) 通過登錄賬號獲取LDAP中的Entry DN
  f) 通過DN和用戶口令進行身份驗證
  g) 將通過驗證的用戶名加入到Principal列表中,這些以后都將代表用戶的一定權限
  h) 查找用戶屬于哪些組
  i) 將這些組的名字加入到Principal列表中

4.5.6 LoginModule中的commit()和abort()方法實現(xiàn)
  
完成了以上的驗證后,我們需要通過LoginModule.commit()方法提交驗證結果;或者abort()方法取消驗證結果。

  // A
  private Subject subject;
    /**
   *
   *
   * LoginException
   */
  public boolean commit() throws LoginException { if (loginSucceeded) {
  // B
      subject.getPrincipals().addAll(principals);
      return true;
    }else {
      return false;
    }
  }
    /**
   *
   *
   * LoginException
   */
    public boolean abort() throws LoginException {
    // C
    subject.getPrincipals().removeAll(principals);
    return true;
  }    

 

  我這里仍然通過代碼中的注釋進行相應的說明:

  a) JAAS Subject,在LoginModule初始化時,由WebLogic Security Framework負責傳入,用戶的權限信息(Principals等)都將封裝到該Subject中。同時Principal Validator會對Subject中的Principals進行簽名,防止被黑客纂改。

  b) 用戶認證成功的情況下,將用戶關聯(lián)的Principals加入到Subject中。這樣用戶在進行后續(xù)的訪問時,WebLogic Security Framework會檢查與用戶關聯(lián)的Subject中是否有可以訪問受限資源的Principal。

  c) 用戶登錄失敗的情況下(有可能是其他的LoginModule導致的整體登錄失敗,具體參考JAAS Control Flag),我們在commit中添加的Principals必須從Subject中刪除。


  通過上面的描述和討論,相信讀者已經(jīng)對如何實現(xiàn)一個LDAP AuthenticationProvider/LoginModule有了比較清楚的了解。限于篇幅省略了很多細節(jié),對此有需要的讀者可以根據(jù)文中給出的相關鏈接做進一步的了解;或者與我交流。從這里我們也可以看到核心的邏輯還是比較簡單的。其中將LDAP Group作為用戶的Principal,是J2EE Security與LDAP Security結合的非常重要的一步。通過這種結合,我們可以將LDAP作為后端,設計出基于J2EE,JAAS的用戶身份驗證,權限管理等系統(tǒng)。

5 部署中的注意事項
  開發(fā)完成LDAP Authentication Provider后,需要將通過WebLogic MBean Maker生成的MBean Jar File放到/server/lib/mbeantypes目錄下。由于WebLogic Server在啟動時會加載這些Provider,因此在一個分布式的環(huán)境中,WebLogic Domain內的全部WLServer(包括Managed Server)均需要部署該Jar包。

  同時由于使用了Provider MBean,因此,當你刪除了MBean Types中定義的,并且通過修改保存在config.xml(WebLogic Domain配置文件)中的屬性時,重新部署的Jar包會導致WebLogic Server無法正常啟動,因為它沒有辦法按照config.xml中配置的屬性初始化MBean。此時需要手工修改config.xml,刪除相關的屬性即可。

<examples.security.providers.authentication.simple.SimpleSampleAuthenticator
  ControlFlag="OPTIONAL"
  Name="Security:Name=myrealmSimpleSampleAuthenticator"
  UserBaseDN="ou=people, o=examples" Realm="Security:Name=myrealm"/>

  比如UserBaseDN已經(jīng)不需要通過MBean來管理,并且在MBean Types中已經(jīng)刪除,那么直接刪除這里的UserBaseDN="ou=people, o=examples" 就可以了。

6 結束語
  本文所討論的內容有些過于廣泛,從我個人角度講,寫起來也比較困難,很多技術問題沒有進行深入的討論。其實只是希望通過本文,能夠幫助讀者對WebLogic Security,J2EE Security,LDAP Security以及JAAS有一個初步的認識;能夠給希望實現(xiàn)LDAP Authentication Provider,或者被WebLogic LDAP Authentication Provider困惑的讀者一些啟示。只要能夠對讀者有所幫助,本文的目的就算達到了。

  限于篇幅,本文很多地方的表述可能并不清晰,也可能會有一些錯誤,如果在閱讀過程中產(chǎn)生任何疑問或者有任何看法都可以通過E-mail來與我交流,我也非常希望能和大家交流。

7 參考資料
a) http://e-docs./wls/docs81/secwlres/types.html#1213777 關于WebLogic Resource的更多說明
b) http://e-docs./wls/docs81/security/index.html 關于WebLogic Security的全部內容
c) http://e-docs./wls/docs81/dvspisec/rm.html#1145542 關于Role Mapping Provider的更多內容
d) http://dev2dev./techdoc/webser/2005012102.html 關于WebLogic Console的擴展
e) http://dev2dev./codelibrary/code/security_prov81.jsp Custom Seuciryt Provider的Samples
f) http://java./products/JavaManagement/wp/ 更多關于Sun JMX
g) http://java./products/jndi/tutorial/ldap/connect/index.html 關于如何使用Sun JNDI LDAP Service Provider中提供的連接池
h) http://www./rfc/rfc2254.txt 更多關于LDAP Filter
i) http://www./rfc/rfc2251.txt LDAP V3協(xié)議
j) http://www./rfc/rfc2222.txt 更多關于SASL

 作者簡介
馬曉強是高級軟件工程師,現(xiàn)在廣東從事J2EE,LDAP相關的設計開發(fā)工作。對WebLogic,LDAP以及Single Sign-On等產(chǎn)品、技術很感興趣。
dot dot dot

dot
  作者其它文章

    本站是提供個人知識管理的網(wǎng)絡存儲空間,所有內容均由用戶發(fā)布,不代表本站觀點。請注意甄別內容中的聯(lián)系方式、誘導購買等信息,謹防詐騙。如發(fā)現(xiàn)有害或侵權內容,請點擊一鍵舉報。
    轉藏 分享 獻花(0

    0條評論

    發(fā)表

    請遵守用戶 評論公約

    類似文章 更多