【轉載】CAS概述及單點登錄介紹

構建和實現單點登錄解決方案

將一個開放源碼的基於 Java 的身份驗證組件集成進 Web 門戶中


我在自己的工作中發現,對各種門戶應用程序的需求正在增長。門戶的技術和功能性需求變得越來越複雜了。儘管出現了可以構建簡單門戶的工具,但是門戶與遠程或遺留數據源的集成問題仍然不容易解決。其中一個問題就是身份驗證。

身份驗證是個複雜的問題。門戶需要向後端數據源和應用程序驗證用戶的身份,但是這些應用程序可能具有互不相同的底層安全基礎設施。理想的最高效的身份驗證解決方案是單點登錄(single sign-on,SSO) 解決方案;在這種解決方案中,用戶只需要登錄一次,就可以向所有網絡資源驗證他的身份。

最近,在構建一個需要 SSO 的教育門戶時,我研究了許多商業的和開放源碼的 SSO 解決方案。在本文中,我將一步步地介紹使用免費的 SSO 實現(來自 Yale University 的 CAS)構建簡單 SSO 系統的過程。

爲什麼要選擇單點登錄?

有多少人實現過自己的身份驗證機制(常常是某種簡單的數據庫查詢)?您是否常常考慮創建和管理用戶帳號所需的工作流?在任何開發項目中,身份驗證都是很常見的任務。如果幸運的話,公司已經有一些通用的身份驗證類或庫。但是,這個任務常常被忽視,被當作只在後臺發生的微不足道的事情。

一般來說,公司往往沒有一致的身份驗證策略或可靠的身份驗證框架。隨着時間的推移,這會導致大量應用程序具有自己的身份驗證需求和用戶存儲庫。每個人都需要記住多個用戶名和密碼,才能訪問網絡上的不同應用程序。這給管理和支持部門帶來很大的負擔 —— 必須在每個應用程序中爲每個職員設置帳號,當用戶忘記密碼時還要幫助他們解決問題,等等。

身份驗證是多種應用程序、平臺和基礎設施共有的需求。一般來說,一個用戶應該不需要多個用戶名。理想情況下,他應該只需要證明自己的身份一次,然後就能夠訪問所有已經得到授權的網絡資源。

SSO 的目標是,讓用戶能夠通過一次登錄訪問所有應用程序。它提供一個統一的機制來管理用戶的身份驗證,並實現業務規則來決定用戶對應用程序和數據的訪問。

在討論單點登錄的技術細節之前,先談談單點登錄的一些好處和風險。好處包括:

  • 提高用戶的效率。用戶不再被多次登錄困擾,也不需要記住多個 ID 和密碼。另外,用戶忘記密碼並求助於支持人員的情況也會減少。
  • 提高開發人員的效率。SSO 爲開發人員提供了一個通用的身份驗證框架。實際上,如果 SSO 機制是獨立的,那麼開發人員就完全不需要爲身份驗證操心。他們可以假設,只要對應用程序的請求附帶一個用戶名,身份驗證就已經完成了。
  • 簡化管理。如果應用程序加入了單點登錄協議,管理用戶帳號的負擔就會減輕。簡化的程度取決於應用程序,因爲 SSO 只處理身份驗證。所以,應用程序可能仍然需要設置用戶的屬性(比如訪問特權)。

對於單點登錄,經常提到的一些問題包括:

  • 難以重構。對 SSO 解決方案進行重構來適應現有的應用程序很困難,很耗費時間而且昂貴。
  • 無人看守的桌面。實現 SSO 會減少一些安全風險,但是也增加了其他安全風險。例如,如果用戶登錄之後離開了他的機器,惡意用戶就可以訪問他的資源。儘管這是一個普遍存在的安全問題,但是 SSO 會使這個問題更加嚴重,因爲惡意用戶可以訪問所有獲得授權的資源。在採用多次登錄方式時,用戶每次只能登錄進一個系統,所以只有一個資源被泄露。
  • 單點攻擊。在使用單點登錄時,所有應用程序使用一個集中的身份驗證服務。對於希望實施拒絕服務攻擊的黑客,這是一個有吸引力的目標。

所以,SSO 並非毫無缺點。但是我相信,在用戶、管理員和開發人員看來,它的優點要超過缺點。

SSO 開放源碼項目

正如前面提到的,我當前正在爲一個教育機構構建 Web 門戶。這個門戶將爲參與遠程課程的學生提供一個在線學習環境。

