建立使用「表單驗證」來針對 Active Directory 驗證使用者的 Web 應用程式。
• 從 Active Directory 取得驗證使用者所屬之群組及通訊群組清單的清單
• 使用 HttpContext.Current.User 屬性建立與使用者 Web 要求相關的 GenericPrincipal 物件。
    
目標
透過此單元即可:
| • | 
 建立使用「表單驗證」來針對 Active Directory 驗證使用者的 Web 應用程式。  | 
| • | 
 從 Active Directory 取得驗證使用者所屬之群組及通訊群組清單的清單  | 
| • | 
 使用 HttpContext.Current.User 屬性建立與使用者 Web 要求相關的 GenericPrincipal 物件。  | 
適用於
本單元適用於下列產品及技術:
| • | 
 Microsoft Windows® XP 或 Windows 2000 Server (含 Service Pack 3) 及更新的作業系統  | 
| • | 
 Active Directory  | 
| • | 
 Microsoft .NET Framework 1.0 版 (含 Service Pack 2) 及更新版本  | 
| • | 
 Microsoft Visual Studio® 1.0 .NET 及更新版本  | 
| • | 
 Microsoft Visual C#® .NET  | 
| • | 
 Microsoft SQL Server™ 2000 (含 Service Pack 2) 及更新版本  | 
如何使用本單元
若要充分瞭解此單元:
| • | 
 您必須具有使用 Visual C# .NET 及 Visual Studio .NET 的經驗。  | 
| • | 
 您必須具有使用 ASP.NET 開發 Web 應用程式的經驗。  | 
| • | 
 您必須具有使用 Active Directory 的經驗。  | 
| • | 
 您需要存取可用來測試應用程式的 Active Directory 執行個體,其不應是生產系統。  | 
| • | 
 請閱讀本手冊的第 3 單元<驗證及授權>。當中提供了各種驗證機制的詳細資訊,並討論 .NET 角色安全性。  | 
| • | 
 請閱讀本手冊的第 8 單元<ASP.NET 安全性>。當中提供了 ASP.NET Web Form 驗證的詳細資訊。  | 
摘要
ASP.NET「表單」驗證可讓使用者在 Web Form 上輸入憑證 (使用者名稱及密碼) 以表明身分。收到這些憑證時,Web 應用程式可藉由針對資料來源檢查該憑證來驗證使用者。
本單元描述如何使用「輕量型目錄存取通訊協定 (LDAP)」,來針對 Microsoft® Active Directory® 目錄服務驗證使用者。還描述如何擷取使用者所屬之安全性群組及通訊群組清單的清單,並設定 GenericPrincipal 物件,以與 .NET 角色式授權搭配使用。
建立含有登入頁面的 Web 應用程式
本程序會建立簡單的 Visual C# Web 應用程式,其中包含可讓使用者輸入使用者名稱及密碼的登入頁面,以及會顯示與現有 Web 要求相關之身分識別名稱及群組成員資格資訊的預設頁面。
| • | 
 建立含有登入頁面的 Web 應用程式 
| 
 1.  | 
 啟動 Visual Studio .NET 並建立名為 FormsAuthAD 的新 Visual C# ASP.NET Web 應用程式。  |  
| 
 2.  | 
 使用 [方案總管] 將 WebForm1.aspx 重新命名為 Logon.aspx。  |  
| 
 3.  | 
 加入 System.DirectoryServices.dll 的新組件參考。如此可存取包含 Managed 型別的 System.DirectoryServices 命名空間,以協助 Active Directory 查詢及操作。  |  
| 
 4.  | 
 將表 1 所列的控制項加入 Logon.aspx,以建立簡單的登入表單。 
表 1:Logon.aspx 控制項 
| 
 Label  | 
 網域名稱:  | 
 -  |  
| 
 Label  | 
 使用者名稱:  | 
 -  |  
| 
 Label  | 
 密碼  | 
 -  |  
| 
 Text Box  | 
 -  | 
 txtDomainName  |  
| 
 Text Box  | 
 -  | 
 txtUserName  |  
| 
 Text Box  | 
 -  | 
 txtPassword  |  
| 
 Button  | 
 登入  | 
 btnLogon  |  
| 
 Label  | 
    | 
 lblError  |   
 |  
| 
 5.  | 
 將 txtPassword 的 TextMode 屬性設為 Password。  |  
| 
 6.  | 
 在 [方案總管] 的 [FormsAuthAd] 上按一下滑鼠右鍵,並指向 [加入] 後,再按 [加入 Web Form]。  |  
| 
 7.  | 
 在 [名稱] 欄位中鍵入 default.aspx,再按一下 [開啟]。  |  
| 
 8.  | 
 在 [方案總管] 的 [default.aspx] 上按一下滑鼠右鍵,再按一下 [設定為起始頁]。  |  
