天天看點

【IBM】使用 CAS 在 Tomcat 中實作單點登入

簡介: 單點登入(Single Sign On , 簡稱 SSO )是目前比較流行的服務于企業業務整合的解決方案之一, SSO 使得在多個應用系統中,使用者隻需要登入一次就可以通路所有互相信任的應用系統。CAS(Central Authentication Service)是一款不錯的針對 Web 應用的單點登入架構,本文介紹了 CAS 的原理、協定、在 Tomcat 中的配置和使用,對于采用 CAS 實作輕量級單點登入解決方案的入門讀者具有一定指導作用。

 CAS 介紹

CAS 是 Yale 大學發起的一個開源項目,旨在為 Web 應用系統提供一種可靠的單點登入方法,CAS 在 2004 年 12 月正式成為 JA-SIG 的一個項目。CAS 具有以下特點:

開源的企業級單點登入解決方案。

CAS Server 為需要獨立部署的 Web 應用。

CAS Client 支援非常多的用戶端(這裡指單點登入系統中的各個 Web 應用),包括 Java, .Net, PHP, Perl, Apache, uPortal, Ruby 等。

從結構上看,CAS 包含兩個部分: CAS Server 和 CAS Client。CAS Server 需要獨立部署,主要負責對使用者的認證工作;CAS Client 負責處理對用戶端受保護資源的通路請求,需要登入時,重定向到 CAS Server。圖1 是 CAS 最基本的協定過程:

CAS Client 與受保護的用戶端應用部署在一起,以 Filter 方式保護受保護的資源。對于通路受保護資源的每個 Web 請求,CAS Client 會分析該請求的 Http 請求中是否包含 Service Ticket,如果沒有,則說明目前使用者尚未登入,于是将請求重定向到指定好的 CAS Server 登入位址,并傳遞 Service (也就是要通路的目的資源位址),以便登入成功過後轉回該位址。使用者在第 3 步中輸入認證資訊,如果登入成功,CAS Server 随機産生一個相當長度、唯一、不可僞造的 Service Ticket,并緩存以待将來驗證,之後系統自動重定向到 Service 所在位址,并為用戶端浏覽器設定一個 Ticket Granted Cookie(TGC),CAS Client 在拿到 Service 和新産生的 Ticket 過後,在第 5,6 步中與 CAS Server 進行身份合适,以確定 Service Ticket 的合法性。

在該協定中,所有與 CAS 的互動均采用 SSL 協定,確定,ST 和 TGC 的安全性。協定工作過程中會有 2 次重定向的過程,但是 CAS Client 與 CAS Server 之間進行 Ticket 驗證的過程對于使用者是透明的。

另外,CAS 協定中還提供了 Proxy (代理)模式,以适應更加進階、複雜的應用場景,具體介紹可以參考 CAS 官方網站上的相關文檔。

本文中的例子以 tomcat5.5 為例進行講解,下載下傳位址:

<a href="http://tomcat.apache.org/download-55.cgi">http://tomcat.apache.org/download-55.cgi</a>

到 CAS 官方網站下載下傳 CAS Server 和 Client,位址分别為:

<a href="http://www.ja-sig.org/downloads/cas/cas-server-3.1.1-release.zip">http://www.ja-sig.org/downloads/cas/cas-server-3.1.1-release.zip</a>

<a href="http://www.ja-sig.org/downloads/cas-clients/cas-client-java-2.1.1.zip">http://www.ja-sig.org/downloads/cas-clients/cas-client-java-2.1.1.zip</a>

<a href="http://www.ibm.com/developerworks/cn/opensource/os-cn-cas/#ibm-pcon">回頁首</a>

CAS Server 是一套基于 Java 實作的服務,該服務以一個 Java Web Application 單獨部署在與 servlet2.3 相容的 Web 伺服器上,另外,由于 Client 與 CAS Server 之間的互動采用 Https 協定,是以部署 CAS Server 的伺服器還需要支援 SSL 協定。當 SSL 配置成功過後,像普通 Web 應用一樣将 CAS Server 部署在伺服器上就能正常運作了,不過,在真正使用之前,還需要擴充驗證使用者的接口。

在 Tomcat 上部署一個完整的 CAS Server 主要按照以下幾個步驟:

如果希望 Tomcat 支援 Https,主要的工作是配置 SSL 協定,其配置過程和配置方法可以參考 Tomcat 的相關文檔。不過在生成證書的過程中,會有需要用到主機名的地方,CAS 建議不要使用 IP 位址,而要使用機器名或域名。

CAS Server 是一個 Web 應用包,将前面下載下傳的 cas-server-3.1.1-release.zip 解開,把其中的 cas-server-webapp-3.1.1.war 拷貝到 tomcat的 webapps 目錄,并更名為 cas.war。由于前面已配置好 tomcat 的 https 協定,可以重新啟動 tomcat,然後通路:https://localhost:8443/cas ,如果能出現正常的 CAS 登入頁面,則說明 CAS Server 已經部署成功。

雖然 CAS Server 已經部署成功,但這隻是一個預設的實作,在實際使用的時候,還需要根據實際概況做擴充和定制,最主要的是擴充認證 (Authentication) 接口和 CAS Server 的界面。

CAS Server 負責完成對使用者的認證工作,它會處理登入時的使用者憑證 (Credentials) 資訊,使用者名/密碼對是最常見的憑證資訊。CAS Server 可能需要到資料庫檢索一條使用者帳号資訊,也可能在 XML 檔案中檢索使用者名/密碼,還可能通過 LDAP Server 擷取等,在這種情況下,CAS 提供了一種靈活但統一的接口和實作分離的方式,實際使用中 CAS 采用哪種方式認證是與 CAS 的基本協定分離開的,使用者可以根據認證的接口去定制和擴充。

擴充 AuthenticationHandler

CAS 提供擴充認證的核心是 AuthenticationHandler 接口,該接口定義如清單 1 下:

該接口定義了 2 個需要實作的方法,supports ()方法用于檢查所給的包含認證資訊的Credentials 是否受目前 AuthenticationHandler 支援;而 authenticate() 方法則擔當驗證認證資訊的任務,這也是需要擴充的主要方法,根據情況與存儲合法認證資訊的媒體進行互動,傳回 boolean 類型的值,true 表示驗證通過,false 表示驗證失敗。

CAS3中還提供了對AuthenticationHandler 接口的一些抽象實作,比如,可能需要在執行authenticate() 方法前後執行某些其他操作,那麼可以讓自己的認證類擴充自清單 2 中的抽象類:

AbstractPreAndPostProcessingAuthenticationHandler 類新定義了 preAuthenticate() 方法和 postAuthenticate() 方法,而實際的認證工作交由 doAuthentication() 方法來執行。是以,如果需要在認證前後執行一些額外的操作,可以分别擴充 preAuthenticate()和 ppstAuthenticate() 方法,而 doAuthentication() 取代 authenticate() 成為了子類必須要實作的方法。

由于實際運用中,最常用的是使用者名和密碼方式的認證,CAS3 提供了針對該方式的實作,如清單 3 所示:

基于使用者名密碼的認證方式可直接擴充自 AbstractUsernamePasswordAuthenticationHandler,驗證使用者名密碼的具體操作通過實作 authenticateUsernamePasswordInternal() 方法達到,另外,通常情況下密碼會是加密過的,setPasswordEncoder() 方法就是用于指定适當的加密器。

從以上清單中可以看到,doAuthentication() 方法的參數是 Credentials 類型,這是包含使用者認證資訊的一個接口,對于使用者名密碼類型的認證資訊,可以直接使用 UsernamePasswordCredentials,如果需要擴充其他類型的認證資訊,需要實作Credentials接口,并且實作相應的 CredentialsToPrincipalResolver 接口,其具體方法可以借鑒 UsernamePasswordCredentials 和 UsernamePasswordCredentialsToPrincipalResolver。

JDBC 認證方法

使用者的認證資訊通常儲存在資料庫中,是以本文就選用這種情況來介紹。将前面下載下傳的 cas-server-3.1.1-release.zip 包解開後,在 modules 目錄下可以找到包 cas-server-support-jdbc-3.1.1.jar,其提供了通過 JDBC 連接配接資料庫進行驗證的預設實作,基于該包的支援,我們隻需要做一些配置工作即可實作 JDBC 認證。

JDBC 認證方法支援多種資料庫,DB2, Oracle, MySql, Microsoft SQL Server 等均可,這裡以 DB2 作為例子介紹。并且假設DB2資料庫名: CASTest,資料庫登入使用者名: db2user,資料庫登入密碼: db2password,使用者資訊表為: userTable,該表包含使用者名和密碼的兩個資料項分别為 userName 和 password。

1. 配置 DataStore

打開檔案 %CATALINA_HOME%/webapps/cas/WEB-INF/deployerConfigContext.xml,添加一個新的 bean 标簽,對于 DB2,内容如清單 4 所示:

其中 id 屬性為該 DataStore 的辨別,在後面配置 AuthenticationHandler 會被引用,另外,需要提供 DataStore 所必需的資料庫驅動程式、連接配接位址、資料庫登入使用者名以及登入密碼。

2. 配置 AuthenticationHandler

在 cas-server-support-jdbc-3.1.1.jar 包中,提供了 3 個基于 JDBC 的 AuthenticationHandler,分别為 BindModeSearchDatabaseAuthenticationHandler, QueryDatabaseAuthenticationHandler, SearchModeSearchDatabaseAuthenticationHandler。其中 BindModeSearchDatabaseAuthenticationHandler 是用所給的使用者名和密碼去建立資料庫連接配接,根據連接配接建立是否成功來判斷驗證成功與否;QueryDatabaseAuthenticationHandler 通過配置一個 SQL 語句查出密碼,與所給密碼比對;SearchModeSearchDatabaseAuthenticationHandler 通過配置存放使用者驗證資訊的表、使用者名字段和密碼字段,構造查詢語句來驗證。

使用哪個 AuthenticationHandler,需要在 deployerConfigContext.xml 中設定,預設情況下,CAS 使用一個簡單的 username=password 的 AuthenticationHandler,在檔案中可以找到如下一行:&lt;bean class="org.jasig.cas.authentication.handler.support.SimpleTestUsernamePassword

AuthenticationHandler" /&gt;,我們可以将其注釋掉,換成我們希望的一個 AuthenticationHandler,比如,使用QueryDatabaseAuthenticationHandler 或 SearchModeSearchDatabaseAuthenticationHandler 可以分别選取清單 5 或清單 6 的配置。

另外,由于存放在資料庫中的密碼通常是加密過的,是以 AuthenticationHandler 在比對時需要知道使用的加密方法,在 deployerConfigContext.xml 檔案中我們可以為具體的 AuthenticationHandler 類配置一個 property,指定加密器類,比如對于 QueryDatabaseAuthenticationHandler,可以修改如清單7所示:

其中 myPasswordEncoder 是對清單 8 中設定的實際加密器類的引用:

這裡 MyPasswordEncoder 是根據實際情況自己定義的加密器,實作 PasswordEncoder 接口及其 encode() 方法。

3. 部署依賴包

在以上配置完成以後,需要拷貝幾個依賴的包到 cas 應用下,包括:

将 cas-server-support-jdbc-3.1.1.jar 拷貝到 %CATALINA_HOME%/webapps/cas/ WEB-INF/lib 目錄。

資料庫驅動,由于這裡使用 DB2,将 %DB2_HOME%/java 目錄下的 db2java.zip (更名為 db2java.jar), db2jcc.jar, db2jcc_license_cu.jar 拷貝到 %CATALINA_HOME%/webapps/cas/WEB-INF/lib 目錄。對于其他資料庫,同樣将相應資料庫驅動程式拷貝到該目錄。

DataStore 依賴于 commons-collections-3.2.jar, commons-dbcp-1.2.1.jar, commons-pool-1.3.jar,需要到 apache 網站的 Commons 項目下載下傳以上 3 個包放進 %CATALINA_HOME%/webapps/cas/WEB-INF/lib 目錄。

CAS 提供了 2 套預設的頁面,分别為“ default ”和“ simple ”,分别在目錄“ cas/WEB-INF/view/jsp/default ”和“ cas/WEB-INF/view/jsp/simple ”下。其中 default 是一個稍微複雜一些的頁面,使用 CSS,而 simple 則是能讓 CAS 正常工作的最簡化的頁面。

在部署 CAS 之前,我們可能需要定制一套新的 CAS Server 頁面,添加一些個性化的内容。最簡單的方法就是拷貝一份 default 或 simple 檔案到“ cas/WEB-INF/view/jsp ”目錄下,比如命名為 newUI,接下來是實作和修改必要的頁面,有 4 個頁面是必須的:

casConfirmView.jsp: 當使用者選擇了“ warn ”時會看到的确認界面

casGenericSuccess.jsp: 在使用者成功通過認證而沒有目的Service時會看到的界面

casLoginView.jsp: 當需要使用者提供認證資訊時會出現的界面

casLogoutView.jsp: 當使用者結束 CAS 單點登入系統會話時出現的界面

CAS 的頁面采用 Spring 架構編寫,對于不熟悉 Spring 的使用者,在修改之前需要熟悉該架構。

頁面定制完過後,還需要做一些配置進而讓 CAS 找到新的頁面,拷貝“ cas/WEB-INF/classes/default_views.properties ”,重命名為“ cas/WEB-INF/classes/ newUI_views.properties ”,并修改其中所有的值到相應新頁面。最後是更新“ cas/WEB-INF/cas-servlet.xml ”檔案中的 viewResolver,将其修改為如清單 9 中的内容。

