认识ASP.NET Windows身份认证要使用Windows身份认证模式需要在web.config设置authentication modeWindows /Windows身份认证做为ASP.NET的默认认证方式与Forms身份认证在许多基础方面是一样的。 上篇博客我说过我认为ASP.NET的身份认证的最核心部分其实就是HttpContext.User这个属性所指向的对象。在接下来的部分我将着重分析这个对象在二种身份认证中有什么差别。在ASP.NET身份认证过程中IPrincipal和IIdentity这二个接口有着非常重要的作用。 前者定义用户对象的基本功能后者定义标识对象的基本功能 不同的身份认证方式得到的这二个接口的实例也是不同的。ASP.NET Windows身份认证是由WindowsAuthenticationModule实现的。 WindowsAuthenticationModule在ASP.NET管线的AuthenticateRequest事件中 使用从IIS传递到ASP.NET的Windows访问令牌(Token)创建一个WindowsIdentity对象Token通过调用context.WorkerRequest.GetUserToken()获得 然后再根据WindowsIdentity 对象创建WindowsPrincipal对象 然后把它赋值给HttpContext.User。在Forms身份认证中我们需要创建登录页面让用户提交用户名和密码然后检查用户名和密码的正确性 接下来创建一个包含FormsAuthenticationTicket对象的登录Cookie供后续请求使用。 FormsAuthenticationModule在ASP.NET管线的AuthenticateRequest事件中 解析登录Cookie并创建一个包含FormsIdentity的GenericPrincipal对象 然后把它赋值给HttpContext.User。上面二段话简单了概括了二种身份认证方式的工作方式。我们可以发现它们存在以下差别1. Forms身份认证需要Cookie表示登录状态Windows身份认证则依赖于IIS2. Windows身份认证不需要我们设计登录页面不用编写登录验证逻辑因此更容易使用。在授权阶段UrlAuthorizationModule仍然会根据当前用户检查将要访问的资源是否得到许可。 接下来FileAuthorizationModule检查 HttpContext.User.Identity 属性中的 IIdentity 对象是否是 WindowsIdentity 类的一个实例。 如果 IIdentity 对象不是 WindowsIdentity 类的一个实例则 FileAuthorizationModule 类停止处理。 如果存在 WindowsIdentity 类的一个实例则 FileAuthorizationModule 类调用 AccessCheck Win32 函数通过 P/Invoke 来确定是否授权经过身份验证的客户端访问请求的文件。 如果该文件的安全描述符的随机访问控制列表 (DACL) 中至少包含一个 Read 访问控制项 (ACE)则允许该请求继续。 否则FileAuthorizationModule 类调用 HttpApplication.CompleteRequest 方法并将状态码 401 返回到客户端。在Windows身份认证中验证工作主要是由IIS实现的WindowsAuthenticationModule其实只是负责创建WindowsPrincipal和WindowsIdentity而已。 顺便介绍一下Windows 身份验证又分为“NTLM 身份验证”和“Kerberos v5 身份验证”二种 关于这二种Windows身份认证的更多说明可查看MSDN技术文章解释ASP.NET 2.0 中的 Windows 身份验证。 在我看来IIS最终使用哪种Windows身份认证方式并不影响我们的开发过程因此本文不会讨论这个话题。根据我的实际经验来看使用Windows身份认证时主要的开发工作将是根据登录名从Active Directory获取用户信息。 因为此时不需要我们再设计登录过程IIS与ASP.NET已经为我们准备好了WindowsPrincipal和WindowsIdentity这二个与用户身份相关的对象。回到顶部访问 Active Directory我们通常使用LDAP协议来访问Active Directory 在.net framework中提供了DirectoryEntry和DirectorySearcher这二个类型让我们可以方便地从托管代码中访问 Active Directory 域服务。如果我们要在test.corp”这个域中搜索某个用户信息我们可以使用下面的语句构造一个DirectoryEntry对象DirectoryEntry entry new DirectoryEntry(LDAP://test.corp);在这段代码中我采用硬编码的方式把域名写进了代码。我们如何知道当前电脑所使用的是哪个域名呢答案是查看“我的电脑”的属性对话框注意这个域名不一定与System.Environment.UserDomainName相同。除了可以查看“我的电脑”的属性对话框外我们还可以使用代码的方式获取当前电脑所使用的域名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; }当构造了DirectorySearcher对象后我们便可以使用DirectorySearcher来执行对Active Directory的搜索。我们可以使用下面的步骤来执行搜索1. 设置 DirectorySearcher.Filter 指示LDAP格式筛选器这是一个字符串。2. 多次调用PropertiesToLoad.Add() 设置搜索过程中要检索的属性列表。3. 调用FindOne() 方法获取搜索结果。下面的代码演示了如何从Active Directory中搜索登录名为“fl45”的用户信息static void Main(string[] args) { Console.WriteLine(Environment.UserDomainName); Console.WriteLine(Environment.UserName); Console.WriteLine(------------------------------------------------); ShowUserInfo(fl45, GetDomainName()); } private static string AllProperties name,givenName,samaccountname,mail; public static void ShowUserInfo(string loginName, string domainName) { if( string.IsNullOrEmpty(loginName) || string.IsNullOrEmpty(domainName) ) return; string[] properties AllProperties.Split(new char[] { \r, \n, , }, StringSplitOptions.RemoveEmptyEntries); try { DirectoryEntry entry new DirectoryEntry(LDAP:// domainName); DirectorySearcher search new DirectorySearcher(entry); search.Filter (samaccountname loginName ); foreach( string p in properties ) search.PropertiesToLoad.Add(p); SearchResult result search.FindOne(); if( result ! null ) { foreach( string p in properties ) { ResultPropertyValueCollection collection result.Properties[p]; for( int i 0; i collection.Count; i ) Console.WriteLine(p : collection[i]); } } } catch( Exception ex ) { Console.WriteLine(ex.ToString()); } }结果如下在前面的代码我在搜索Active Directory时只搜索了name,givenName,samaccountname,mail这4个属性。 然而LDAP还支持更多的属性我们可以使用下面的代码查看更多的用户信息回到顶部在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 namecontext/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; }那么我们可以使用这样的代码来获取所需的用户信息使用UserHelper的页面代码html xmlnshttp://www.w3.org/1999/xhtml head titleWindowsAuthentication 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程序运行的效果如下另外还可以从Active Directory查询一个叫做memberof的属性它与Windows用户组无关有时候可以用它区分用户设计与权限相关的操作。在设计数据持久化的表结构时由于此时没有“用户表”那么我们可以直接保存用户的登录名。 剩下的开发工作就与Forms身份认证没有太多的差别了。回到顶部使用Active Directory验证用户身份前面介绍了ASP.NET Windows身份认证在这种方式下IIS和WindowsAuthenticationModule为我们实现了用户身份认证的过程。 然而有时可能由于各种原因需要我们以编程的方式使用Active Directory验证用户身份比如在WinForm程序或者其它的验证逻辑。我们不仅可以从Active Directory中查询用户信息也可以用它来实现验证用户身份这样便可以实现自己的登录验证逻辑。不管是如何使用Active Directory我们都需要使用DirectoryEntry和DirectorySearcher这二个对象。 DirectoryEntry还提供一个构造函数可让我们输入用户名和密码