| 
 9.  | 
 按兩下 [default.aspx],顯示頁面載入事件處理常式。  |  
| 
 10.  | 
 將下列程式碼加入事件處理常式,以顯示與目前 Web 要求相關的身分識別名稱。 Response.Write( HttpContext.Current.User.Identity.Name );
  |    | 
設定執行表單驗證的 Web 應用程式 
本程序會編輯應用程式的 Web.config 檔案,以設定該應用程式執行「表單」驗證。
| • | 
 設定執行表單驗證的 Web 應用程式 
| 
 1.  | 
 使用 [方案總管] 開啟 Web.config。  |  
| 
 2.  | 
 找到 <authentication> 項目,並將 mode 屬性變更為 Forms。  |  
| 
 3.  | 
 加入下列 <forms> 項目作為驗證項目的子項,再設定 loginUrl、name、timeout 及 path 屬性,如下所示: <authentication mode="Forms">
  <forms loginUrl="logon.aspx" name="adAuthCookie" timeout="60" path="/">
  </forms>
</authentication>
  |  
| 
 4.  | 
 在 <authentication> 項目下加入下列 <authorization> 項目。這將只允許已驗證的使用者才能存取應用程式。<authentication> 項目中先前建立的 loginUrl 屬性會將尚未驗證的要求重新導向 logon.aspx 頁面。 <authorization> 
  <deny users="?" />
  <allow users="*" />
</authorization>
  |  
| 
 5.  | 
 儲存 Web.config。  |  
| 
 6.  | 
 啟動 IIS Microsoft Management Console (MMC) 嵌入式管理單元。  |  
| 
 7.  | 
 在應用程式的虛擬目錄上按一下滑鼠右鍵,再按 [內容]。  |  
| 
 8.  | 
 按一下 [目錄安全設定] 索引標籤,再按 [匿名存取及驗證控制] 群組中的 [編輯] 按鈕。  |  
| 
 9.  | 
 選取 [匿名存取] 核取方塊,並清除 [允許 IIS 來控制密碼] 核取方塊。  |  
| 
 10.  | 
 因為預設匿名帳戶 IUSR_MACHINE 沒有存取 Active Directory 的權限,請建立新的最小權限帳戶,並在 [驗證方法] 對話方塊中輸入帳戶詳細資訊。  |  
| 
 11.  | 
 按一下 [確定],再按 [確定] 以關閉 [內容] 對話方塊。  |  
| 
 12.  | 
 返回 Visual Studio .NET,在 Web.config 中的 <authorization> 項目下加入 <identity> 項目,再將模擬屬性設為 true。這會使 ASP.NET 模擬先前設定的匿名帳戶。 <identity impersonate="true" />
 
由於這個設定,對於應用程式的所有要求都會在設定之匿名帳戶的安全性內容下執行。使用者將透過 Web Form 提供對於 Active Directory 進行驗證的憑證,但存取 Active Directory 的帳戶會是已設定的匿名帳戶。  |    | 
開發 LDAP 驗證程式碼以便在 Active Directory 中查詢使用者
本程序會將新的 helper 類別加入 Web 應用程式,以封裝 LDAP 程式碼。類別將先提供 IsAuthenticated 方法,以針對 Active Directory 使用者物件驗證提供的網域、使用者名稱及密碼。
| • | 
 開發 LDAP 驗證程式碼以便在 Active Directory 中查詢使用者 
| 
 1.  | 
 加入稱為 LdapAuthentication.cs 的新 C# 類別檔案。  |  
| 
 2.  | 
 加入 System.DirectoryServices.dll 組件的參考。  |  
| 
 3.  | 
 將下列 using 陳述式加入 LdapAuthentication.cs 的頂端。 using System.Text;
using System.Collections;
using System.DirectoryServices;
  |  
| 
 4.  | 
 將現有命名空間重新命名為 FormsAuthAD。  |  
| 
 5.  | 
 將兩個私用字串加入 LdapAuthentication 類別;一個儲存 Active Directory 的 LDAP 路徑,另一個儲存搜尋 Active Directory 的篩選條件屬性。 private string _path;
private string _filterAttribute;
  |  
| 
 6.  | 
 加入可用於初始化 Active Directory 路徑的 Public 建構函式。 public LdapAuthentication(string path)
{
  _path = path;
}
 |  
