本分步指南演示如何在 ASP.NET 应用程序如何使用窗体身份验证允许用户使用轻型目录访问协议 (LDAP),对 Active Directory 进行验证。
经过身份验证的用户重定向之后,可以使用Application_AuthenticateRequest方法的 Global.asax 文件流动在整个请求的HttpContext.User属性中存储绎对象。
Logon.aspx 页中的代码调用LdapAuthentication.IsAuthenticated方法,并从用户收集凭据传递。 然后,为目录树、 用户名和密码的路径创建一个DirectoryEntry对象。 用户名必须是"域 \ 用户名"格式。 DirectoryEntry对象然后会尝试通过获取NativeObject属性强制AdsObject绑定。 如果此操作成功,通过创建一个DirectorySearcher对象,并通过在SAMAccountName上筛选获得用户的CN属性。 验证用户身份后, IsAuthenticated方法将返回true。
若要获取该用户所属的组的列表,此代码将调用LdapAuthentication.GetGroups方法。 LdapAuthentication.GetGroups方法获取安全和分发通过创建一个DirectorySearcher对象,并根据memberOf属性筛选用户所属的组的列表。 此方法返回组的列表,用竖线 (|) 分隔。
请注意, LdapAuthentication.GetGroups方法操作,并将截断的字符串。 这将减少身份验证 cookie 中存储的字符串的长度。 如果该字符串不会被截断,每个组的格式如下所示。
这样可以创建一个很长的字符串。
此字符串的长度大于该 cookie 的长度,如果浏览器不能接受的身份验证 cookie,并会将您重定向到登录页。
但是,如果您是在多域环境中,您可能需要保留组名称的域名,因为在不同的域中的组可以有相同的组名。
您必须保留域的名称来区分一组从另一个。
大多数浏览器支持达 4096 字节的 cookie。 如果可能,此字符串可能会超过 cookie 的长度,您可以在 ASP.NET 缓存对象中或数据库中存储的组信息。 或者,您可能希望组信息进行加密,并将此信息存储在一个隐藏的窗体字段。
代码分析的字符串中的字符串数组创建绎对象。 绎对象创建后,此对象将位于HttpContext.User属性。
在 Web.config 文件中的现有代码替换为以下代码。
请注意< 标识模拟 ="true"/ >配置元素。
这将导致 ASP.NET 模拟的帐户被配置为从 Microsoft Internet Information Services (IIS) 的匿名帐户。
由于此配置中,对此应用程序的所有请求都运行的已配置帐户的安全上下文。
用户提供的凭据以进行身份验证的 Active Directory,但访问 Active Directory 帐户配置的帐户。
有关详细信息,请参阅参考部分。
在 Visual C#.NET 创建 ASP.NET Web 应用程序
请按照以下步骤创建新的 ASP.NET Web 应用程序名为 FormsAuthAd Visual C#.NET 中:- 启动 Microsoft Visual Studio.NET。
- 在文件菜单上,指向新建,然后单击项目。
- 单击项目类型下的Visual C# 项目,然后单击模板下的ASP.NET Web 应用程序。
- 在位置框中,替换为FormsAuthAdWebApplication1。
- 单击确定。
- 用鼠标右键单击解决方案资源管理器中,在引用节点,然后单击添加引用。
- 添加引用对话框中的.NET选项卡上,单击System.DirectoryServices.dll,单击选择,然后单击确定。
编写验证代码
请按照下列步骤创建一个名为 LdapAuthentication.cs 的新类文件:- 在解决方案资源管理器中,用鼠标右键单击项目节点,指向添加,然后单击添加新项。
- 在模板下,单击类。
- 在名称框中,键入LdapAuthentication.cs ,然后单击打开。
-
LdapAuthentication.cs 文件中的现有代码替换为以下代码。
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(); } } }
Logon.aspx 页中的代码调用LdapAuthentication.IsAuthenticated方法,并从用户收集凭据传递。 然后,为目录树、 用户名和密码的路径创建一个DirectoryEntry对象。 用户名必须是"域 \ 用户名"格式。 DirectoryEntry对象然后会尝试通过获取NativeObject属性强制AdsObject绑定。 如果此操作成功,通过创建一个DirectorySearcher对象,并通过在SAMAccountName上筛选获得用户的CN属性。 验证用户身份后, IsAuthenticated方法将返回true。
若要获取该用户所属的组的列表,此代码将调用LdapAuthentication.GetGroups方法。 LdapAuthentication.GetGroups方法获取安全和分发通过创建一个DirectorySearcher对象,并根据memberOf属性筛选用户所属的组的列表。 此方法返回组的列表,用竖线 (|) 分隔。
请注意, LdapAuthentication.GetGroups方法操作,并将截断的字符串。 这将减少身份验证 cookie 中存储的字符串的长度。 如果该字符串不会被截断,每个组的格式如下所示。
CN=...,...,DC=domain,DC=com
大多数浏览器支持达 4096 字节的 cookie。 如果可能,此字符串可能会超过 cookie 的长度,您可以在 ASP.NET 缓存对象中或数据库中存储的组信息。 或者,您可能希望组信息进行加密,并将此信息存储在一个隐藏的窗体字段。
编写 Global.asax 的代码
在 Global.asax 文件中的代码提供了一个Application_AuthenticateRequest事件处理程序。 此事件处理程序从Context.Request.Cookies集合中检索身份验证 cookie,解密 cookie,并检索将FormsAuthenticationTicket.UserData属性中存储的组的列表。 在 Logon.aspx 页中创建管道分隔的列表中显示的组。代码分析的字符串中的字符串数组创建绎对象。 绎对象创建后,此对象将位于HttpContext.User属性。
- 在解决方案资源管理器中,用鼠标右键单击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 文件
在本节中,您可以配置< 窗体 >、 < 身份验证 >和< 授权 >元素的 Web.config 文件中。 进行这些更改,只有已通过身份验证的用户才能访问该应用程序,,未经身份验证的请求重定向到 Logon.aspx 页。 您可以修改此配置为允许特定用户和组访问该应用程序。在 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>
将 IIS 配置为匿名身份验证
若要将 IIS 配置为匿名身份验证,请按照下列步骤操作:- 在 IIS 中,展开计算机节点,为您的服务器,展开 网站、 展开 默认网站、 FormsAuthAd,用鼠标右键单击,然后单击属性。
- 单击 目录安全性选项卡,然后单击 匿名访问和验证控件 下的 编辑。
- 请为应用程序的匿名帐户具有活动目录的权限的帐户。
- 单击以清除允许 IIS 到控制密码复选框。
- 在身份验证的访问部分取消选中集成 Windows 身份验证复选框。
- 单击确定。
- 单击应用
创建 Logon.aspx 页
请按照下列步骤创建新 ASP.NET Web 窗体名为 Logon.aspx 操作:- 在解决方案资源管理器中,用鼠标右键单击该项目节点,指向 添加,然后单击 添加 Web 窗体。
- 在 名称 框中键入 Logon.aspx,然后单击 打开。
- 在解决方案资源管理器中,用鼠标右键单击 Logon.aspx,然后单击 视图设计器。
- 单击在设计器中的 HTML 选项卡。
- Replace the existing code with the following code.
<%@ 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://corp.com"; //Fully-qualified Domain Name 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>
- 在修改路径 Logon.aspx 页,使其指向您 LDAP 目录服务器。
修改 WebForm1.aspx 页
WebForm1.aspx 页是最初请求的页。 当用户请求此页时,将请求重定向到该 Logon.aspx 页。请求进行身份验证之后,该请求会被重定向到 WebForm1.aspx 页。- 在解决方案资源管理器中,用鼠标右键单击 WebForm1.aspx,然后单击 视图设计器。
- 单击在设计器中的 HTML 选项卡。
- 将现有代码替换下面 code.
<%@ 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.AuthenticationType 属性。
参考
有关详细的信息请单击下面的文章编号,以查看 Microsoft 知识库中相应的文章:
306590ASP.NET 安全性概述
317012在 ASP.NET 中的过程和请求身份
311495如何通过使用 Visual C#.net 在 ASP.NET 应用程序中实现基于窗体的身份验证与基于角色的安全性
313091如何创建通过使用 Visual Basic.net 在 Forms 身份验证中使用的键
313116窗体身份验证请求不被定向到 loginUrl 页