這個門戶的構造塊已經就位了。站點已經建立了,課程內容已經開發出來了,虛擬學習環境也就位了,輔助應用程序(比如日記、日曆、電子郵件和筆記本)已經構建好或已經取得。所有這些組件都正在由學生使用,而且每個應用程序都運行在自己的服務器上。

客戶現在希望能夠通過 Web 瀏覽器遠程訪問這些應用程序,所以要構建一個門戶來提供訪問應用程序的單一入口,門戶應該向用戶提供單點登錄功能。用戶登錄進門戶之後,對他的身份進行驗證,然後他就能夠訪問門戶中所有已授權的資源。

我決定搜索一下關於如何實現 SSO 方案的信息,尋找有幫助的白皮書、產品和開放源碼項目。我使用以下條件來限制搜索的範圍:

  • Java 實現。這個門戶應用程序基礎設施是基於 Java 的,所以我希望 SSO 實現也是基於 Java 語言的。
  • 容易實現。在這個場景中,容易實現是指不需要對基礎設施或現有的應用程序做大量修改。
  • 其效果已經得到證明。它應該已經被一些大型組織採用,而且仍然處於活躍的開發階段。
  • 與 LDAP 兼容。我們的客戶使用 Microsoft Active Directory Server,所以我需要系統能夠輕鬆地根據 ADS 進行身份驗證。

我很高興地發現了幾個出色的開放源碼項目。(參見 參考資料 中的列表。)

我開始使用來自 Yale University 的 CAS(Central Authentication Service)系統開發一個原型,因爲它滿足我的所有條件。它是基於 Java 的,源代碼是開放的。只需使用 JSP 標記和 servlet 過濾器,就能夠相當輕鬆地在 Java 應用程序環境中實現它。Yale University 正在使用它,這說明它的品質滿足我的條件。它還允許輕鬆地改變或擴展實際的身份驗證機制(無論是查詢數據庫還是 LDAP 服務器上的用戶名和密碼)。

CAS 概述

注意,在採用 CAS 協議時,應用程序不會看到用戶的密碼。CAS 服務器執行身份驗證,只有它能夠看到用戶的密碼。這會增強安全性,因爲用戶名和密碼並不通過網絡傳遞給其他應用程序。

下圖說明了在集成了 CAS 服務器的系統中身份驗證是如何執行的。

圖 1. CAS 協議如何執行身份驗證
CAS 協議如何執行身份驗證

下面是這個身份驗證協議中的主要步驟。

  1. 用戶嘗試使用應用程序的 URL 訪問應用程序。用戶被重定向到 CAS 登錄 URL,採用的是 HTTPS 連接,他請求的服務的名稱作爲參數傳遞。這時向用戶顯示一個用戶名/密碼對話框。
  2. 用戶輸入 ID 和密碼,CAS 對他進行身份驗證。如果身份驗證失敗,目標應用程序根本不會知道這個用戶曾經試圖訪問它 —— 用戶在 CAS 服務器上就被攔住了。
  3. 如果身份驗證成功,CAS 就將用戶重定向回目標應用程序,並在 URL 中附加一個稱爲 ticket 的參數。然後,CAS 嘗試創建一個稱爲 ticket-granting cookie 的內存 cookie。這是爲了以後進行自動的重新驗證;如果存在這個 cookie,就表示這個用戶已經成功地登錄了,用戶就不需要再次輸入他的用戶名和密碼。
  4. 然後,應用程序要檢查這個 ticket 是否正確,以及是否代表一個有效用戶;檢查的方法是,打開一個 HTTPS 連接來調用 CAS serviceValidate URL,並作爲參數傳遞 ticket 和服務名稱。CAS 檢查這個 ticket 是否有效,以及是否與請求的服務相關聯。如果檢查成功,CAS 就將用戶名返回給應用程序。

如果採用 Servlet 2.3 規範進行開發,那麼甚至不需要爲這些步驟操心。一個 servlet 過濾器會處理整個協議。您要做的只是在 web.xml 文件中配置過濾器參數。我就採用這種方式 —— 它意味着對門戶中應用程序代碼的修改非常少。

對 CAS 的深入討論超出了本文的範圍,我建議您閱讀 參考資料 中列出的來自 Yale University 的文章,從而判斷這種身份驗證方案是否滿足您的需要。

CAS 入門

設置 CAS 軟件是非常簡單的,但是在開始設置之前,您應該瞭解我用的一些軟件。我只用 Tomcat 4.1、Java Development Kit 1.4 和 Ant 1.5 測試了 CAS。(可以從 參考資料 下載這裏提到的文件和客戶機庫。)

首先,下載 CAS 服務器和客戶機庫。已經針對許多語言和環境開發了客戶機庫,包括 Java、ASP、Perl、PHP 和 PL/SQL。

CAS 使用 HTTPS,所以必須在 Tomcat 中啓用這個功能。我發現這需要點兒技巧,但是如果按照我提供的說明(readme_tomcat_ssl.txt 文件)去做,應該不困難。

對 CAS 服務器 ZIP 文件進行解壓,並使用 Ant 構建腳本構建 CAS 服務器軟件。將 WAR 文件(Web Archives)部署到 Tomcat 的 /webapps 目錄中。在啓動 Tomcat 時,用 WAR 文件在 Tomcat/webapps 中創建一個 CAS 目錄。

下載 CAS 客戶機庫。對 ZIP 文件進行解壓,就會看到許多目錄。我要使用的是 Java 客戶機庫。同樣,也提供了 Ant 構建腳本。運行這個構建腳本。這會生成一個稱爲 casclient.jar 的 JAR 文件。將這個文件複製到 Tomcat 根目錄下的 common/lib 目錄中。

現在,需要配置應用程序來使用 CAS。本文中用來進行演示的應用程序是 Tomcat 提供的可靠的 “HelloWorld” servlet 示例。這個應用程序應該在 Tomcat 系統中的 /webapps/examples 目錄下面。修改 web.xml 文件來配置 servlet 過濾器。

HelloWorld JSP 的 web.xml 文件包含下面的 servlet 過濾器配置。它對 HTTPS 使用本地主機和端口 8443。根據自己的配置修改這些設置。我提供的 zip 文件中包含一個 web.xml 文件示例。

清單 1. HelloWorld JSP 的默認 servlet 過濾器配置
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
<filter>
    <filter-name>CAS Filter</filter-name>
    <filter-class>edu.yale.its.tp.cas.client.filter.CASFilter</filter-class>
    <init-param>
      <param-name>edu.yale.its.tp.cas.client.filter.loginUrl</param-name>
      <param-value>https://localhost:8443/cas/login</param-value>
    </init-param>
    <init-param>
      <param-name>edu.yale.its.tp.cas.client.filter.validateUrl</param-name>
      <param-value>https://localhost:8443/cas/proxyValidate</param-value>
    </init-param>
    <init-param>
      <param-name>edu.yale.its.tp.cas.client.filter.serverName</param-name>
      <param-value>localhost</param-value>
    </init-param>
  </filter>
 
  <filter-mapping>
    <filter-name>CAS Filter</filter-name>
    <url-pattern>/*</url-pattern>
  </filter-mapping>

啓動 Tomcat。然後輸入 URL http://localhost/examples/servlet/HelloWorldExample。這時會重定向到 CAS 登錄屏幕。默認的身份驗證器只要求爲用戶名和密碼提供相同的字符串,例如在兩個域中都輸入 demo。然後,就會重定向到 HelloWorld 頁面。

這只是個簡單的 CAS 演示,但是它說明,通過使用這個強大的 servlet 過濾器,讓現有的 Java servlet 應用程序使用 CAS 是多麼容易。還可以使用 JSP 標記集替代 servlet 過濾器,這種方法適合那些無法使用 servlet 過濾器的應用程序或應用服務器。

Active Directory Server 身份驗證

在我的教育門戶項目中,可以很輕鬆地修改應用程序,因爲我能夠得到源代碼。我在每個應用程序中使用了 servlet 過濾器。在這種情況下,當用戶首次訪問門戶並請求一個受限制的應用程序或數據源時,CAS 將執行身份驗證。身份驗證成功之後,CAS servlet 過濾器可以檢查 ticket,並將用戶名作爲參數傳遞給請求的應用程序。我修改了現有的應用程序登錄屏幕,讓它們在成功的身份驗證之後從 CAS 接收用戶名參數。然後,應用程序可以使用這個參數對用戶進行授權,並對用戶的活動進行審計記錄。

但是,CAS 本身沒有附帶任何真正有用的身份驗證器。默認的身份驗證器僅僅檢查用戶名和密碼是否相同。在教育門戶項目中,我使用 Microsoft 的 Active Directory Server 存儲用戶的個人信息。所以我需要擴展 CAS 服務器,讓它根據 Active Directory Server 檢查用戶名和密碼。

Yale University 的 CAS 設計人員開發了一個可插入的身份驗證器架構。通過擴展 PasswordHandler 接口,開發人員可以創建一個類來實現密碼檢查邏輯。

清單 2. CAS 的可插入身份驗證器架構
1
2
3
4
5
6
7
8
9
10
11
12
/** Interface for password-based authentication handlers. */
public interface PasswordHandler extends AuthHandler {
 
