本主题说明 ASP.NET 应用程序如何使用 Forms 身份验证来允许用户使用轻型目录访问协议 (LDAP) 对 Active Directory 域服务进行身份验证。在对用户进行身份验证和重定向后,可以使用 Global.asax 文件的 Application_AuthenticateRequest 方法在 HttpContext.User 属性中存储 GenericPrincipal 对象(该对象贯穿整个请求)。
-
启动 Microsoft Visual Studio .NET。
-
在“文件”菜单上,指向“新建”,然后单击“项目”。
-
在“项目类型”下,单击“Visual C# 项目”,然后在“模板”下单击“ASP.NET Web 应用程序”。
-
在“名称”框中,键入 FormsAuthAd。
-
如果您使用的是本地服务器,则在“服务器”框中保留默认的 http://localhost。否则,添加指向您所用服务器的路径。单击“确定”。
-
在“解决方案资源管理器”中,右键单击“引用”节点,然后单击“添加引用”。
-
在“添加引用”对话框中的 .NET 选项卡上,依次单击 System.DirectoryServices.dll、“选择”和“确定”。
-
在“解决方案资源管理器”中,右键单击项目节点,指向“添加”,然后单击“添加新项”。
-
在“模板”下,单击“类”。
-
在“名称”框中键入 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 域服务不具有权限。
-
在“解决方案资源管理器”中,右键单击项目节点,指向“添加”,然后单击“添加 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 属性的身份验证类型。