| 
 7.  | 
 加入下列 IsAuthenticated 方法,其會將網域名稱、使用者名稱及密碼作為參數,然後傳回 bool 以指示 Active Directory 中是否存在具有相符密碼的使用者。該方法會先使用所提供的憑證,嘗試繫結至 Active Directory。如果成功,該方法會使用 DirectorySearcher Managed 類別搜尋指定的使用者物件。如果找到物件,則會更新 _path 成員以指向使用者物件,並以使用者物件的通用名稱屬性來更新 _filterAttribute 成員。 public bool IsAuthenticated(string domain, string username, string pwd)
{
  string domainAndUsername = domain + @"\" + username;
  DirectoryEntry entry = new DirectoryEntry( _path, 
                                             domainAndUsername, pwd);
  try
  { 
    // 繫結至原始 AdsObject 以強制進行驗證。
    Object obj = entry.NativeObject;
    DirectorySearcher search = new DirectorySearcher(entry);
    search.Filter = "(SAMAccountName=" + username + ")";
    search.PropertiesToLoad.Add("cn");
    SearchResult result = search.FindOne();
    if(null == result)
    {
      return false;
    }
    // 將新路徑更新為指向目錄中的使用者
    _path = result.Path;
    _filterAttribute = (String)result.Properties["cn"][0];
  }
  catch (Exception ex)
  {
    throw new Exception("驗證使用者時發生錯誤。" + ex.Message);
  }
  return true;
}
 |    | 
開發 LDAP 群組擷取程式碼以查詢使用者的群組成員資格
本程序會擴充 LdapAuthentication 類別以提供 GetGroups 方法,擷取目前使用者所屬群組的清單。GetGroups 方法傳回的清單是以管線分隔的字串,如下所示。
"Group1|Group2|Group3|"
| • | 
 開發 LDAP 群組擷取程式碼以查詢使用者的群組成員資格 
| 
 1.  | 
 將下列 GetGroups 方法的實作加入 LdapAuthentication 類別。 public string GetGroups()
{
  DirectorySearcher search = new DirectorySearcher(_path);
  search.Filter = "(cn=" + _filterAttribute + ")";
  search.PropertiesToLoad.Add("memberOf");
  StringBuilder groupNames = new StringBuilder();
  try
  {
    SearchResult result = search.FindOne();
    int propertyCount = result.Properties["memberOf"].Count;
    String dn;
    int equalsIndex, commaIndex;
    for( int propertyCounter = 0; propertyCounter < propertyCount;
         propertyCounter++)
    {
      dn = (String)result.Properties["memberOf"][propertyCounter];
      equalsIndex = dn.IndexOf("=", 1);
      commaIndex = dn.IndexOf(",", 1);
      if (-1 == equalsIndex)
      {
        return null;
      }
      groupNames.Append(dn.Substring((equalsIndex + 1), 
                        (commaIndex - equalsIndex) - 1));
      groupNames.Append("|");
    }
  }
  catch(Exception ex)
  {
    throw new Exception("取得群組名稱時發生錯誤。" + ex.Message);
  } 
  return groupNames.ToString();
}
 |    | 
驗證使用者並建立表單驗證票證
本程序會實作 btnLogon_Click 事件處理常式,以驗證使用者。對於已驗證的使用者,請建立一個含有使用者群組清單的「表單」驗證票證。然後,將使用者重新導向至所要求的原始頁面 (在重新導向至登入頁面之前)。
| • | 
 驗證使用者並建立表單驗證票證 
| 
 1.  | 
 返回 Logon.aspx 表單,並按兩下 [登入] 按鈕,以建立空的 btnLogon_Click 事件處理常式。  |  
| 
 2.  | 
 在檔案的頂端加入下列 using 陳述式,放在現有 using 陳述式之下。如此即可存取 FormsAuthentication 方法。 using System.Web.Security;
  |  
| 
 3.  | 
 加入程式碼,以建立最初指向 LDAP Active Directory 之 LdapAuthentication 類別的新執行個體,如下列程式碼所示。請記得變更路徑,以指向 Active Directory 伺服器。 // LDAP 目錄伺服器的路徑。
// 連絡網路管理員,以取得有效的路徑。
string adPath = "LDAP://yourCompanyName.com/DC=yourCompanyName,DC=com"; 
LdapAuthentication adAuth = new LdapAuthentication(adPath);
  |  
| 
 4.  | 
 加入程式碼,以執行下列步驟: 
| 
 1.  | 
 對於 Active Directory 驗證呼叫者。  |  
| 
 2.  | 
 擷取使用者所屬群組的清單。  |  
| 
 3.  | 
 建立包含群組清單的 FormsAuthenticationTicket。  |  
| 
 4.  | 
 加密票證。  |  
| 
 5.  | 
 建立含有已加密票證的新 Cookie。  |  