  /**
   * Authenticates the given username/password pair, returning true
   * on success and false on failure.
   */
  boolean authenticate(javax.servlet.ServletRequest request,
                       String username,
                       String password);
 
}

我要做的只是瞭解如何使用 Active Directory Server 檢查用戶名和密碼,創建一個類來擴展這個接口,並添加執行身份驗證的代碼。

ADS 是一種與 LDAP3 兼容的目錄服務器。它支持許多不同的身份驗證方法,主要包括匿名(anonymous)簡單(simple)和 SASL(Simple Authentication and Security Layer)。匿名身份驗證不適合我的項目;簡單身份驗證用明文發送密碼,這也不滿足需要。所以我使用 SASL。

SASL 支持可插入的身份驗證。這意味着可以對 LDAP 客戶機和服務器進行配置,讓它們相互協商並使用多種身份驗證機制之一。下面是當前定義的一部分 SASL 機制:

  • Anonymous
  • CRAM-MD5
  • Digest-MD5
  • External
  • Kerberos V4
  • Kerberos V5
  • SecurID
  • Secure Remote Password
  • S/Key
  • X.509

在這些機制中,流行的 LDAP 服務器(比如 Sun、OpenLDAP 和 Microsoft 提供的服務器)都支持 External、Digest-MD5 和 Kerberos V5。

CAS 本身與 Kerberos 相似,它們有許多相同的概念,比如 ticket 和 ticket-granting ticket(在 CAS 中實際上稱爲 ticket-granting cookie),它們的協議也是相似的。所以,選擇 Kerberos 機制似乎是很自然的。另外,添加對 Kerberos 身份驗證的支持讓 CAS 在未來的開發中能夠作爲基於 Web 的 Kerberos 代理代表用戶,這使 CAS 能夠替用戶管理 Kerberos ticket 的所有方面。這意味着遠程用戶也可以使用相同的 Kerberos 身份驗證機制訪問本地和網絡資源。

Kerberos 協議已經內置在 ADS 和 Windows 2000/XP 中。Java Authentication and Authorization Service(JAAS)提供了 Kerberos Login Module 的一個實現(參考資料 中列出的一個教程詳細描述瞭如何建立一個示例應用程序)。精確地說,它是 GSS-API SASL 機制,但是它只爲 LDAP3 服務器提供 Kerberos v5 身份驗證。

Kerberos 身份驗證是一個很簡單的過程(如果仔細地按照以下說明進行操作的話):

  1. 在 JAAS 配置文件中爲應用程序類配置 Login Module。
    1
    2
    3
    4
    edu.yale.its.tp.cas.auth.provider.KerberosAuthHandler 
    {
    com.sun.security.auth.module.Krb5LoginModule required client=TRUE;
    };
  2. 創建一個 LoginContext,傳遞執行身份驗證的類的名稱和 CallBackHandler 對象。
    1
    2
    LoginContext lc = new LoginContext(CASApp.class.getName(),
                        new CASCallbackHandler());
  3. 調用 login() 方法來執行身份驗證。如果執行過程沒有異常,身份驗證就成功了。如果拋出了異常,那麼這個異常會指出失敗的原因。

要想深入瞭解 Kerberos 身份驗證,建議您利用本文末尾的參考資料。(我還提供了自己的實現和配置文件,KerberosAuthHandler 和 CASCallBackHandler。)

需要創建一個新的 PasswordHandler 實現,KerberosAuthHandler,它按照上面的方法根據使用 Kerberos v5 的 Active Directory Server 進行身份驗證。

清單 3. 創建新的 PasswordHandler 實現
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
public class KerberosAuthHandler implements PasswordHandler {
 
