本页内容
目标
本章的目标是:
• | 创建一个使用窗体身份验证针对 Active Directory 验证用户身份的 Web 应用程序。 |
• | 从 Active Directory 获取一个通过身份验证的用户所属的组和通讯组的列表。 |
• | 创建一个与使用 HttpContext.Current.User 属性的用户 Web 请求相关联的 GenericPrincipal 对象。 |
适用范围
本章适用于以下产品和技术:
• | Microsoft Windows® XP 或 Windows 2000 Server (Service Pack 3) 以及更高版本的操作系统 |
• | Active Directory |
• | Microsoft .NET Framework 版本 1.0 (Service Pack 2) 以及更高版本 |
• | Microsoft Visual Studio® 1.0 .NET 以及更高版本 |
• | Microsoft Visual C#® .NET |
• | Microsoft SQL Server® 2000 (Service Pack 2) 以及更高版本 |
如何使用本章内容
若要学好本章内容:
• | 您必须具有使用 Visual C# .NET 和 Visual Studio .NET 的经验。 |
• | 您必须具有使用 ASP.NET 开发 Web 应用程序的经验。 |
• | 您必须具有使用 Active Directory 的经验。 |
• | 您必须访问一个用于测试您的应用程序的 Active Directory 实例。该实例不能是生产系统。 |
• | 阅读本指南中的第 3 章身份验证和授权。这一章提供了有关各种身份验证机制的详细信息,并讨论了基于 .NET 角色的安全性。 |
• | 阅读本指南中的第 8 章 ASP.NET 安全性。这一章提供了有关 ASP.NET Web 窗体身份验证的详细信息。 |
摘要
借助 ASP.NET 窗体身份验证,用户可以通过在 Web 窗体中输入凭据(用户名和密码)来表明自己的身份。在收到这些凭据后,Web 应用程序针对数据源检查凭据,以便验证用户的身份。
本章介绍了如何通过使用轻型目录访问协议 (LDAP),针对 Microsoft® Active Directory® 目录服务来验证用户的身份。这里还介绍了如何检索用户所属的安全组和通讯组的列表,以及如何配置 GenericPrincipal 对象以便用于基于 .NET 角色的授权。
创建一个具有登录页的 Web 应用程序
此过程创建一个简单的 Visual C# Web 应用程序,其中包含一个用户可以输入用户名和密码的登录页,以及一个显示与当前 Web 请求相关联的身份名称和组成员身份信息的默认页。
• | 创建一个具有登录页的 Web 应用程序
1. | 启动 Visual Studio .NET,然后创建一个名为 FormsAuthAD 的新的 Visual C# ASP.NET Web 应用程序。 | 2. | 使用解决方案资源管理器将 WebForm1.aspx 重命名为 Logon.aspx。 | 3. | 添加一个新的 System.DirectoryServices.dll 程序集引用。这可提供对 System.DirectoryServices 命名空间的访问,该命名空间包含用于 Active Directory 查询和操作的托管类型。 | 4. | 向 Logon.aspx 添加表 1 中列出的控件,创建一个简单的登录窗体。 表 1:Logon.aspx 控件
标签 | 域名: | - | 标签 | 用户名: | - | 标签 | 密码 | - | 文本框 | - | txtDomainName | 文本框 | - | txtUserName | 文本框 | - | txtPassword | 按钮 | 登录 | btnLogon | 标签 | | lblError |
| 5. | 将 txtPassword 的 TextMode 属性设置为 Password。 | 6. | 在解决方案资源管理器中,右键单击“FormsAuthAd”,指向“添加”,然后单击“添加 Web 窗体”。 | 7. | 在“名称”字段中,键入“default.aspx”,然后单击“打开”。 | 8. | 在解决方案资源管理器中,右键单击“default.aspx”,然后单击“设为起始页”。 | 9. | 双击“default.aspx”显示页加载事件处理程序。 | 10. | 向事件处理程序中添加以下代码,显示与当前 Web 请求相关联的标识名称: Response.Write( HttpContext.Current.User.Identity.Name );
| |
配置 Web 应用程序的窗体身份验证
此过程编辑应用程序的 Web.config 文件,以便配置应用程序的窗体身份验证。
• | 配置 Web 应用程序的窗体身份验证
1. | 使用解决方案资源管理器打开 Web.config。 | 2. | 查找 <authentication> 元素,并将 mode 属性更改为 Forms。 | 3. | 将 <forms> 元素添加为 authentication 元素的子元素,并按以下所示设置 loginUrl、name、timeout 和 path 属性: <authentication mode="Forms">
<forms loginUrl="logon.aspx" name="adAuthCookie" timeout="60" path="/">
</forms>
</authentication>
| 4. | 在 <authentication> 元素下面添加以下 <authorization> 元素。这样将只允许通过身份验证的用户访问该应用程序。先前为 <authentication> 元素建立的 loginUrl 属性会将未通过身份验证的请求重定向到 logon.aspx 页。 <authorization>
<deny users="?" />
<allow users="*" />
</authorization>
| 5. | 保存 Web.config。 | 6. | 启动 IIS Microsoft 管理控制台 (MMC) 管理单元。 | 7. | 右键单击应用程序的虚拟目录,然后单击“属性”。 | 8. | 单击“目录安全性”选项卡,然后单击“匿名访问和身份验证控制”组中的“编辑”按钮。 | 9. | 选中“匿名访问”复选框并清除“允许 IIS 控制密码”复选框。 | 10. | 因为默认的匿名帐户 IUSR_MACHINE 没有访问 Active Directory 的权限,请创建一个具有最低权限的新帐户,并在“身份验证方法”对话框中输入帐户详细信息。 | 11. | 单击“确定”,然后再次单击“确定”关闭“属性”对话框。 | 12. | 返回到 Visual Studio .NET,在 Web.config 中的 <authorization> 元素下面添加 <identity> 元素,并将 impersonate 属性设置为 true。这样会使 ASP.NET 模拟以前指定的匿名帐户。 <identity impersonate="true" />
执行此配置后,所有应用程序请求均在已配置的匿名帐户的安全性上下文中运行。用户将通过 Web 窗体提供凭据,以便进行 Active Directory 身份验证,但用于访问 Active Directory 的帐户将是已配置的匿名帐户。 | |
开发 LDAP 身份验证代码,以便在 Active Directory 中查找用户
此过程向 Web 应用程序中添加一个新的帮助器类,用于封装 LDAP 代码。该类最初将提供一个 IsAuthenticated 方法,用于针对 Active Directory 用户对象验证提供的域、用户名和密码。
• | 开发 LDAP 身份验证代码,以便在 Active Directory 中查找用户
1. | 添加一个名为 LdapAuthentication.cs 的新的 C# 类文件。 | 2. | 添加一个 System.DirectoryServices.dll 程序集引用。 | 3. | 在 LdapAuthentication.cs 的顶部添加以下 using 语句: using System.Text;
using System.Collections;
using System.DirectoryServices;
| 4. | 将现有命名空间重命名为 FormsAuthAD。 | 5. | 向 LdapAuthentication 类中添加两个私有字符串:一个保存 Active Directory 的 LDAP 路径,另一个保存用于搜索 Active Directory 的筛选器属性。 private string _path;
private string _filterAttribute;
| 6. | 添加一个用于初始化 Active Directory 路径的公共构造函数。 public LdapAuthentication(string path)
{
_path = path;
}
| 7. | 添加以下 IsAuthenticated 方法,该方法将域名、用户名和密码作为参数,并返回一个布尔值,指示 Active Directory 中是否存在具有匹配密码的用户。该方法最初尝试使用提供的凭据绑定到 Active Directory。如果成功,该方法将使用 DirectorySearcher 托管类搜索指定的用户对象。如果找到,则更新 _path 成员以指向该用户对象,并使用用户对象的公用名属性更新 _filterAttribute 成员。 public bool IsAuthenticated(string domain, string username, string pwd)
{
string domainAndUsername = domain + @"/" + username;
DirectoryEntry entry = new DirectoryEntry( _path,
domainAndUsername, pwd);
try
{
// 绑定到本机 AdsObject 以强制身份验证。
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;
}
// 更新目录中的用户的新路径
_path = result.Path;
_filterAttribute = (String)result.Properties["cn"][0];
}
catch (Exception ex)
{
throw new Exception("对用户进行身份验证时出错。 " + ex.Message);
}
return true;
}
| |
开发 LDAP 组检索代码,以便查找用户的组成员身份
此过程扩展了 LdapAuthentication 类,提供了一个 GetGroups 方法,用于检索当前用户所在组的列表。GetGroups 方法将以管道符分隔的字符串形式返回组列表,如下所示:
"Group1|Group2|Group3|"
• | 开发 LDAP 组检索代码,以便查找用户的组成员身份
1. | 向 LdapAuthentication 类中添加 GetGroups 方法的以下实现: 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("获取组名时出错。 " + ex.Message);
}
return groupNames.ToString();
}
| |
验证用户身份和创建窗体身份验证票证
此过程实现验证用户身份的 btnLogon_Click 事件处理程序。对于通过身份验证的用户,随后将创建一个包含用户组列表的窗体身份验证票证。然后,将用户重定向到他们所请求的原始页(在重定向到登录页之前)。
• | 验证用户身份和创建窗体身份验证票证
1. | 返回到 Logon.aspx 窗体,双击“登录”按钮,创建一个空的 btnLogon_Click 事件处理程序。 | 2. | 在文件顶部的现有 using 语句下面,添加以下 using 语句。这样可以提供对 FormsAuthentication 方法的访问。 using System.Web.Security;
| 3. | 添加代码以创建初始化的 LdapAuthentication 类的新实例,以便指向 LDAP Active Directory,如以下代码所示。切记更改路径,使其指向 Active Directory 服务器。 // 您的 LDAP 目录服务器的路径.
// 与您的网络管理员联系以获得有效路径。
string adPath = "LDAP://yourCompanyName.com/DC=yourCompanyName,DC=com";
LdapAuthentication adAuth = new LdapAuthentication(adPath);
| 4. | 添加下面的代码以执行以下步骤:
1. | 对调用方进行 Active Directory 身份验证。 | 2. | 检索用户所在组的列表。 | 3. | 创建一个包含组列表的 FormsAuthenticationTicket。 | 4. | 对票证进行加密。 | 5. | 创建包含加密票证的新的 cookie。 | 6. | 将 cookie 添加到返回给用户浏览器的 cookie 列表中。 try
{
if(true == adAuth.IsAuthenticated(txtDomainName.Text,
txtUserName.Text,
txtPassword.Text))
{
// 检索用户组
string groups = adAuth.GetGroups();
// 创建身份验证票证
FormsAuthenticationTicket authTicket =
new FormsAuthenticationTicket(1, // 版本
txtUserName.Text,
DateTime.Now,
DateTime.Now.AddMinutes(60),
false, groups);
// 现在对票证进行加密。
string encryptedTicket = FormsAuthentication.Encrypt(authTicket);
// 创建一个 cookie 并将加密的票证添加到
// 该 cookie 作为数据。
HttpCookie authCookie =
new HttpCookie(FormsAuthentication.FormsCookieName,
encryptedTicket);
// 将该 cookie 添加到传出 cookie 集合。
Response.Cookies.Add(authCookie);
// 将用户重定向到最初请求的页面
Response.Redirect(
FormsAuthentication.GetRedirectUrl(txtUserName.Text,
false));
}
else
{
lblError.Text =
"身份验证失败,检查用户名和密码。";
}
}
catch(Exception ex)
{
lblError.Text = "身份验证出错。 " + ex.Message;
}
| | |
实现身份验证请求处理程序以构造 GenericPrincipal 对象
此过程实现 global.asax 中的 Application_AuthenticateRequest 事件处理程序,并为当前通过身份验证的用户创建一个 GenericPrincipal 对象。这将包含用户所在组的列表。该组列表是从身份验证 cookie 中包含的 FormsAuthenticationTicket 中检索而来的。最后,将 GenericPrincipal 对象与为每个 Web 请求创建的当前 HttpContext 对象关联起来。
• | 实现身份验证请求处理程序以构造 GenericPrincipal 对象
1. | 使用解决方案资源管理器打开 global.asax.cs。 | 2. | 在文件的顶部添加以下 using 语句: using System.Web.Security;
using System.Security.Principal;
| 3. | 找到 Application_AuthenticateRequest 事件处理程序并添加以下代码,以便从随请求一起传输的 cookie 集合中获取包含加密的 FormsAuthenticationTicket 的 cookie: // 提取窗体身份验证 cookie
string cookieName = FormsAuthentication.FormsCookieName;
HttpCookie authCookie = Context.Request.Cookies[cookieName];
if(null == authCookie)
{
// 没有身份验证 cookie
return;
}
| 4. | 添加以下代码,从 cookie 中提取 FormsAuthenticationTicket 并进行解密: FormsAuthenticationTicket authTicket = null;
try
{
authTicket = FormsAuthentication.Decrypt(authCookie.Value);
}
catch(Exception ex)
{
// 记录异常情况详细信息(为简便起见,已省略)
return;
}
if (null == authTicket)
{
// 无法解密 Cookie。
return;
}
| 5. | 添加以下代码,解析当用户最初通过身份验证后附加到票证的以管道符分隔的组名列表: // 创建票证后,为 UserData 属性指定一个
// 以管道符分隔的组名字符串。
String[] groups = authTicket.UserData.Split(new char[]{'|'});
| 6. | 添加以下代码,使用从票证名中获取的用户名创建一个 GenericIdentity 对象,并创建一个包含此标识以及用户组列表的 GenericPrincipal 对象: // 创建一个标识对象
GenericIdentity id = new GenericIdentity(authTicket.Name,
"LdapAuthentication");
// 该主体将通过整个请求。
GenericPrincipal principal = new GenericPrincipal(id, groups);
// 将新的主体对象附加到当前的 HttpContext 对象
Context.User = principal;
| |
测试应用程序
此过程使用 Web 应用程序请求 default.aspx 页。您将被重定向到登录页以进行身份验证。身份验证成功后,浏览器将被重定向到最初请求的 default.aspx 页。这将从 GenericPrincipal 对象(该对象在身份验证过程中与当前请求关联起来)中提取并显示通过身份验证的用户所属的组的列表。
• | 测试应用程序
1. | 在“生成”菜单上,单击“生成解决方案”。 | 2. | 在解决方案资源管理器中,右键单击“default.aspx”,然后单击“在浏览器中查看”。 | 3. | 输入有效的域名、用户名和密码,然后单击“登录”。 | 4. | 成功地通过身份验证后,您将被重定向回 default.aspx。本页上的代码将显示通过身份验证的用户名。 若要查看通过身份验证的用户所在组的列表,请在 global.aspx.cs 文件中的 Application_AuthenticateRequest 事件处理程序的结尾添加以下代码: Response.Write("组: " + authTicket.UserData + "<br>");
| |