498)this.width=498;'' onmousewheel = ''javascript:return big(this)'' alt="" src="/uploadfile/201301/5/95151816363.png" />
在前面的代码,我在搜索Active Directory时,只搜索了"name,givenName,samaccountname,mail"这4个属性。然而,LDAP还支持更多的属性,我们可以使用下面的代码查看更多的用户信息: 498)this.width=498;'' onmousewheel = ''javascript:return big(this)'' alt="" src="/uploadfile/201301/5/DF151816784.png" />
- private static string AllProperties = @"
- homemdb
- distinguishedname
- countrycode
- cn
- lastlogoff
- mailnickname
- dscorepropagationdata
- msexchhomeservername
- msexchmailboxsecuritydescriptor
- msexchalobjectversion
- usncreated
- objectguid
- whenchanged
- memberof
- msexchuseraccountcontrol
- accountexpires
- displayname
- primarygroupid
- badpwdcount
- objectclass
- instancetype
- objectcategory
- samaccounttype
- whencreated
- lastlogon
- useraccountcontrol
- physicaldeliveryofficename
- samaccountname
- usercertificate
- givenname
- userparameters
- adspath
- homemta
- msexchmailboxguid
- pwdlastset
- logoncount
- codepage
- name
- usnchanged
- legacyexchangedn
- proxyaddresses
- department
- userprincipalname
- badpasswordtime
- objectsid
- sn
- mdbusedefaults
- telephonenumber
- showinaddressbook
- msexchpoliciesincluded
- textencodedoraddress
- lastlogontimestamp
- company
- ";
在ASP.NET中访问Active Directory
前面我在一个控制台程序中演示了访问Active Directory的方法,通过示例我们可以看到:在代码中,我用Environment.UserName就可以得到当前用户的登录名。然而,如果是在ASP.NET程序中,访问Environment.UserName就很有可能得不到真正用户登录名。因为:Environment.UserName是使用WIN32API中的GetUserName获取线程相关的用户名,但ASP.NET运行在IIS中,线程相关的用户名就不一定是客户端的用户名了。不过,ASP.NET可以模拟用户方式运行,通过这种方式才可以得到正确的结果。关于“模拟”的话题在本文的后面部分有说明。
在ASP.NET中,为了能可靠的获取登录用户的登录名,我们可以使用下面的代码:
- /// <summary>
- /// 根据指定的HttpContext对象,获取登录名。
- /// </summary>
- /// <param name="context"></param>
- /// <returns></returns>
- public static string GetUserLoginName(HttpContext context)
- {
- if( context == null )
- return null;
- if( context.Request.IsAuthenticated == false )
- return null;
- string userName = context.User.Identity.Name;
- // 此时userName的格式为:UserDomainName\LoginName
- // 我们只需要后面的LoginName就可以了。
- string[] array = userName.Split(new char[] { ''\\'' }, StringSplitOptions.RemoveEmptyEntries);
- if( array.Length == 2 )
- return array[1];
- return null;
- }
在ASP.NET中使用Windows身份认证时,IIS和WindowsAuthenticationModule已经做了许多验证用户的相关工作,虽然我们可以使用前面的代码获取到用户的登录名,但用户的其它信息即需要我们自己来获取。在实际使用Windows身份认证时,我们要做的事:基本上就是从Active Directory中根据用户的登录名获取所需的各种信息。
比如:我的程序在运行时,还需要使用以下与用户相关的信息:
- public sealed class UserInfo
- {
- public string GivenName;
- public string FullName;
- public string Email;
- }
那么,我们可以使用这样的代码来获取所需的用户信息: 498)this.width=498;'' onmousewheel = ''javascript:return big(this)'' alt="" src="/uploadfile/201301/5/DF151816784.png" />
- public static class UserHelper
- {
- /// <summary>
- /// 活动目录中的搜索路径,也可根据实际情况来修改这个值。
- /// </summary>
- public static string DirectoryPath = "LDAP://" + GetDomainName();
- /// <summary>
- /// 获取与指定HttpContext相关的用户信息
- /// </summary>
- /// <param name="context"></param>
- /// <returns></returns>
- public static UserInfo GetCurrentUserInfo(HttpContext context)
- {
- string loginName = GetUserLoginName(context);
- if( string.IsNullOrEmpty(loginName) )
- return null;
- return GetUserInfoByLoginName(loginName);
- }
- /// <summary>
- /// 根据指定的HttpContext对象,获取登录名。
- /// </summary>
- /// <param name="context"></param>
- /// <returns></returns>
- public static string GetUserLoginName(HttpContext context)
- {
- if( context == null )
- return null;
- if( context.Request.IsAuthenticated == false )
- return null;
- string userName = context.User.Identity.Name;
- // 此时userName的格式为:UserDomainName\LoginName
- // 我们只需要后面的LoginName就可以了。
- string[] array = userName.Split(new char[] { ''\\'' }, StringSplitOptions.RemoveEmptyEntries);
- if( array.Length == 2 )
- return array[1];
- return null;
- }
- /// <summary>
- /// 根据登录名查询活动目录,获取用户信息。
- /// </summary>
- /// <param name="loginName"></param>
- /// <returns></returns>
- public static UserInfo GetUserInfoByLoginName(string loginName)
- {
- if( string.IsNullOrEmpty(loginName) )
- return null;
- // 下面的代码将根据登录名查询用户在AD中的信息。
- // 为了提高性能,可以在此处增加一个缓存容器(Dictionary or Hashtable)。
- try {
- DirectoryEntry entry = new DirectoryEntry(DirectoryPath);
- DirectorySearcher search = new DirectorySearcher(entry);
- search.Filter = "(SAMAccountName=" + loginName + ")";
- search.PropertiesToLoad.Add("givenName");
- search.PropertiesToLoad.Add("cn");
- search.PropertiesToLoad.Add("mail");
- // 如果还需要从AD中获取其它的用户信息,请参考ActiveDirectoryDEMO
- SearchResult result = search.FindOne();
- if( result != null ) {
- UserInfo info = new UserInfo();
- info.GivenName = result.Properties["givenName"][0].ToString();
- info.FullName = result.Properties["cn"][0].ToString();
- info.Email = result.Properties["mail"][0].ToString();
- return info;
- }
- }
- catch {
- // 如果需要记录异常,请在此处添加代码。
- }
- return null;
- }
- private static string GetDomainName()
- {
- // 注意:这段代码需要在Windows XP及较新版本的操作系统中才能正常运行。
- SelectQuery query = new SelectQuery("Win32_ComputerSystem");
- using( ManagementObjectSearcher searcher = new ManagementObjectSearcher(query) ) {
- foreach( ManagementObject mo in searcher.Get() ) {
- if( (bool)mo["partofdomain"] )
- return mo["domain"].ToString();
- }
- }
- return null;
- }
- }
使用UserHelper的页面代码:
- <html xmlns="http://www.w3.org/1999/xhtml">
- <head>
- <title>WindowsAuthentication DEMO - http://www.cnblogs.com/fish-li/</title>
- </head>
- <body>
- <% if( Request.IsAuthenticated ) { %>
- 当前登录全名:<%= Context.User.Identity.Name.HtmlEncode()%> <br />
- <% var user = UserHelper.GetCurrentUserInfo(Context); %>
- <% if( user != null ) { %>
- 用户短名:<%= user.GivenName.HtmlEncode()%> <br />
- 用户全名:<%= user.FullName.HtmlEncode() %> <br />
- 邮箱地址:<%= user.Email.HtmlEncode() %>
- <% } %>
- <% } else { %>
- 当前用户还未登录。
- <% } %>
- </body>
- </html>
程序运行的效果如下:
498)this.width=498;'' onmousewheel = ''javascript:return big(this)'' alt="" src="http://images.myeducs.cn/files/uploadimg/20120507/1452184.png" />
另外,还可以从Active Directory查询一个叫做memberof的属性(它与Windows用户组无关),有时候可以用它区分用户,设计与权限相关的操作。
在设计数据持久化的表结构时,由于此时没有“用户表”,那么我们可以直接保存用户的登录名。剩下的开发工作就与Forms身份认证没有太多的差别了。
使用Active Directory验证用户身份
前面介绍了ASP.NET Windows身份认证,在这种方式下,IIS和WindowsAuthenticationModule为我们实现了用户身份认证的过程。然而,有时可能由于各种原因,需要我们以编程的方式使用Active Directory验证用户身份,比如:在WinForm程序,或者其它的验证逻辑。
我们不仅可以从Active Directory中查询用户信息,也可以用它来实现验证用户身份,这样便可以实现自己的登录验证逻辑。
不管是如何使用Active Directory,我们都需要使用DirectoryEntry和DirectorySearcher这二个对象。 DirectoryEntry还提供一个构造函数可让我们输入用户名和密码:
- // 摘要:
- // 初始化 System.DirectoryServices.DirectoryEntry 类的新实例。
- //
- // 参数:
- // Password:
- // 在对客户端进行身份验证时使用的密码。DirectoryEntry.Password 属性初始化为该值。
- //
- // username:
- // 在对客户端进行身份验证时使用的用户名。DirectoryEntry.Username 属性初始化为该值。
- //
- // Path:
- // 此 DirectoryEntry 的路径。DirectoryEntry.Path 属性初始化为该值。
- public DirectoryEntry(string path, string username, string password);
要实现自己的登录检查,就需要使用这个构造函数。以下是我写用WinForm写的一个登录检查的示例:
- private void btnLogin_Click(object sender, EventArgs e)
- {
- if( txtUsername.Text.Length == 0 || txtPassword.Text.Length == 0 ) {
- MessageBox.Show("用户名或者密码不能为空。", this.Text, MessageBoxButtons.OK, MessageBoxIcon.Warning);
- return;
- }
- string ldapPath = "LDAP://" + GetDomainName();
- string domainAndUsername = Environment.UserDomainName + "\\" + txtUsername.Text;
- DirectoryEntry entry = new DirectoryEntry(ldapPath, domainAndUsername, txtPassword.Text);
- DirectorySearcher search = new DirectorySearcher(entry);
- try {
- SearchResult result = search.FindOne();
- MessageBox.Show("登录成功。", this.Text, MessageBoxButtons.OK, MessageBoxIcon.Information);
- }
- catch( Exception ex ) {
- // 如果用户名或者密码不正确,也会抛出异常。
- MessageBox.Show(ex.Message, this.Text, MessageBoxButtons.OK, MessageBoxIcon.Stop);
- }
- }
程序运行的效果如下:
498)this.width=498;'' onmousewheel = ''javascript:return big(this)'' alt="" src="/uploadfile/201301/5/C7151816530.png" />
安全上下文与用户模拟
在ASP.NET Windows身份认证环境中,与用户相关的安全上下文对象保存在HttpContext.User属性中,是一个类型为WindowsPrincipal的对象,我们还可以访问HttpContext.User.Identity来获取经过身份认证的用户标识,它是一个WindowsIdentity类型的对象。
在.NET Framework中,我们可以通过WindowsIdentity.GetCurrent()获取与当前线程相关的WindowsIdentity对象,这种方法获取的是当前运行的Win32线程的安全上下文标识。由于ASP.NET运行在IIS进程中,因此ASP.NET线程的安全标识其实是从IIS的进程中继承的,所以此时用二种方法得到的WindowsIdentity对象其实是不同的。
在Windows操作系统中,许多权限检查都是基于Win32线程的安全上下文标识,于是前面所说的二种WindowsIdentity对象会造成编程模型的不一致问题,为了解决这个问题,ASP.NET提供了“模拟”功能,允