Active Directory 與 Single Sign-On 的 Web 應用程式(兼談「雙躍點」問題)

摘要:Active Directory 與 Single Sign-On 的 Web 應用程式(兼談「雙躍點」問題)

在一個組織裡面,可能有許多獨立的小系統或應用程式,這些應用程式往往各自存放一份使用者資料,結果是使用者在執行每個應用程式時都還要輸入一次帳號密碼,而且帳號密碼可能還有很多組,造成使用者極大的不便,也增加了日後整合應用程式的困難。

如果使用者的帳號密碼只有一份,事情會簡單得多。這裡簡單介紹利用 Active Directory 來實現 Single Sign-On(以下簡稱 SSO) 的構想。也就是說,驗證使用者身份的動作主要是由 Active Directory(以下簡稱 AD)來負責。這意味著使用者的帳號、密碼是儲存在 AD 裡面,而不是應用程式的資料庫。這麼做的最大好處,是企業內部的使用者帳號、密碼資料是集中存放的,而不是每一個應用程式各自以不同的資料庫存放一份;而 且使用者只要登入作業系統之後,進入其他應用程式時都不再需要輸入帳號密碼。由於身分驗證是透過 AD 來做,因此這種架構只適用於企業內網,不適用於 Internet。

除了使用 AD 來驗證使用者身分,應用程式通常也會提供其他使用者帳戶的維護功能,例如:更改使用者密碼。那麼,當使用者在網頁上輸入新的密碼之後,我們的網頁程式要如 何通知 AD 更改使用者的密碼呢?這就得透過 LDAP 了。LDAP 是 Lightweight Directory Access Protocol 的縮寫,即輕量級目錄存取協定。簡單的說,LDAP 是一種目錄存取的標準協定,而 AD 則是符合 LDAP 規範的實作品。因此,我們只要知道如何在程式中呼叫 AD 提供的 LDAP 程式呼叫介面,就能對 AD 裡面的使用者帳號(或其他物件)進行新增、修改、刪除等維護動作。就 .NET 而言,這個 LDAP 程式呼叫介面就是 System.DirectoryServices 命名空間裡面的類別。這篇文章也會約略提到 LDAP 程式設計,以及 ASP.NET 應用程式在存取目錄服務時經常會碰到的權限不足的問題與解決方法。

作業環境:Windows 2003 + IIS + Active Directory
開發工具:Visual Studio 2005 + SQL Server 2005

設計

實 際的作法可能因為各人不同的考量而有細節上的差異,例如,有些人可能除了使用者的帳號、密碼之外,連其他屬性也存在 AD 裡面,像是使用者的群組和權限資料等等。這裡介紹的方法,則是只利用 AD 來儲存使用者的帳號、密碼,並且執行一些使用者帳戶的維護管理工作(如驗證身份、更改密碼),使用者的其他屬性和應用程式的權限資料則存在應用程式的資料 庫裡面。其運作架構如圖 1所示,使用者登入的情節則描述於後:

情節:使用者登入

先決條件:使用者以登入 Windows 網域的方式登入作業系統。

正常路徑:

  1. 使用者開啟 IE,進入 Web 應用程式的首頁(或任何需要驗證身分才能存取的網頁),這裡假設使用者要求的頁面是 Default.aspx。
  2. 接著 IE 會把使用者的憑證傳遞給 IIS 進行身分驗證(所以 Web 應用程式在 IIS 中的安全機制要採用整合式 Windows驗證)。
  3. IIS 再把使用者憑證傳遞給 AD 進行驗證。若驗證成功,控制權便進一步轉移至 ASP.NET runtime(ASP.NET 應用程式的驗證方式必須設定為 Windows),通過 ASP.NET 的驗證與授權檢查之後,用戶端發出的 HTTP request 始進入應用程式的網頁(Default.aspx)。
  4. 應用程式的 Default.aspx 接著取得目前的(已經驗證通過的)使用者帳號,並且到資料庫中取得該使用者的其他屬性和權限資料。
  5. 根據 user 的權限建立首頁的功能選單。

