JNDIRealm 是 Tomcat Realm 接口的一種實現,通過一個 JNDI 提供者1在 LDAP 目錄服務器中查找用戶。realm 支持大量的方法來使用認證目錄。
通常是可以使用 JNDI API 類的標準 LDAP 提供者。
realm 與目錄服務器的連接是通過 connectionURL 配置屬性來定義的。這個 URL 的格式是通過 JNDI 提供者來定義的。它通常是個 LDAP URL,指定了所要連接的目錄服務器的域名,另外還(可選擇)指定了所需的根命名上下文的端口和唯一名稱(DN)。
如果有多個提供者,則可以配置 alternateURL。如果一個套接字連接無法傳遞給提供者 connectionURL,則會換用 alternateURL。
當通過創建連接來搜索目錄,獲取用戶及角色信息時,realm 會利用 connectionName 和 connectionPassword 這兩個屬性所指定的用戶名和密碼在目錄上進行自我認證。如果未指定這兩個屬性,則創立的連接是匿名連接,這種連接適用于大多數情況。
在目錄中,每個可被認證的用戶都必須表示為獨立的項,這種獨立項對應著由屬性 connectionURL 定義的初始 DirContext 中的元素。這種用戶項必須有一個包含認證所需用戶名的屬性。
每個用戶項的唯一性名稱(DN)通常含有用于認證的用戶名。在這種情況下,userPattern 屬性可以用來指定 DN,其中的 {0} 代表用戶名應該被替換的位置。
realm 必須搜索目錄來尋找一個包含用戶名的唯一項,可用下列屬性來配置搜索:
默認情況下,realm 會利用用戶項的 DN 與用戶所提供的密碼,將用戶綁定到目錄上。如果成功執行了這種簡單的綁定,那么就可以認為用戶認證成功。
出于安全考慮,目錄可能保存的是用戶的摘要式密碼,而非明文密碼(參看摘要式密碼以獲知詳情)。在這種情況下,在綁定過程中,目錄會自動將用戶所提供的明文密碼加密為正確的摘要式密碼,以便后續和存儲的摘要式密碼進行比對。然而在綁定過程中,realm 并不參與處理摘要式密碼。不會用到 digest 屬性,如果設置了該屬性,也會被自動忽略。
另外一種方法是,realm 從目錄中獲取存儲的密碼,然后將其與用戶所提供的值進行比對。配置方法是,在包含密碼的用戶項中,將 userPassword 屬性設為目錄屬性名。
對比模式的缺點在于:首先,connectionName 和 connectionPassword 屬性必須配置成允許 realm 讀取目錄中的用戶密碼。出于安全考慮,這是一種不可取的做法。事實上,很多目錄實現甚至都不允許目錄管理器讀取密碼。其次,realm 必須自己處理摘要式密碼,包括要設置所使用的具體算法、在目錄中表示密碼散列值的方式。但是,realm 可能有時又要訪問存儲的密碼,比如為了支持 HTTP 摘要式訪問認證(HTTP Digest Access Authentication,RFC 2069)。(注意,HTTP 摘要式訪問認證不同于之前討論過的在庫中存儲密碼摘要的方式。)
Realm 支持兩種方法來表示目錄中的角色:
通過明確的目錄項來表示角色。角色項通常是一個 LDAP 分組項,該分組項的一個屬性包含角色名稱,另一屬性值則是擁有該角色的用戶的 DN 名或用戶名。下列屬性配置了一個目錄搜索來尋找與認證用戶相關的角色名。
將角色名稱保存為用戶目錄項中的一個屬性值。使用 userRoleName 來指定該屬性名稱。當然,也可以綜合使用這兩種方法來表示角色。
為了配置 Tomcat 使用 JNDIRealm,需要下列步驟:
如上所述,為了配置 JDBCRealm,需要創建一個 Realm 元素,并把它放在 $CATALINA_BASE/conf/server.xml 文件中。JDBCRealm 的屬性都定義在 Realm 配置文檔中。
在目錄服務器上創建適合的模式超出了本文檔的講解范圍,因為這是跟每個目錄服務器的實現密切相關的。在下面的實例中,我們將假定使用的是 OpenLDAP 目錄服務器的一個分發版(2.0.11 版或更新版本,可從http://www.openldap.org處下載)。假設 slapd.conf 文件包含下列設置(除了其他設置之外)。
database ldbm
suffix dc="mycompany",dc="com"
rootdn "cn=Manager,dc=mycompany,dc=com"
rootpw secret
我們還假定 connectionURL,使目錄服務器與 Tomcat 運行在同一臺機器上。要想了解如何配置及使用 JNDI LDAP 提供者的詳細信息,請參看 http://docs.oracle.com/javase/7/docs/technotes/guides/jndi/index.html。
接下來,假定利用如下所示的元素(以 LDIF 格式)來填充目錄服務器。
# Define top-level entry
dn: dc=mycompany,dc=com
objectClass: dcObject
dc:mycompany
# Define an entry to contain people# searches for users are based on this entry
dn: ou=people,dc=mycompany,dc=com
objectClass: organizationalUnit
ou: people
# Define a user entry for Janet Jones
dn: uid=jjones,ou=people,dc=mycompany,dc=com
objectClass: inetOrgPerson
uid: jjones
sn: jones
cn: janet jones
mail: [email protected]
userPassword: janet
# Define a user entry for Fred Bloggs
dn: uid=fbloggs,ou=people,dc=mycompany,dc=com
objectClass: inetOrgPerson
uid: fbloggs
sn: bloggs
cn: fred bloggs
mail: [email protected]
userPassword: fred
# Define an entry to contain LDAP groups# searches for roles are based on this entry
dn: ou=groups,dc=mycompany,dc=com
objectClass: organizationalUnit
ou: groups
# Define an entry for the "tomcat" role
dn: cn=tomcat,ou=groups,dc=mycompany,dc=com
objectClass: groupOfUniqueNames
cn: tomcat
uniqueMember: uid=jjones,ou=people,dc=mycompany,dc=com
uniqueMember: uid=fbloggs,ou=people,dc=mycompany,dc=com
# Define an entry for the "role1" role
dn: cn=role1,ou=groups,dc=mycompany,dc=com
objectClass: groupOfUniqueNames
cn: role1
uniqueMember: uid=fbloggs,ou=people,dc=mycompany,dc=com
OpenLDAP 服務器。假定用戶使用他們的 uid(比如說 jjones)登錄應用,匿名連接已經足夠可以搜索目錄并獲取角色信息了:
.apache.catalina.realm.JNDIRealm"
connectionURL="ldap://localhost:389"
userPattern="uid={0},ou=people,dc=mycompany,dc=com"
roleBase="ou=groups,dc=mycompany,dc=com"
roleName="cn"
roleSearch="(uniqueMember={0})"
/>
利用這種配置,通過在 userPattern 替換用戶名,realm 能夠確定用戶的 DN,然后利用這個 DN 和取自用戶的密碼將用戶綁定到目錄中,從而驗證用戶身份,然后搜索整個目錄服務器來找尋用戶角色。
現在假定希望用戶輸入電子郵件地址(而不是用戶 id)。在這種情況下,realm 必須搜索目錄找到用戶項。當用戶項被保存在多個子樹中,而這些子樹可能分別對應不同的組織單位或企業位置時,可能必須執行一個搜索。
另外,假設除了分組項之外,你還想用用戶項的屬性來保存角色,那么在這種情況下,Janet Jones 對應的項可能如下所示:
dn: uid=jjones,ou=people,dc=mycompany,dc=comobjectClass: inetOrgPersonuid: jjonessn: jonescn: janet jonesmail: [email protected]: role2memberOf: role3userPassword: janet
這個 realm 配置必須滿足以下新要求:
<Realm className="org.apache.catalina.realm.JNDIRealm"
connectionURL="ldap://localhost:389"
userBase="ou=people,dc=mycompany,dc=com"
userSearch="(mail={0})"
userRoleName="memberOf"
roleBase="ou=groups,dc=mycompany,dc=com"
roleName="cn"
roleSearch="(uniqueMember={0})"
/>
當 Janet Jones 用她的電子郵件 [email protected] 登錄時,realm 會搜索目錄,尋找帶有該電郵值的唯一項,并嘗試利用給定密碼來綁定到目錄:uid=jjones,ou=people,dc=mycompany,dc=com。如果驗證成功,該用戶將被賦予以下三個角色:"role2" 與 "role3",她的目錄項中的 memberOf 屬性值;"tomcat",她作為成員存在的唯一分組項中的 cn 屬性值。
最后,為了驗證用戶,我們必須從目錄中獲取密碼并在 realm 中執行本地比對,將 realm 按照如下方式來配置:
<Realm className="org.apache.catalina.realm.JNDIRealm"
connectionName="cn=Manager,dc=mycompany,dc=com"connectionPassword="secret"
connectionURL="ldap://localhost:389"
userPassword="userPassword"
userPattern="uid={0},ou=people,dc=mycompany,dc=com"
roleBase="ou=groups,dc=mycompany,dc=com"
roleName="cn"
roleSearch="(uniqueMember={0})"
/>
但是,正如之前所討論的那樣,往往應該優先考慮默認的綁定模式。
使用 JNDIRealm 需要遵循以下規則: