從 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 Web 應用程式]。

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

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

  6. 在 [方案總管] 中的 [參考] 節點上按一下滑鼠右鍵,再按一下 [加入參考]。

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

加入 System.DirectoryServices 驗證程式碼

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

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

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

  4. 將 LdapAuthentication.cs 檔案中現有的程式碼取代為下列程式碼:

    C#
    using System;
    using System.Text;
    using System.Collections;
    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 物件。使用者名稱必須使用「網域\使用者名稱」的格式。

接著,DirectoryEntry 物件會嘗試取得 NativeObject 屬性,以強制 AdsObject 進行繫結。若成功,使用者的 CN 屬性就會經由 DirectorySearcher 物件的建立以及 sAMAccountName 的篩選而取得adschema.a_samaccountname。如需 Active Directory 結構描述中 sAMAccountName 屬性的詳細資訊,請參閱位於 MSDN Library (http://msdn.microsoft.com/libraryhttp://msdn.microsoft.com/library) 上的<sAMAccountName>或<SAM-Account-Name 屬性>。使用者經過驗證後,IsAuthenticated 方法會傳回 true。為取得使用者所屬的群組清單,此程式碼會呼叫 LdapAuthentication.GetGroups 方法。LdapAuthentication.GetGroups 方法會藉由建立 DirectorySearcher 物件以及根據 memberOf 屬性進行篩選,來取得使用者所屬的安全性與通訊群組清單。如需 Active Directory 結構描述中 memberOf 的詳細資訊,請參閱位於 MSDN Library (http://msdn.microsoft.com/libraryhttp://msdn.microsoft.com/library) 上的<memberOf>或<Is-Member-Of-DL 屬性>。此方法會傳回以縱線 (|) 分隔的群組清單。請注意,LdapAuthentication.GetGroups 方法會控制及截斷字串。這樣可以減少儲存在驗證 Cookie 中的字串長度。若字串並未截斷,各個群組的格式將如下所示:

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

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

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 檔案加入下列程式碼:

    C#
    using System.Web.Security;
    using System.Security.Principal;
  3. 以下列程式碼取代 Application_AuthenticateRequest 現有的空白事件處理常式:

    C#
    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
    <?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>

請注意下列組態項目:

Xml
<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. 將現有的程式碼更換成下列程式碼:

    Xml
    <%@ Page language="c#" AutoEventWireup="true" %>
    <%@ Import Namespace="FormsAuth" %>
    <html>
      <body>
        <form id="Login" method="post" runat="server">
          <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://DC=..,DC=.."; //Path to your LDAP directory server
      LdapAuthentication adAuth = new LdapAuthentication(adPath);
      try
      {
        if(true == adAuth.IsAuthenticated(txtDomain.Text, txtUsername.Text, txtPassword.Text))
        {
          string groups = adAuth.GetGroups();
    
          //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. 將現有的程式碼更換成下列程式碼:

    Xml
    <%@ Page language="c#" AutoEventWireup="true" %>
    <%@ Import Namespace="System.Security.Principal" %>
    <html>
      <body>
        <form id="Form1" method="post" runat="server">
          <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。

注意事項注意事項

使用表單驗證時,建議您使用 Secure Sockets Layer (SSL) 加密。這是因為識別使用者時是以驗證 Cookie 為基礎,而此應用程式上的 SSL 加密可防止他人危害驗證 Cookie 以及任何其他傳輸的重要資訊。

標記 :


Page view tracker