从 ASP .NET 进行 Active Directory 域服务身份验证

从 ASP .NET 进行 Active Directory 域服务身份验证
本主题说明 ASP.NET 应用程序如何使用 Forms 身份验证来允许用户使用轻型目录访问协议 (LDAP) 对 Active Directory 域服务进行身份验证。在对用户进行身份验证和重定向后,可以使用 Global.asax 文件的 Application_AuthenticateRequest 方法在 HttpContext.User 属性中存储 GenericPrincipal 对象(该对象贯穿整个请求)。

创建新的 ASP.NET Web 应用程序
启动 Microsoft Visual Studio .NET。

在“文件”菜单上,指向“新建”,然后单击“项目”。

在“项目类型”下,单击“Visual C# 项目”,然后在“模板”下单击“ASP.NET Web 应用程序”。

在“名称”框中,键入 FormsAuthAd。

如果您使用的是本地服务器,则在“服务器”框中保留默认的 http://localhost。否则,添加指向您所用服务器的路径。单击“确定”。

在“解决方案资源管理器”中,右键单击“引用”节点,然后单击“添加引用”。

在“添加引用”对话框中的 .NET 选项卡上,依次单击 System.DirectoryServices.dll、“选择”和“确定”。

添加 System.DirectoryServices 身份验证代码
在“解决方案资源管理器”中,右键单击项目节点,指向“添加”,然后单击“添加新项”。

在“模板”下,单击“类”。

在“名称”框中键入 LdapAuthentication.cs,然后单击“打开”。

用下面的代码替换 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 对象,该对象包含指向目录树的路径、用户名和密码。用户名必须采用“域/用户名”格式。

然后,DirectoryEntry 对象通过获取 NativeObject 属性,尝试强制绑定 AdsObject。如果上述操作成功,则通过创建 DirectorySearcher 对象并按 sAMAccountName 进行筛选,获取用户的 CN 属性。有关 Active Directory 域服务架构中 有关 sAMAccountName 的详细信息,请参阅 MSDN Library 中的“sAMAccountName”或“SAM-Account-Name attribute(SAM-Account-Name 属性)”。在对用户进行身份验证后,IsAuthenticated 方法返回 true。为获取用户所属的组列表,此代码调用 LdapAuthentication.GetGroups 方法。LdapAuthentication.GetGroups 方法通过创建 DirectorySearcher 对象并根据 memberOf 属性进行筛选,获取用户所属的安全和通讯组的列表。有关 Active Directory 域服务架构中 有关 memberOf 的详细信息,请参阅 MSDN Library 中的“memberOf”或“Is-Member-Of-DL attribute(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 代码
在“解决方案资源管理器”中,右键单击 Global.asax,然后单击“查看代码”。

将以下代码添加到 Global.asax.cs 文件的代码隐藏的顶部:

using System.Web.Security;
using System.Security.Principal;
使用以下代码替换 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 文件
在记事本中打开 Web.config。

用下面的代码替换现有代码:

<?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"/>
对于配置为来自 Microsoft Internet 信息服务 (IIS) 的匿名帐户的帐户,上述元素将导致 ASP.NET 模拟该帐户。此配置导致的结果是,对此应用程序的所有请求都基于所配置的帐户的安全上下文运行。该用户提供凭据以针对 Active Directory 域服务进行身份验证,但访问 Active Directory 域服务的帐户是已配置的帐户。

为匿名身份验证配置 IIS
在 IIS 管理器(在“管理工具”中)或用于 IIS 的 MMC 管理单元中,右键单击您要为其配置身份验证的网站,然后单击“属性”。

单击“目录安全性”选项卡,然后在“身份验证和访问控制”下,单击“编辑”。

选中“匿名身份验证”复选框(在 Windows Server 2003 中标记为“启用匿名访问”)。

使应用程序的匿名帐户成为对 Active Directory 域服务具有权限的帐户。

如果启用了“允许 IIS 控制密码”复选框,则清除该复选框。默认的 IUSR_<computername> 帐户对 Active Directory 域服务不具有权限。

创建 Logon.aspx 页
在“解决方案资源管理器”中,右键单击项目节点,指向“添加”,然后单击“添加 Web 窗体”。

在“名称”框中,键入 Logon.aspx,然后单击“打开”。

在“解决方案资源管理器”中,右键单击 Logon.aspx,然后单击“视图设计器”。

单击“设计器”中的“HTML”选项卡。

用下面的代码替换现有代码:

<%@ 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://" + 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>
修改 Logon.aspx 页中的路径,以指向您的 LDAP 目录服务器。

Logon.aspx 页是一个用于收集来自用户的信息并调用 LdapAuthentication 类上的方法的页面。在代码对用户进行身份验证并获取了组列表后,该代码创建一个 FormsAuthenticationTicket 对象,对票证进行加密,向一个 cookie 添加加密的票证,向 HttpResponse.Cookies 集合添加该 cookie,然后将请求重定向到最初请求的 URL。

WebForm1.aspx 页是最初请求的页。在用户请求此页时,请求被重定向到 Logon.aspx 页。在对请求进行身份验证后,该请求被重定向到 WebForm1.aspx 页。

修改 WebForm1.aspx 页
在“解决方案资源管理器”中,右键单击 WebForm1.aspx,然后单击“视图设计器”。

单击“设计器”中的“HTML”选项卡。

用下面的代码替换现有代码:

<%@ 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>
保存所有文件,然后编译该项目。

请求 WebForm1.aspx 页。请注意,您将被重定向到 Logon.aspx。

键入登录凭据,然后单击“提交”。在您重定向到 WebForm1.aspx 后,请注意,您的用户名将出现并且 LdapAuthentication 是用于 Context.User.Identity.AuthenticationType 属性的身份验证类型。

注意:
建议您在使用窗体身份验证时使用安全套接字层 (SSL) 加密。这是因为用户是基于身份验证 cookie 标识的;所以,对此应用程序进行 SSL 加密将防止任何人损害传输的身份验证 cookie 以及任何其他重要信息。

你可能感兴趣的:(Directory)