單點登入的目的是為了讓多個相關聯的應用使用相同的登入過程,本文在講解過程中構造 2個簡單的應用,分别以 casTest1 和 casTest2 來作為示例,它們均隻有一個頁面,顯示歡迎資訊和目前登入使用者名。這 2 個應用使用同一套登入資訊,并且隻有登入過的使用者才能通路,通過本文的配置,實作單點登入,即隻需登入一次就可以通路這兩個應用。

假設 CAS Server 單獨部署在一台機器 A,而用戶端應用部署在機器 B 上,由于用戶端應用與 CAS Server 的通信采用 SSL,是以,需要在 A 與 B 的 JRE 之間建立信任關系。

準備好應用 casTest1 和 casTest2 過後,分别部署在 B 和 C 機器上,由于 casTest1 和casTest2,B 和 C 完全等同,我們以 casTest1 在 B 機器上的配置做介紹,假設 A 和 B 的域名分别為 domainA 和 domainB。

将 cas-client-java-2.1.1.zip 改名為 cas-client-java-2.1.1.jar 并拷貝到 casTest1/WEB-INF/lib目錄下,修改 web.xml 檔案,添加 CAS Filter,如清單 10 所示:

對于所有通路滿足 casTest1/protected-pattern/ 路徑的資源時,都要求到 CAS Server 登入,如果需要整個 casTest1 均受保護,可以将 url-pattern 指定為“/*”。

參數名

作用

edu.yale.its.tp.cas.client.filter.loginUrl

指定 CAS 提供登入頁面的 URL

edu.yale.its.tp.cas.client.filter.validateUrl

指定 CAS 提供 service ticket 或 proxy ticket 驗證服務的 URL

edu.yale.its.tp.cas.client.filter.serverName

指定用戶端的域名和端口,是指用戶端應用所在機器而不是 CAS Server 所在機器,該參數或 serviceUrl 至少有一個必須指定

edu.yale.its.tp.cas.client.filter.serviceUrl

該參數指定過後将覆寫 serverName 參數,成為登入成功過後重定向的目的位址

edu.yale.its.tp.cas.client.filter.proxyCallbackUrl

用于目前應用需要作為其他服務的代理(proxy)時擷取 Proxy Granting Ticket 的位址

edu.yale.its.tp.cas.client.filter.authorizedProxy

用于允許目前應用從代理處擷取 proxy tickets,該參數接受以空格分隔開的多個 proxy URLs,但實際使用隻需要一個成功即可。當指定該參數過後,需要修改 validateUrl 到 proxyValidate,而不再是 serviceValidate

edu.yale.its.tp.cas.client.filter.renew

如果指定為 true,那麼受保護的資源每次被通路時均要求使用者重新進行驗證,而不管之前是否已經通過

edu.yale.its.tp.cas.client.filter.wrapRequest

如果指定為 true,那麼 CASFilter 将重新包裝 HttpRequest,并且使 getRemoteUser() 方法傳回目前登入使用者的使用者名

edu.yale.its.tp.cas.client.filter.gateway

指定 gateway 屬性

CAS 在登入成功過後,會給浏覽器回傳 Cookie,設定新的到的 Service Ticket。但用戶端應用擁有各自的 Session,我們要怎麼在各個應用中擷取目前登入使用者的使用者名呢?CAS Client 的 Filter 已經做好了處理,在登入成功後,就可以直接從 Session 的屬性中擷取,如清單 11 所示:

在 JSTL 中擷取使用者名的方法如清單 12 所示:

另外,CAS 提供了一個 CASFilterRequestWrapper 類,該類繼承自HttpServletRequestWrapper,主要是重寫了 getRemoteUser() 方法,隻要在前面配置 CASFilter 的時候為其設定“ edu.yale.its.tp.cas.client.filter.wrapRequest ”參數為 true,就可以通過 getRemoteUser() 方法來擷取登入使用者名,具體方法如清單 13 所示:

在 casTest1 和 casTest2 中,都有一個簡單 Servlet 作為歡迎頁面 WelcomPage,且該頁面必須登入過後才能通路,頁面代碼如清單 14 所示:

在上面所有配置結束過後,分别在 A, B, C上啟動 cas, casTest1 和 casTest2,按照下面步驟來通路 casTest1 和 casTest2:

本文介紹了 CAS 單點登入解決方案的原理,并結合執行個體講解了在 Tomcat 中使用 CAS 的配置、部署方法以及效果。CAS 是作為開源單點登入解決方案的一個不錯選擇,更多的使用細節可以參考 CAS 官方網站。