來自 ASP .NET 的 Active Directory 網域服務驗證

本主題示範 ASP.NET 應用程式如何使用表單驗證來允許使用者利用輕量型目錄存取通訊協定 (LDAP) 向 Active Directory 網域服務驗證。驗證並重新導向使用者之後,您可以使用 Global.asax 檔案的 Application_AuthenticateRequest 方法將 GenericPrincipal 物件儲存在 HttpContext.User 屬性中,此屬性會用於整個要求。

建立新的 ASP.NET Web 應用程式

  1. 啟動 Microsoft Visual Studio .NET。

  2. 在 [檔案] 功能表上,指向 [新增],然後按一下 [專案]。

  3. 在 [專案類型] 下按一下 [Visual C# 專案],然後按一下 [範本] 下的 [ASP.NET 應用程式]。

  4. 在 [名稱] 方塊中,輸入 FormsAuthAd

  5. 若使用本機伺服器,請保留 [伺服器] 方塊中的預設 https://localhost。否則,請加入伺服器的路徑。按一下 [確定]。

  6. 在 [方案總管] 中,以滑鼠右鍵按一下 [參考],然後按一下 [加入參考]。

  7. 在 [加入參考] 對話方塊的 [.NET] 索引標籤中,依序按一下 [System.DirectoryServices.dll] 及 [選取],然後按一下 [確定]。

加入 System.DirectoryServices 驗證程式碼

  1. 在 [方案總管] 中,以滑鼠右鍵按一下專案節點,指向 [加入],然後按一下 [加入新項目]。

  2. 按一下 [範本] 底下的 [類別]。

  3. 在 [名稱] 方塊中輸入 LdapAuthentication.cs,然後按一下 [開啟]。

  4. 將 LdapAuthentication.cs 檔案中的現有程式碼替換成下列程式碼:

    using System;
    using System.Text;
    using System.Collections;
    using System.Web.Security;
    
    using System.Security.Principal;    
    using System.DirectoryServices;
    
    
    namespace FormsAuth
    {
      public class LdapAuthentication
      {
        private string _path;
        private string _filterAttribute;
    
        public LdapAuthentication(string path)
        {
          _path = path;
        }
    
        public bool IsAuthenticated(string domain, string username, string pwd)
        {
          string domainAndUsername = domain + @"\" + username;
          DirectoryEntry entry = new DirectoryEntry(_path, domainAndUsername, pwd);
    
          try
          {
            //Bind to the native AdsObject to force authentication.
            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;
            }
    
            //Update the new path to the user in the directory.
            _path = result.Path;
            _filterAttribute = (string)result.Properties["cn"][0];
          }
          catch (Exception ex)
          {
            throw new Exception("Error authenticating user. " + ex.Message);
          }
    
          return true;
        }
    
        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("Error obtaining group names. " + ex.Message);
        }
        return groupNames.ToString();
      }
    }
    }
    

在上一個程序中,驗證程式碼接受網域、使用者名稱、密碼與 Active Directory 網域服務中的樹狀結構路徑。此程式碼使用 LDAP 目錄提供者。Logon.aspx 頁面中的程式碼會呼叫 LdapAuthentication.IsAuthenticated 方法,並傳入從使用者收集的認證。接著,會使用目錄服務的路徑、使用者名稱與密碼建立 DirectoryEntry 物件。使用者名稱的格式必須是 domain\username。

DirectoryEntry 物件接著會取得 NativeObject 屬性,以嘗試強制 AdsObject 進行繫結。若此動作成功,會取得使用者的 CN 屬性,方式是建立 DirectorySearcher 物件並篩選 sAMAccountName。如需有關 Active Directory 網域服務結構描述之 sAMAccountName 的詳細資訊,請參閱 MSDN Library (本頁面可能為英文) 中的<sAMAccountName>或<SAM-Account-Name 屬性>。驗證使用者之後,IsAuthenticated 方法會傳回 true。為取得使用者所屬的群組清單,此程式碼會呼叫 LdapAuthentication.GetGroups 方法。LdapAuthentication.GetGroups 方法可取得使用者所屬的安全性與通訊群組清單,方式是建立 DirectorySearcher 物件並根據 memberOf 屬性進行篩選。如需 Active Directory 網域服務結構描述之 memberOf 的詳細資訊,請參閱 MSDN Library (本頁面可能為英文) 中的<memberOf>或<Is-Member-Of-DL 屬性>。此方法會傳回由管道 (|) 分隔的群組清單。請注意,LdapAuthentication.GetGroups 方法會管理並截斷字串。這樣可縮短儲存在驗證 Cookie 中的字串長度。若未截斷字串,每個群組的格式看起來像下面這樣:

CN=...,...,DC=domain,DC=com

LdapAuthentication.GetGroups 方法可以傳回超長字串。若此字串的長度超過 Cookie 長度,可能無法建立驗證 Cookie。若此字串可能超過 Cookie 長度,您可能想將群組資訊儲存在 ASP.NET Cache 物件或資料庫中。此外,您也可以加密群組資訊並將此資訊儲存在隱藏的表單欄位中。