簡 單起見,這裡就不詳列替代路徑了(例如:身分驗證失敗)。不過,有一個地方值得進一步討論,就是步驟 4──以目前的使用者帳號到資料庫中取得該使用者的其他屬性和權限資料。這意味著資料庫中必定事先存在一筆使用者的基本資料,其中可能包含所屬部門、出生 日期、和權限資料等,但不包含密碼(因為密碼是存在 AD 裡面)。可是萬一網域管理員在 AD 裡面建立了一名新的使用者,卻沒有在應用程式中建立對應的使用者資料,該怎麼辦?這突顯了應用程式的資料庫與 AD 的使用者帳戶同步的問題。你可能會碰到以下兩種情形:

Case 1:AD 中有該名使用者,但應用程式的資料庫中沒有。

碰到這種情況,你的應用程式可以自動在資料庫中增加一名對應的使用者資料,並存入預設的基本屬性。或者,你的應用程式可以告訴使用者:「本系統沒有您的帳戶資料,請通知系統管理員。」並且禁止他繼續使用應用程式,等到系統管理員幫他建立好基本資料才能使用。

Case 2:AD 中沒有該名使用者,但應用程式的資料庫中有。

這 種情況有可能是因為網域管理員將該使用者帳戶刪除了,卻沒有到應用程式中刪除對應的使用者資料。這種情況對應用程式的影響應該不大,因為在 IIS 驗証使用者身份時就會失敗了,資料庫中只是存放了多餘的使用者資料罷了。管理員也可以寫一支工具程式把資料庫中所有多餘的使用者資料刪除。

聽起來不是很完美?當你想要把使用者的帳戶資料集中存放在某個地方,那麼其他應用程式必定會有其他附加的使用者資訊必須與之關連,如何保持兩邊的使用者資料同步,是應用程式必須處理的問題。

其他應用程式也可以利用上述作法完成使用者身份驗證的工作。由於所有使用者帳號都存在 AD 裡面,身分驗證的工作都由 AD 負責完成,應用程式幾乎不用寫什麼程式碼就能實現 Single Sign-On。

實作

以上的情節實作起來並不困難,因為身分驗證的部份都由 IIS 和 AD 幫我們完成了。步驟 2~3 主要是需要設定 IIS 和應用程式的 Web.config。假設 Web 應用程式名稱為 SsoWeb,則 IIS 的設定步驟如下:

  1. 先開啟 IIS 管理員,然後從左邊的樹狀結構展開本機電腦  網站  預設的網站,然後在 SsoWeb 上點右鍵,選「內容」。
  2. 切到「目錄安全設定」頁籤,點第一個「編輯」鈕,如圖 2所示。
  3. 指定驗證方法為「整合式 Windows 驗証」,並且禁止匿名存取(取消「啟用匿名存取」核取方塊)。參考圖 3。


