借助于 ASP.NET 表单身份验证,用户可通过在 Web 表单上输入凭据(用户名和密码)来表明自己的身份。在收到这些凭据后,Web 应用程序对照数据源检查用户名和密码以验证用户的身份。
本“如何做”介绍如何通过使用轻量目录访问协议 (LDAP) 对照 Microsoft? Active Directory? 目录服务来验证用户。这里还介绍了如何检索用户所属的安全组和通讯组;如何将此类信息存储在 GenericPrincipal 对象中,以及如何将它存储在 HttpContext.Current.User 属性(与请求一起传递到 ASP.NET Web 应用程序中)。可随后将其用于 .NET 基于角色的授权。
需求
以下各项介绍了推荐的硬件、软件、网络基础结构、技巧和知识以及您需要的服务包。
● Microsoft Windows? 2000 操作系统
● Microsoft Visual Studio? .NET 开发系统
“如何做”中的过程还要求您具备 Microsoft Visual C#? 开发工具的相关知识。
总结
“如何做”包括如下过程:
1. 创建一个具有登录页面的 Web 应用程序
2. 配置 Web 应用程序的表单身份验证
3. 开发 LDAP 身份验证代码,以便在 Active Directory 中查找用户
4. 开发 LDAP组检索代码,以便查找用户的组成员身份
5. 验证用户的身份并创建表单身份验证票
6. 实现身份验证请求处理程序以构造 GenericPrincipal 对象
7. 测试应用程序
1. 创建一个具有登录页面的 Web 应用程序
此过程创建一个简单的 C# Web 应用程序,该应用程序包含一个登录页面(用户可在其中输入用户名和密码)和一个默认页面(显示与当前 Web 请求关联的标识名称和组成员身份信息)。
u 创建一个具有登录页面的 Web 应用程序
1. 启动 Visual Studio .NET,然后创建一个名为 FormsAuthAD 的新 C# ASP.NET Web 应用程序。
2. 使用解决方案资源管理器将 WebForm1.aspx 重命名为 Logon.aspx。
3. 添加一个新的 System.DirectoryServices.dll 程序集引用。这可提供对 System.DirectoryServices 命名空间的访问,该命名空间包含用于 Active Directory 查询和操作的托管类型。
4. 将表 1 中所列的控件添加到 Logon.aspx,创建一个简单的登录表单。
表 1:Logon.aspx 控件
控件类型 | 文本 | ID |
标签 | 域名: | - |
标签 | 用户名: | - |
标签 | 密码 | - |
文本框 | - | 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 );
2. 配置 Web 应用程序的表单身份验证
此过程编辑应用程序的 Web.config 文件,以配置该应用程序的表单身份验证。
u 配置 Web 应用程序的表单身份验证
1. 使用解决方案资源管理器打开 Web.config。
2. 查找 <authentication> 元素并将 mode 属性改为 Forms。
3. 将下面的 <forms> 元素作为身份验证元素的一个子元素进行添加,并按如下所示设置 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> 元素,并将模拟属性设置为 true。这将导致 ASP.NET 模拟先前指定的匿名帐户。
<identity impersonate="true"/>
由于采用此配置,所有应用程序请求均在配置的匿名帐户的安全上下文中运行。用户将通过 Web 表单来提供凭据以进行 Active Directory 身份验证,但用于访问 Active Directory 的帐户是配置的匿名帐户。
3. 开发 LDAP 身份验证代码,以便在 Active Directory 中查找用户
此过程将新的帮助器类添加到 Web 应用程序中以封装 LDAP 代码。该类最初提供 IsAuthenticated 方法以针对 Active Directory 用户对象验证提供的域、用户名和密码。
u 开发 LDAP 身份验证代码,以便在 Active Directory 中查找用户
1. 添加名为 LdapAuthentication.cs 的新 C# 类文件。
2. 添加对 System.DirectoryServices.dll 程序集的引用。
3. 将以下 using 语句添加到 LdapAuthentication.cs 顶部。
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. 添加以下将域名、用户名和密码作为参数并返回 bool 的 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
{
// 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;
}
4. 开发 LDAP 组检索代码,以便查找用户的组成员身份
此过程扩展 LdapAuthentication 类以提供一个 GetGroups 方法,该方法将检索当前用户所在的组列表。该 GetGroups 方法将组列表作为管道分隔的字符串返回(如下所示)。
"Group1|Group2|Group3|"
u 开发 LDAP组检索代码,以便查找用户的组成员身份
1. 将 GetGroups 方法的以下实现添加到 LdapAuthentication 类中。
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();
}
5. 验证用户的身份并创建表单身份验证票
此过程实现 btnLogon_Click 事件处理程序以验证用户的身份。对于已验证的用户,您将随后创建一个包含用户组列表的表单身份验证票。然后,将用户重定向到他们所请求的原始页面(在被重定向到登录页之前)。
u 验证用户的身份并创建表单身份验证票
1. 返回到 Logon.aspx 表单,并双击“登录”按钮以创建一个空的 btnLogon_Click 事件处理程序。
2. 在文件顶部的现有 using 语句下面,添加下面的 using 语句。它可提供对 FormsAuthentication 方法的访问。
using System.Web.Security;
3. 添加代码以创建初始化的 LdapAuthentication 类的新实例以指向 LDAP Active Directory(如下面的代码所示)。切记更改路径以指向 Active Directory 服务器。
// Path to you LDAP directory server.
// Contact your network administrator to obtain a valid path.
string adPath = "LDAP://yourCompanyName.com/DC=yourCompanyName,DC=com";
LdapAuthentication adAuth = new LdapAuthentication(adPath);
4. 添加下面的代码以执行以下步骤:
a. 对调用者进行 Active Directory 身份验证。
b. 检索用户所在的组列表。
c. 创建一个包含组列表的 FormsAuthenticationTicket。
d. 加密该票。
e. 创建包含加密票的新 Cookie。
f. 将该 Cookie 添加到返回到用户浏览器的 Cookie 列表中。
try
{
if(true == adAuth.IsAuthenticated(txtDomainName.Text,
txtUserName.Text,
txtPassword.Text))
{
// Retrieve the user's groups
string groups = adAuth.GetGroups();
// Create the authetication ticket
FormsAuthenticationTicket authTicket =
new FormsAuthenticationTicket(1, // version
txtUserName.Text,
DateTime.Now,
DateTime.Now.AddMinutes(60),
false, groups);
// Now encrypt the ticket.
string encryptedTicket = FormsAuthentication.Encrypt(authTicket);
// Create a cookie and add the encrypted ticket to the
// cookie as data.
HttpCookie authCookie =
new HttpCookie(FormsAuthentication.FormsCookieName,
encryptedTicket);
// Add the cookie to the outgoing cookies collection.
Response.Cookies.Add(authCookie);
// Redirect the user to the originally requested page
Response.Redirect(
FormsAuthentication.GetRedirectUrl(txtUserName.Text,
false));
}
else
{
lblError.Text =
"Authentication failed, check username and password.";
}
}
catch (Exception ex)
{
lblError.Text = "Error authenticating." + ex.Message;
}
6. 实现身份验证请求处理程序以构造 GenericPrincipal 对象
此过程在 global.asax 中实现 Application_AuthenticateRequest 事件处理程序,并给当前已验证的用户创建 GenericPrincipal 对象。它将包含用户所在组的一个列表,该组列表是从身份验证 Cookie 中包含的 FormsAuthenticationTicket 中检索而来的。最后,将 GenericPrincipal 对象与为每个 Web 请求创建的当前 HttpContext 对象关联起来。
u 实现身份验证请求处理程序以构造 GenericPrincipal 对象
1. 使用解决方案资源管理器打开 global.asax.cs。
2. 将以下 using 语句添加到文件顶部。
using System.Web.Security;
using System.Security.Principal;
3. 找到 Application_AuthenticateRequest 事件处理程序并添加以下代码,以便从用该请求传递的 Cookie 集合中获取包含加密 FormsAuthenticationTicket 的 Cookie。
// Extract the forms authentication cookie
string cookieName = FormsAuthentication.FormsCookieName;
HttpCookie authCookie = Context.Request.Cookies[cookieName];
if(null == authCookie)
{
// There is no authentication cookie.
return;
}
4. 添加以下代码,以便从该 Cookie 中提取并解密 FormsAuthenticationTicket。
FormsAuthenticationTicket authTicket = null;
try
{
authTicket = FormsAuthentication.Decrypt(authCookie.Value);
}
catch (Exception ex)
{
// Log exception details (omitted for simplicity)
return;
}
if (null == authTicket)
{
// Cookie failed to decrypt.
return;
}
5. 添加以下代码,以便解析出用户最初进行身份验证时附加到该票上的组名管道分隔列表。
// When the ticket was created, the UserData property was assigned a
// pipe delimited string of group names.
String[] groups = authTicket.UserData.Split(new char[]{'|'});
6. 添加以下代码,以便使用从该票名获取的用户名来创建 GenericIdentity 对象,并创建包含该标识和该用户组列表的 GenericPrincipal 对象。
// Create an Identity object
GenericIdentity id = new GenericIdentity(authTicket.Name,
"LdapAuthentication");
// This principal will flow throughout the request.
GenericPrincipal principal = new GenericPrincipal(id, groups);
// Attach the new principal object to the current HttpContext object
Context.User = principal;
7. 测试应用程序
此过程使用 Web 应用程序来请求 default.aspx 页。您将被重定向到登录页以进行身份验证。在成功进行身份验证后,您的浏览器将被重定向到原来请求的 default.aspx 页。这将从 GenericPrincipal 对象(身份验证过程已将其与当前请求关联起来)提取并显示经身份验证的用户所属的组列表。
u 测试应用程序
1. 在“构建”菜单中,单击“构建解决方案”
2. 在解决方案资源管理器中,右键单击 default.aspx,然后单击“在浏览器中查看”。
3. 输入有效的域名、用户名和密码,然后单击“登录”。
4. 如果身份验证成功,您就会被重定向回 default.aspx。本页面上的代码应该显示经身份验证的用户的用户名。
要查看经身份验证的用户所属的组的列表,请在 global.aspx.cs 文件中的 Application_AuthenticateRequest 事件处理程序的结束处添加以下代码。
Response.Write("Groups: " + authTicket.UserData + "<br>");