Global.asax 檔案中的程式碼提供 Application_AuthenticateRequest 事件處理常式。此事件處理常式會從 Context.Request.Cookies 集合擷取驗證 Cookie、解密該 Cookie,然後擷取將儲存在 FormsAuthenticationTicket.UserData 屬性中的群組清單。群組會以管道分隔清單格式出現在 Logon.aspx 頁面上。程式碼會剖析字串陣列中的字串以建立 GenericPrincipal 物件。建立 GenericPrincipal 物件之後,此物件會被放在 HttpContext.User 屬性中。

撰寫 Global.asax 程式碼

  1. 在 [方案總管] 中,以滑鼠右鍵按一下 Global.asax,然後按一下 [檢視程式碼]。

  2. 在 Global.asax.cs 檔案之程式碼後置 (Code-Behind) 的頂端加入下列程式碼:

    using System.Web.Security;
    using System.Security.Principal;
    
  3. 將 Application_AuthenticateRequest 的現有空白事件處理常式替換成下列程式碼:

    void Application_AuthenticateRequest(object sender, EventArgs e)
    {
      string cookieName = FormsAuthentication.FormsCookieName;
      HttpCookie authCookie = Context.Request.Cookies[cookieName];
    
      if(null == authCookie)
      {
        //There is no authentication cookie.
        return;
      }
      FormsAuthenticationTicket authTicket = null;
      try
      {
        authTicket = FormsAuthentication.Decrypt(authCookie.Value);
      }
      catch(Exception ex)
      {
        //Write the exception to the Event Log.
        return;
      }
    if(null == authTicket)
      {
        //Cookie failed to decrypt.
        return;
      }
      //When the ticket was created, the UserData property was assigned a
      //pipe-delimited string of group names.
      string[] groups = authTicket.UserData.Split(new char[]{'|'});
      //Create an Identity.
      GenericIdentity id = new GenericIdentity(authTicket.Name, "LdapAuthentication");
      //This principal flows throughout the request.
      GenericPrincipal principal = new GenericPrincipal(id, groups);
      Context.User = principal;
    }
    

在本節中,您會設定 Web.config 檔案中的 <forms>、<authentication> 與 <authorization> 項目。進行這些變更之後,只有已驗證的使用者可以存取應用程式,而未驗證的要求會被重新導向到 Logon.aspx 頁面。您可以修改此組態,以便只允許特定使用者與群組存取應用程式。

修改 Web.config 檔

  1. 以 [記事本] 開啟 Web.config。

  2. 將現有的程式碼更換成下列程式碼:

    <?xml version="1.0" encoding="utf-8"?>
    <configuration>
      <system.web>
        <authentication mode="Forms">
          <forms loginUrl="logon.aspx" name="adAuthCookie" timeout="10" path="/">
          </forms>
        </authentication>
        <authorization>
          <deny users="?"/>
          <allow users="*"/>
        </authorization>
        <identity impersonate="true"/>
      </system.web>
     </configuration>
    

請注意下列組態項目:

<identity impersonate="true"/>

此項目會使得 ASP.NET 模擬從 Microsoft Internet Information Services (IIS) 設定為匿名帳戶的帳戶。由於此組態,所有對此應用程式發出的要求都會以已設定之帳戶的安全性內容執行。使用者必須提供認證以向 Active Directory 網域服務驗證,但只有已設定的帳戶可存取 Active Directory 網域服務。

設定 IIS 以進行匿名驗證

  1. 在 [IIS 管理員] (位於 [系統管理工具]) 或 IIS 的 MMC 嵌入式管理單元中,以滑鼠右鍵按一下要設定驗證的網站,然後按一下 [內容]。

  2. 按一下 [目錄安全性] 索引標籤,然後按一下 [驗證及存取控制] 底下的 [編輯]。

  3. 選取 [匿名驗證] 核取方塊 (在 Windows Server 2003 中標示為 [啟用匿名存取])。

  4. 確定應用程式的匿名帳戶具有存取 Active Directory 網域服務的權限。

  5. 清除 [允許 IIS 控制密碼] 核取方塊 (若該核取方塊已存在)。預設的 IUSR*_<computername>* 帳戶沒有存取 Active Directory 網域服務的權限。