比 較需要寫程式的地方,是第 4 和第 5 個步驟。步驟 4 的關鍵處理,是在 Default.aspx.cs(或 Default.aspx.vb)的 Page_Load 事件中取得目前登入的使用者帳號,再到資料庫中取出該使用者的應用程式權限或其他屬性資料。你可以用以下程式碼取得目前登入的使用者帳號:

        string userName = HttpContext.Current.User.Identity.Name;

        int i = userName.LastIndexOf(@"\");

        if (i >= 0)

        {

            userName = userName.Substring(i + 1);

        }

由 於取得的使用者名稱可能包含網域,但我的應用程式的資料庫中記錄的使用者名稱並未包含網域名稱,因此必須先把網域名稱去掉,以利後續的資料處理。至於後續 的處理動作,包括第 5 步驟的產生應用程式主選單的部份,由於每個應用程式可能有不同的作法,而且超出本文的主題範圍,就不再說明細節了。

更改密碼

除 了驗證身份,SSO 的應用程式也可能提供基本的使用者帳戶管理的功能,例如更改使用者密碼。如果你想要在 Web 應用程式中提供使用者更改密碼的功能,而且密碼是儲存在 AD 裡面,你可以透過 LDAP API 來達成此目的。.NET 應用程式可以利用 System.DirectoryServices 命名空間的類別(以下簡稱 SDS)來呼叫 LDAP API。由於單單利用 SDS 需要撰寫很多程式碼,因此我另外寫了一個 DSHelper 元件,把常用的功能封裝在這個元件裡面。以下就是在 Web 應用程式中呼叫 DSHelper 元件來變更 AD 使用者密碼的程式片段:
 

    /// <summary>

    /// 變更 AD 使用者密碼。

    /// </summary>

    /// <param name="uid">使用者帳戶</param>

    /// <param name="pwd">新的密碼</param>

    private void ChangeADUserPassword(string uid, string pwd)

    {
        string domainName = "MyCompany.com.tw";

        string ldapPath = "LDAP://” +;
        string domainAdmin = "Administrator";
        string domainAdminPW = "p@ssw0rd”;

 

        DSHelper.DSManager manager = new DSHelper.DSManager(

            "LDAP://" + domainName,

            domainAdmin, domainAdminPW,

            AuthenticationTypes.Secure,

            domainName);

        try

        {

            DSHelper.DSUser user = manager.LoadUser(uid);

            user.SetPassword(pwd);

        }

        finally

        {

            manager.Disconnect();

        }

    }

注 意ChangeADUserPassword 函式只有兩個參數:使用者帳戶(uid)和新密碼(pwd),它並沒有要求傳入舊的密碼。這是因為應用程式根本不可能取得使用者的密碼。因此,程式當中先 用網域管理員的身分與 LDAP 繫結,然後直接設定該使用者的密碼(網域管理員可以直接修改使用者的密碼,無須提供使用者原先的密碼)。

DSHelper 元件是我參考網路上蒐集來的一些範例和函式庫原始碼修改而成,你可以按這裡下載完整原始碼

要特別聲明的是,其中有些函式尚未經過測試,也有許多尚待改進之處,請小心使用。

安全性的議題

在 撰寫 LDAP 應用程式時,最常碰到的麻煩就是安全性的問題,尤其是當你的 ASP.NET 應用程式採用整合式 Windows 驗證時,在存取遠端 AD 伺服器資源時,更容易發生權限不足的情形。例如前面提過的更改 AD 使用者密碼,看似簡單的過程,其實牽涉到非常多需要考慮的問題。例如,使用者在 Web 應用程式的更改密碼網頁中輸入的新密碼可能不符合網域安全性設定的帳戶密碼原則,包括:密碼的複雜度、密碼長度、以及有效期限。因此,應用程式必須在呼叫 LDAP API 時特別仔細處理可能發生的錯誤,以免使用者搞不清楚為什麼密碼變更失敗。然而,底層的 LDAP API 傳回的錯誤訊息卻常常很含糊籠統,令程式很難判斷呼叫失敗的原因究竟是什麼,這是其中一個麻煩。

另一個麻煩是 Web 應用程式在存取系統資源時經常碰到的「雙躍點」 (double-hop)問題。以之前更改密碼的例子來說,該程式碼若放在 Windows 應用程式中,可以順利更改使用者密碼,可是同樣的程式碼放在 ASP.NET 程式裡執行,就可能發生更改密碼失敗的情形。為什麼會這樣?當使用者執行某個 Windows 應用程式時,作業系統是以當時登入的使用者(亦稱為互動使用者)的身分來執行該應用程式,並且以該使用者的權限來決定應用程式是否能夠存取系統資源。因 此,使用者只要有足夠的權限,他就可以順利執行 Windows 應用程式的任何動作。

ASP.NET 應用程式的情況就比較複雜一點了,它會有「雙躍點」的問題。第一個跳躍點,是當使用者從 IE 發出 request 給 IIS 時。如前面說過的,此時 IE 會將使用者憑證傳遞給 IIS 進行驗證,驗證成功後,最後控制權會轉移到 ASP.NET 網頁程式。如果 ASP.NET 網頁程式中有呼叫 LDAP API 去存取遠端 AD 伺服器的資源,這就是第二個跳躍點。由於 Windows 預設不允許任何服務將使用者的身分傳遞給遠端的伺服器,因此位於 IIS 機器上的 ASP.NET 應用程式在存取遠端 AD 伺服器的資源時,是以匿名使用者與 AD 伺服器驗證。匿名使用者權限很低,因此大部分對 AD 的存取操作都會失敗。

當你碰到同一段程式 碼可以在 Windows 應用程式中順利執行,在 ASP.NET 程式中執行卻會發生錯誤的情況,八成就是因為「雙躍點」的緣故。此時你可以利用模擬(impersonate)使用者身份的方式解決,例如在 web.config 裡面利用 <identity> 標籤指定欲模擬的使用者,例如:

<identity impersonate="true" userName="MyDomain\Administrator" password="123" />

如 果照上面的設定,當 ASP.NET 程式在呼叫 LDAP API 存取 AD 資源時,就會以網域管理員的身分去存取 AD 資援,因此一定不會有權限不足的情形。當然啦,這樣也會引發另一個安全性的問題──你的 Web 應用程式的權限太大了,駭客或其他有心人士可能利用你的 Web 應用程式為非作歹。所以在利用模擬的機制時,也要考慮一下要模擬哪個特定的使用者,或者是直接模擬前端發出 request 的使用者。

其他方法

Single Sign-On 的機制,基本上只是提供一個集中存放使用者帳號資料的地方,並且提供驗證使用者身份和其他管理使用者帳號的服務。實現的方法很多,以上介紹的只是其中一種可能的作法而已。

與 AD 有點類似的,是 ADAM(Active Directory Application Mode)。ADAM 是微軟提供的一個獨立的目錄服務,它和 AD 一樣支援 LDAP,但是沒有 AD 的一些網域功能。當你的系統需要儲存的使用者帳戶數量很龐大,而且大部分的使用者都不需要作業系統的功能時,就可以使用 ADAM。若是這樣,前面的「使用者登入」情節的步驟 3 就要自己寫程式來驗證使用者身份了──你可以利用 LDAP API 來驗證使用者。 使用 ADAM 的好處,是你依然可以用標準的 LDAP 來存取和管理使用者帳戶,而且你的 Web 應用程式不一定要用 Windows 驗證。你可以用 Forms 驗証,只要把使用者輸入的帳號密碼透過 LDAP 向 ADAM 驗証身分即可,這讓你的 SSO Web 應用程式可以適用於 Internet 環境。

另一種可能的方法是自己實作 SSO 的服務。如果你不想用 AD 或 ADAM 來儲存使用者帳戶資料,你也可以自己實作一個 SSO 的服務。你可能會有一台專門用來驗證使用者身份的伺服器,此伺服器上安裝了一個用來驗證身份的 Web service,以提供驗證使用者、更改密碼、和其他維護使用者的服務。而使用者帳戶資料則可能全部集中存放在一個資料庫中。若採用這種方式,你可以完全 依照自己的需求來實作,但相對的也需要花比較多工夫。另一個缺點是,使用者帳戶資料若存放在關聯式資料庫中,不像 LDAP 那麼容易展現與存取階層式的資料(例如使用者與部門或角色的關係)。

結論

透 過 AD 驗證使用者的主要優點,除了可以輕易達到 Single Sign-On(僅限於企業內網的應用程式),應用程式也不需要寫什麼驗證身份的程式碼。不過,當應用程式需要提供管理使用者帳戶的功能時,例如:新增使 用者、變更使用者密碼等,開發人員就需要學習 LDAP 程式設計。個人以為,LDAP 程式設計與 Windows 安全性的觀念與運作機制才是採用 AD 實現 SSO 的主要技術門檻,本文只能算是起了個頭,如果您需要這方面的資料,除了參考前面提過的 DSHelper 元件,有兩本書我覺得很不錯,應該會有幫助:

The .NET Developer's Guide to Directory Services Programming
by Joe Kaplan and Ryan Dunn. Addison Wesley, 2006.

The .NET Developer's Guide to Windows Security

by Keith Brown. Addison Wesley, 2005.