| 
 6.  | 
 將 Cookie 加入傳回使用者瀏覽器的 Cookie 清單。 try
{
  if(true == adAuth.IsAuthenticated(txtDomainName.Text, 
                                    txtUserName.Text, 
                                    txtPassword.Text))
  {
    // 擷取使用者的群組
    string groups = adAuth.GetGroups();
    // 建立驗證票證
    FormsAuthenticationTicket authTicket = 
        new FormsAuthenticationTicket(1,  // 版本
                                      txtUserName.Text,
                                      DateTime.Now, 
                                      DateTime.Now.AddMinutes(60),
                                      false, groups);
    // 現在加密票證。
    string encryptedTicket = FormsAuthentication.Encrypt(authTicket);
    // 建立 Cookie 並將加密的票證加入 
    // Cookie,作為資料。
    HttpCookie authCookie = 
                 new HttpCookie(FormsAuthentication.FormsCookieName,
                                encryptedTicket);
    // 將 Cookie 加入傳出 Cookie 集合。
    Response.Cookies.Add(authCookie); 
    // 將使用者重新導向至原先要求的頁面
    Response.Redirect(
              FormsAuthentication.GetRedirectUrl(txtUserName.Text, 
                                                 false));
  }
  else
  {
    lblError.Text = 
         "驗證失敗,檢查使用者名稱及密碼。";
  }
}
catch(Exception ex)
{
  lblError.Text = "驗證時發生錯誤。" + ex.Message;
}
 |    |    | 
實作驗證要求處理常式以建構 GenericPrincipal 物件
本程序會在 global.asax 中實作 Application_AuthenticateRequest 事件處理常式,並為目前已驗證的使用者建立 GenericPrincipal 物件。這將包含使用者所屬群組的清單,該清單擷取自驗證 Cookie 中包含的 FormsAuthenticationTicket。最後,將 GenericPrincipal 物件與為每個 Web 要求而建立的目前 HttpContext 物件相關聯。
| • | 
 實作驗證要求處理常式以建構 GenericPricipal 物件 
| 
 1.  | 
 使用 [方案總管] 開啟 global.asax.cs。  |  
| 
 2.  | 
 將下列 using 陳述式加入檔案的頂端。 using System.Web.Security;
using System.Security.Principal;
  |  
| 
 3.  | 
 找到 Application_AuthenticateRequest 事件處理常式,並加入下列程式碼,以從隨要求傳送的 Cookie 集合取得包含加密 FormsAuthenticationTicket 的 Cookie。 // 擷取表單驗證 Cookie
string cookieName = FormsAuthentication.FormsCookieName;
HttpCookie authCookie = Context.Request.Cookies[cookieName];
if(null == authCookie)
{
  // 沒有驗證 Cookie。
  return;
} 
 |  
| 
 4.  | 
 加入下列程式碼,以從 Cookie 擷取 FormsAuthenticationTicket,並進行解密。 FormsAuthenticationTicket authTicket = null;
try
{
  authTicket = FormsAuthentication.Decrypt(authCookie.Value);
}
catch(Exception ex)
{
  // 記錄例外狀況詳細資料 (為簡單起見已省略)
  return;
}
if (null == authTicket)
{
  // Cookie 無法解密。
  return; 
} 
 |  
| 
 5.  | 
 加入下列程式碼,以剖析原先驗證使用者時附加於票證的管線分隔群組名稱清單。 // 建立票證後,會指派給 UserData 屬性
// 以管線分隔的群組名稱字串。
String[] groups = authTicket.UserData.Split(new char[]{'|'});
 |  
| 
 6.  | 
 加入下列程式碼,以使用從票證名稱取得的使用者名稱建立 GenericIdentity 物件,並建立包含此身分識別與該使用者群組清單的 GenericPrincipal 物件。 // 建立 Identity 物件
GenericIdentity id = new GenericIdentity(authTicket.Name, 
                                         "LdapAuthentication");
// 這個主體會在整個要求中傳送。
GenericPrincipal principal = new GenericPrincipal(id, groups);
// 將新的主體物件附加至目前的 HttpContext 物件
Context.User = principal;
 |    | 
測試應用程式
本程序會使用 Web 應用程式來要求 default.aspx 頁面。您將會被重新導向至登入頁面以進行驗證。驗證成功後,會將您的瀏覽器重新導向至原先要求的預設 default.aspx 頁面。這會從與驗證處理程序目前要求相關聯的 GenericPrincipal 物件上,擷取並顯示已驗證使用者所屬群組的清單。
| • | 
 測試應用程式 
| 
 1.  | 
 在 [建置] 功能表上按一下 [建置方案]。  |  
| 
 2.  | 
 在 [方案總管] 的 [default.aspx] 上按一下滑鼠右鍵 ,再按一下 [在瀏覽器中檢視]。  |  
| 
 3.  | 
 輸入有效網域名稱、使用者名稱及密碼,然後按一下 [登入]。  |  
| 
 4.  | 
 如果驗證成功,應會將您重新導向回 default.aspx。此頁面的程式碼應會顯示已驗證使用者的使用者名稱。 若要查看已驗證使用者所屬的群組清單,請在 global.aspx.cs 檔案中,將下列程式碼加入 Application_AuthenticateRequest 事件處理常式的結尾。 Response.Write("群組:" + authTicket.UserData + "<br>");
 |    |