建立 Logon.aspx 頁面

  1. 在 [方案總管] 中,以滑鼠右鍵按一下專案節點、指向 [加入],然後按一下 [加入 Web Form]。

  2. 在 [名稱] 方塊中,輸入 Logon.aspx,然後按一下 [開啟]。

  3. 在 [方案總管] 中,以滑鼠右鍵按一下 [Logon.aspx],然後按一下 [檢視表設計工具]。

  4. 按一下 [設計工具] 中的 [HTML] 索引標籤。

  5. 將現有的程式碼更換成下列程式碼:

    <%@ Page language="c#" AutoEventWireup="true" %>
    <%@ Import Namespace="FormsAuth" %>
    <html>
      <body>
        <form id="Login" method="post" >
          <asp:Label ID="Label1" Runat=server >Domain:</asp:Label>
          <asp:TextBox ID="txtDomain" Runat=server ></asp:TextBox><br>    
          <asp:Label ID="Label2" Runat=server >Username:</asp:Label>
          <asp:TextBox ID=txtUsername Runat=server ></asp:TextBox><br>
          <asp:Label ID="Label3" Runat=server >Password:</asp:Label>
          <asp:TextBox ID="txtPassword" Runat=server TextMode=Password></asp:TextBox><br>
          <asp:Button ID="btnLogin" Runat=server Text="Login" OnClick="Login_Click"></asp:Button><br>
          <asp:Label ID="errorLabel" Runat=server ForeColor=#ff3300></asp:Label><br>
          <asp:CheckBox ID=chkPersist Runat=server Text="Persist Cookie" />
        </form>
      </body>
    </html>
    <script runat=server>
    void Login_Click(object sender, EventArgs e)
    {
      string adPath = "LDAP://" + txtDomain.Text;
    
      LdapAuthentication adAuth = new LdapAuthentication(adPath);
      try
      {
        if(true == adAuth.IsAuthenticated(txtDomain.Text, txtUsername.Text, txtPassword.Text))
        {
          string groups = adAuth.GetGroups(txtDomain.Text, txtUsername.Text, txtPassword.Text);
    
    
          //Create the ticket, and add the groups.
          bool isCookiePersistent = chkPersist.Checked;
          FormsAuthenticationTicket authTicket = new FormsAuthenticationTicket(1, 
                    txtUsername.Text,DateTime.Now, DateTime.Now.AddMinutes(60), isCookiePersistent, groups);
    
          //Encrypt the ticket.
          string encryptedTicket = FormsAuthentication.Encrypt(authTicket);
    
          //Create a cookie, and then add the encrypted ticket to the cookie as data.
          HttpCookie authCookie = new HttpCookie(FormsAuthentication.FormsCookieName, encryptedTicket);
    
          if(true == isCookiePersistent)
          authCookie.Expires = authTicket.Expiration;
    
          //Add the cookie to the outgoing cookies collection.
          Response.Cookies.Add(authCookie);
    
          //You can redirect now.
          Response.Redirect(FormsAuthentication.GetRedirectUrl(txtUsername.Text, false));
        }
        else
        {
          errorLabel.Text = "Authentication did not succeed. Check user name and password.";
        }
      }
      catch(Exception ex)
      {
        errorLabel.Text = "Error authenticating. " + ex.Message;
      }
    }
    </script>
    
  6. 修改 Logon.aspx 頁面中的路徑以指向您的 LDAP 目錄伺服器。

Logon.aspx 頁面可從使用者收集資訊,並呼叫 LdapAuthentication 類別上的方法。當程式碼驗證使用者並取得群組清單之後,會建立 FormsAuthenticationTicket 物件、加密票證、將加密的票證加入至 Cookie、將 Cookie 加入至 HttpResponse.Cookies 集合,然後將要求重新導向至當初要求的 URL。

WebForm1.aspx 頁面是當初要求的頁面。當使用者要求此頁面時,要求會被重新導向到 Logon.aspx 頁面。驗證要求之後,要求會被重新導向至 WebForm1.aspx 頁面。

修改 WebForm1.aspx 頁面

  1. 在 [方案總管] 中,以滑鼠右鍵按一下 [WebForm1.aspx],然後按一下 [檢視表設計工具]。

  2. 按一下 [設計工具] 中的 [HTML] 索引標籤。

  3. 將現有的程式碼更換成下列程式碼:

    <%@ Page language="c#" AutoEventWireup="true" %>
    <%@ Import Namespace="System.Security.Principal" %>
    <html>
      <body>
        <form id="Form1" method="post" >
          <asp:Label ID="lblName" Runat=server /><br>
          <asp:Label ID="lblAuthType" Runat=server />
        </form>
      </body>
    </html>
    <script runat=server>
    void Page_Load(object sender, EventArgs e)
    {
      lblName.Text = "Hello " + Context.User.Identity.Name + ".";
      lblAuthType.Text = "You were authenticated using " + Context.User.Identity.AuthenticationType + ".";
    }
    </script>
    
  4. 儲存所有檔案並編譯專案。

  5. 要求 WebForm1.aspx 頁面。請注意,您將被重新導向至 Logon.aspx。

  6. 輸入登入認證,然後按一下 [送出]。請注意,當您被重新導向至 WebForm1.aspx 時,您的使用者名稱會出現,而且 Context.User.AuthenticationType 屬性的驗證類型是 LdapAuthentication。

注意:
當您使用表單驗證時,建議您使用安全通訊端層 (SSL) 加密。這是因為使用者是透過驗證 Cookie 識別,而此應用程式上的 SSL 加密可防止其他人在傳送過程中竊取驗證 Cookie 與其他重要資訊。

請參閱

參考

System.DirectoryServices
DirectoryEntry
DirectorySearcher

概念

進階程式設計主題

Send comments about this topic to Microsoft.

Copyright © 2007 by Microsoft Corporation.All rights reserved.