  public boolean authenticate(javax.servlet.ServletRequest request,
                                String username,
                                String password)
  {
 
    LoginContext lc = null;
     
    try
    {
    /* Set up the Callback handler, and initialise */
 /* the userid and password fields */
 
       CASCallbackHandler ch = new CASCallbackHandler();
       ch.setUserId(username);
       ch.setPassword(password);
         
       /* Initialise the login context - LoginModule configured */
       /* in cas_jaas.conf and */
              /* set to use Krb5LoginModule. */
       lc = new LoginContext(KerberosAuthHandler.class.getName(), ch);
 
       /* Perform the authentication */
       lc.login();
 
    }
    catch (LoginException le)
    {
     System.err.println("Authentication attempt failed" + le);
     return false;
    }
    return true;
 
  }
}

在創建 LoginContext 時,傳遞這個類名和 CASCallbackHandler 對象。JAAS 配置文件指定這個類使用的登錄模塊。

在調用 login() 方法時,登錄模塊知道爲了進行身份驗證它需要從用戶/應用程序獲得哪些信息。在使用 Kerberos 時,它需要用戶名和密碼,所以它構造一個數組,其中包含兩個回調對象(NameCallback 和 PasswordCallback),然後調用 CallbackHandler 對象,這個對象決定如何執行這些回調並獲得用戶名和密碼。

我採用最簡單最有利的方式,使用 CallbackHandler 上的 setter 方法顯式地設置用戶 ID 和密碼。然後,CallbackHandler將它們傳遞給 NameCallback 和 PasswordCallback 對象。

清單 4. 用 setName (CASUserId) 和 setPassword (CASPassword) 設置 ID 和密碼
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
public class CASCallbackHandler implements CallbackHandler
{
 
    private String CASUserId;
    private char [] CASPassword;
 
    public void handle(Callback[] callbacks)
    throws java.io.IOException, UnsupportedCallbackException {
        for (int i = 0; i < callbacks.length; i++) {
        if (callbacks[i] instanceof NameCallback) {
            NameCallback cb = (NameCallback)callbacks[i];
            cb.setName(CASUserId);
 
        } else if (callbacks[i] instanceof PasswordCallback) {
            PasswordCallback cb = (PasswordCallback)callbacks[i];
            cb.setPassword(CASPassword);
 
        } else {
            throw new UnsupportedCallbackException(callbacks[i]);
        }
        }
    }
 
    public void setUserId(String userid)
    {
    CASUserId = userid;
    }
 
    public void setPassword(String password)
    {
    CASPassword = new char[password.length()];
    password.getChars(0, CASPassword.length, CASPassword, 0);
    }

下面要做的是讓 CAS 使用新的身份驗證處理器。方法是在 webapps/cas 中 CAS 服務器的 web.xml 文件中進行以下設置:

清單 5. 讓 CAS 使用新的身份驗證處理器
1
2
3
4
5
6
7
<!-- Authentication handler -->
    <context-param>
        <param-name>edu.yale.its.tp.cas.authHandler</param-name>
        <param-value>
             edu.yale.its.tp.cas.auth.provider.KerberosAuthHandler
        </param-value>
    </context-param>

ZIP 文件(參考資料 中的 KerberosAuthSrc.zip)中包含一個 web.xml 示例。

必須重新啓動 Tomcat,但是這一次還需要設置一些 Java 運行時屬性。對 ZIP 文件(KerberosAuthSrc.zip)進行解壓,並將文件 cas_jaas.conf、krb5.conf 和 setkerberosjvmoptions.bat 複製到 TOMCAT_HOME 目錄中。運行 setkerberosjvmoptions.bat,然後啓動 Tomcat。

現在,可以再用 HelloWorld 應用程序做實驗了。這一次,可以使用 Active Directory Server 中定義的有效的 Kerberos 用戶名和密碼對。

結束語

如果沒有統一的策略,開發人員就要爲每個網絡應用程序重複實現定製的安全機制。這會導致各種可伸縮性和維護問題。單點登錄解決方案爲安全性和身份驗證提供了統一的框架,這大大減輕了用戶、管理員和開發人員的負擔。

單點登錄的概念、技術和蘊涵對用戶和管理員而言很複雜,我在本文中只觸及了這個領域的皮毛。但是,我解釋瞭如何使用 Yale University 的 CAS 系統實現一個單點登錄方案,還詳細描述瞭如何擴展這種技術,從而對 LDAP 服務器用戶(具體地說,是使用 Kerberos 協議的 Active Directory Server)進行身份驗證。

原創地址:https://www.ibm.com/developerworks/cn/web/wa-singlesign/#artrelatedtopics

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章