上篇博客我谈到了一些关于ASP.NET Forms身份认证方面的话题,此次的博客将主要介绍ASP.NET Windows身份认证。web
Forms身份认证虽然使用普遍,不过,若是是在 Windows Active Directory 的环境中使用ASP.NET, 那么使用Windows身份认证也会比较方便。 方便性表现为:咱们不用再设计登陆页面,不用编写登陆验证逻辑。并且使用Windows身份认证会有更好的安全保障。编程
要使用Windows身份认证模式,须要在web.config设置:浏览器
<authentication mode="Windows" />
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身份认证则依赖于IIS 2. Windows身份认证不须要咱们设计登陆页面,不用编写登陆验证逻辑,所以更容易使用。dom
在受权阶段,UrlAuthorizationModule仍然会根据当前用户检查将要访问的资源是否获得许可。 接下来,FileAuthorizationModule检查 HttpContext.User.Identity 属性中的 IIdentity 对象是不是 WindowsIdentity 类的一个实例。 若是 IIdentity 对象不是 WindowsIdentity 类的一个实例,则 FileAuthorizationModule 类中止处理。 若是存在 WindowsIdentity 类的一个实例,则 FileAuthorizationModule 类调用 AccessCheck Win32 函数(经过 P/Invoke) 来肯定是否受权通过身份验证的客户端访问请求的文件。 若是该文件的安全描述符的随机访问控制列表 (DACL) 中至少包含一个 Read 访问控制项 (ACE),则容许该请求继续。 不然,FileAuthorizationModule 类调用 HttpApplication.CompleteRequest 方法并将状态码 401 返回到客户端。 ide
在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这二个与用户身份相关的对象。
咱们一般使用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还支持更多的属性,咱们可使用下面的代码查看更多的用户信息:
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 mail userparameters adspath homemta msexchmailboxguid pwdlastset logoncount codepage name usnchanged legacyexchangedn proxyaddresses department userprincipalname badpasswordtime objectsid sn mdbusedefaults telephonenumber showinaddressbook msexchpoliciesincluded textencodedoraddress lastlogontimestamp company ";
前面我在一个控制台程序中演示了访问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; }
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>
程序运行的效果以下:
另外,还能够从Active Directory查询一个叫作memberof的属性(它与Windows用户组无关),有时候能够用它区分用户,设计与权限相关的操做。
在设计数据持久化的表结构时,因为此时没有“用户表”,那么咱们能够直接保存用户的登陆名。 剩下的开发工做就与Forms身份认证没有太多的差异了。
前面介绍了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); } }
程序运行的效果以下:
在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提供了“模拟”功能,容许线程以特定的Windows账户的安全上下文来访问资源。
为了能更好的理解模拟的功能,我准备了一个示例(ShowWindowsIdentity.ashx):
public class ShowWindowsIdentity : IHttpHandler { public void ProcessRequest (HttpContext context) { // 要观察【模拟】的影响, // 能够启用,禁止web.config中的设置:<identity impersonate="true"/> context.Response.ContentType = "text/plain"; context.Response.Write(Environment.UserDomainName + "\\" + Environment.UserName + "\r\n"); WindowsPrincipal winPrincipal = (WindowsPrincipal)HttpContext.Current.User; context.Response.Write(string.Format("HttpContext.Current.User.Identity: {0}, {1}\r\n", winPrincipal.Identity.AuthenticationType, winPrincipal.Identity.Name)); WindowsPrincipal winPrincipal2 = (WindowsPrincipal)Thread.CurrentPrincipal; context.Response.Write(string.Format("Thread.CurrentPrincipal.Identity: {0}, {1}\r\n", winPrincipal2.Identity.AuthenticationType, winPrincipal2.Identity.Name)); WindowsIdentity winId = WindowsIdentity.GetCurrent(); context.Response.Write(string.Format("WindowsIdentity.GetCurrent(): {0}, {1}", winId.AuthenticationType, winId.Name)); }
首先,在web.config中设置:
<authentication mode="Windows" />
注意:要把网站部署在IIS中,不然看不出效果。
此时,访问ShowWindowsIdentity.ashx,将看到以下图所示的结果:
如今修改一下web.config中设置:(注意:后面加了一句配置)
<authentication mode="Windows" /> <identity impersonate="true"/>
此时,访问ShowWindowsIdentity.ashx,将看到以下图所示的结果:
说明: 1. FISH-SRV2003是个人计算机名。它在一个没有域的环境中。 2. fish-li是个人一个Windows账号的登陆名。 3. 网站部署在IIS6中,进程以NETWORK SERVICE账号运行。 4. 打开网页时,我输入的用户名是fish-li
前面二张图片的差别之处其实也就是ASP.NET的“模拟”所发挥的功能。
关于模拟,我想说四点: 1. 在ASP.NET中,咱们应该访问HttpContext.User.Identity获取当前用户标识,那么就不存在问题(此时能够不须要模拟),例如FileAuthorizationModule就是这样处理的。 2. 模拟只是在ASP.NET应用程序访问Windows系统资源时须要应用Windows的安全检查功能才会有用。 3. Forms身份认证也能配置模拟功能,但只能模拟一个Windows账户。 4. 绝大多数状况下是不须要模拟的。
与使用Forms身份认证的程序不一样,使用Windows身份认证的程序须要额外的配置步骤。 这个小节将主要介绍在IIS中配置Windows身份认证,我将经常使用的IIS6和IIS7.5为例分别介绍这些配置。
IIS6的配置 请参考下图:
IIS7.5的配置 请参考下图:
注意:Windows身份认证是须要安装的,方法请参考下图:
当咱们用浏览器访问一个使用Windows身份认证的网站时,浏览器都会弹出一个对话框(左IE,右Safari):
此时,要求咱们输入Windows的登陆账号,而后交给IIS验证身份。
首次弹出这个对话框很正常:由于程序要验证用户的身份。 然而,每次关闭浏览器下次从新打开页面时,又会出现此对话框,此时感受就很不方便了。 虽然有些浏览器能记住用户名和密码,但我发现FireFox,Opera,Chrome仍然会弹出这个对话框,等待咱们点击肯定, 只有Safari才不会打扰用户直接打开网页。 IE的那个“记住个人密码”复选框彻底是个摆设,它根本不会记住密码!
所以,我所试过的全部浏览器中,只有Safari是最人性化的。 虽然在默认状况下,虽然IE不会记住密码,每次都须要再次输入。 不过,IE却能够支持不提示用户输入登陆账号而直接打开网页, 此时IE将使用用户的当前Windows登陆账号传递给IIS验证身份。
要让IE打开一个Windows身份认证的网站不提示登陆对话框,必须知足如下条件: 1. 必须在 IIS 的 Web 站点属性中启用 Windows 集成身份验证。 2. 客户端和Web服务器都必须在基于Microsoft Windows的同一个域内。 3. Internet Explorer 必须把所请求的 URL 视为 Intranet(本地)。 4. Internet Explorer 的 Intranet 区域的安全性设置必须设为“只在 Intranet 区域自动登陆”。 5. 请求Web页的用户必须具备访问该Web页以及该Web页中引用的全部对象的适当的文件系统(NTFS)权限。 6. 用户必须用域账号登陆到Windows 。
在这几个条件中,若是网站是在一个Windows域中运行,除了第3条可能不知足外,其它条件应该都容易知足(第4条是默认值)。 所以,要让IE不提示输入登陆账号,只要确保第3条知足就能够了。 下面的图片演示了如何完成这个配置:(注意:配置方法也适合用域名访问的状况)
另外,除了在IE中设置Intranet外,还能够在访问网站时,用计算机名代替IP地址或者域名, 那么IE始终认为是在访问Intranet内的网站,此时也不会弹出登陆对话框。
在此,我想再啰嗦三句: 1. IE在集成Windows身份认证时,虽然不提示登陆对话框,可是不表示不安全,它会自动传递登陆凭据。 2. 这种行为只有IE才能支持。(其它的浏览器只是会记住密码,在实现上实际上是不同的。) 3. 集成Windows身份认证,也只适合在Intranet的环境中使用。
在上篇博客中,我演示了如何用代码访问一个使用Forms身份认证的网站中的受限页面,方法是使用CookieContainer对象接收服务端生的登陆Cookie。 然而,在Windows身份认证的网站中,身份验证的过程发生在IIS中,并且根本不使用Cookie保存登陆状态,而是须要在请求时发送必要的身份验证信息。
在使用代码作为客户端访问Web服务器时,咱们仍然须要使用HttpWebRequest对象。 为了能让HttpWebRequest在访问IIS时发送必要的身份验证信息,HttpWebRequest提供二个属性均可以完成这个功能:
// 获取或设置请求的身份验证信息。
//
// 返回结果:
// 包含与该请求关联的身份验证凭据的 System.Net.ICredentials。默认为 null。
public override ICredentials Credentials { get; set; } // 获取或设置一个 System.Boolean 值,该值控制默认凭据是否随请求一块儿发送。 // // 返回结果: // 若是使用默认凭据,则为 true;不然为 false。默认值为 false。 public override bool UseDefaultCredentials { get; set; }
下面是我准备的完整的示例代码(注意代码中的注释):
static void Main(string[] args) { try { // 请把WindowsAuthWebSite1这个网站部署在IIS中, // 开启Windows认证方式,并禁止匿名用户访问。 // 而后修改下面的访问地址。 HttpWebRequest request = (HttpWebRequest)WebRequest.Create("http://localhost:33445/Default.aspx"); // 下面三行代码,启用任意一行都是能够的。 request.UseDefaultCredentials = true; //request.Credentials = CredentialCache.DefaultCredentials; //request.Credentials = CredentialCache.DefaultNetworkCredentials; // 若是上面的三行代码全被注释了,那么将会看到401的异常信息。 using( HttpWebResponse response = (HttpWebResponse)request.GetResponse() ) { using( StreamReader sr = new StreamReader(response.GetResponseStream()) ) { Console.WriteLine(sr.ReadToEnd()); } } } catch( WebException wex ) { Console.WriteLine("====================================="); Console.WriteLine("异常发生了。"); Console.WriteLine("====================================="); Console.WriteLine(wex.Message); } }
其实关键部分仍是设置UseDefaultCredentials或者Credentials,代码中的三种方法是有效的。 这三种方法的差异: 1. Credentials = CredentialCache.DefaultCredentials; 表示在发送请求会带上当前用户的身份验证凭据。 2. UseDefaultCredentials = true; 此方法在内部会调用前面的方法,所以与前面的方法是同样的。 3. Credentials = CredentialCache.DefaultNetworkCredentials; 是在.NET 2.0中引用的新方法。
关于DefaultCredentials和DefaultNetworkCredentials的更多差异,请看我整理的表格:
Credentials属性 | 申明类型 | 实例类型 | .NET支持版本 |
DefaultCredentials | ICredentials | SystemNetworkCredential | 从1.0开始 |
DefaultNetworkCredentials | NetworkCredential | SystemNetworkCredential | 从2.0开始 |
三个类型的继承关系: 1. NetworkCredential实现了ICredentials接口, 2. SystemNetworkCredential继承自NetworkCredential。
在结束这篇博客以前,我想我应该感谢新蛋。 在新蛋的网络环境中,让我学会了使用Windows身份认证。 除了感谢以外,我如今还特别怀念 fl45 这个登陆名......