上篇博客我談到了一些關(guān)于ASP.NET Forms身份認(rèn)證方面的話題,這次的博客將主要介紹ASP.NET Windows身份認(rèn)證。
Forms身份認(rèn)證雖然使用廣泛,不過,如果是在 Windows Active Directory 的環(huán)境中使用ASP.NET, 那么使用Windows身份認(rèn)證也會(huì)比較方便。 方便性表現(xiàn)為:我們不用再設(shè)計(jì)登錄頁面,不用編寫登錄驗(yàn)證邏輯。而且使用Windows身份認(rèn)證會(huì)有更好的安全保障。
要使用Windows身份認(rèn)證模式,需要在web.config設(shè)置:
<authentication mode="Windows" />
Windows身份認(rèn)證做為ASP.NET的默認(rèn)認(rèn)證方式,與Forms身份認(rèn)證在許多基礎(chǔ)方面是一樣的。上篇博客我說過:我認(rèn)為ASP.NET的身份認(rèn)證的最核心部分其實(shí)就是HttpContext.User這個(gè)屬性所指向的對(duì)象。在接下來的部分,我將著重分析這個(gè)對(duì)象在二種身份認(rèn)證中有什么差別。
在ASP.NET身份認(rèn)證過程中,IPrincipal和IIdentity這二個(gè)接口有著非常重要的作用。 前者定義用戶對(duì)象的基本功能,后者定義標(biāo)識(shí)對(duì)象的基本功能, 不同的身份認(rèn)證方式得到的這二個(gè)接口的實(shí)例也是不同的。
ASP.NET Windows身份認(rèn)證是由WindowsAuthenticationModule實(shí)現(xiàn)的。WindowsAuthenticationModule在ASP.NET管線的AuthenticateRequest事件中, 使用從IIS傳遞到ASP.NET的Windows訪問令牌(Token)創(chuàng)建一個(gè)WindowsIdentity對(duì)象,Token通過調(diào)用context.WorkerRequest.GetUserToken()獲得, 然后再根據(jù)WindowsIdentity 對(duì)象創(chuàng)建WindowsPrincipal對(duì)象, 然后把它賦值給HttpContext.User。
在Forms身份認(rèn)證中,我們需要?jiǎng)?chuàng)建登錄頁面,讓用戶提交用戶名和密碼,然后檢查用戶名和密碼的正確性, 接下來創(chuàng)建一個(gè)包含F(xiàn)ormsAuthenticationTicket對(duì)象的登錄Cookie供后續(xù)請(qǐng)求使用。FormsAuthenticationModule在ASP.NET管線的AuthenticateRequest事件中, 解析登錄Cookie并創(chuàng)建一個(gè)包含F(xiàn)ormsIdentity的GenericPrincipal對(duì)象, 然后把它賦值給HttpContext.User。
上面二段話簡(jiǎn)單了概括了二種身份認(rèn)證方式的工作方式。
我們可以發(fā)現(xiàn)它們存在以下差別:
1. Forms身份認(rèn)證需要Cookie表示登錄狀態(tài),Windows身份認(rèn)證則依賴于IIS
2. Windows身份認(rèn)證不需要我們?cè)O(shè)計(jì)登錄頁面,不用編寫登錄驗(yàn)證邏輯,因此更容易使用。
在授權(quán)階段,UrlAuthorizationModule仍然會(huì)根據(jù)當(dāng)前用戶檢查將要訪問的資源是否得到許可。 接下來,F(xiàn)ileAuthorizationModule檢查 HttpContext.User.Identity 屬性中的 IIdentity 對(duì)象是否是 WindowsIdentity 類的一個(gè)實(shí)例。 如果 IIdentity 對(duì)象不是 WindowsIdentity 類的一個(gè)實(shí)例,則 FileAuthorizationModule 類停止處理。 如果存在 WindowsIdentity 類的一個(gè)實(shí)例,則 FileAuthorizationModule 類調(diào)用 AccessCheck Win32 函數(shù)(通過 P/Invoke) 來確定是否授權(quán)經(jīng)過身份驗(yàn)證的客戶端訪問請(qǐng)求的文件。 如果該文件的安全描述符的隨機(jī)訪問控制列表 (DACL) 中至少包含一個(gè) Read 訪問控制項(xiàng) (ACE),則允許該請(qǐng)求繼續(xù)。 否則,F(xiàn)ileAuthorizationModule 類調(diào)用 HttpApplication.CompleteRequest 方法并將狀態(tài)碼 401 返回到客戶端。
在Windows身份認(rèn)證中,驗(yàn)證工作主要是由IIS實(shí)現(xiàn)的,WindowsAuthenticationModule其實(shí)只是負(fù)責(zé)創(chuàng)建WindowsPrincipal和WindowsIdentity而已。 順便介紹一下:Windows 身份驗(yàn)證又分為“NTLM 身份驗(yàn)證”和“Kerberos v5 身份驗(yàn)證”二種, 關(guān)于這二種Windows身份認(rèn)證的更多說明可查看MSDN技術(shù)文章:解釋:ASP.NET 2.0 中的 Windows 身份驗(yàn)證。 在我看來,IIS最終使用哪種Windows身份認(rèn)證方式并不影響我們的開發(fā)過程,因此本文不會(huì)討論這個(gè)話題。
根據(jù)我的實(shí)際經(jīng)驗(yàn)來看,使用Windows身份認(rèn)證時(shí),主要的開發(fā)工作將是根據(jù)登錄名從Active Directory獲取用戶信息。 因?yàn)椋藭r(shí)不需要我們?cè)僭O(shè)計(jì)登錄過程,IIS與ASP.NET已經(jīng)為我們準(zhǔn)備好了WindowsPrincipal和WindowsIdentity這二個(gè)與用戶身份相關(guān)的對(duì)象。
我們通常使用LDAP協(xié)議來訪問Active Directory, 在.net framework中提供了DirectoryEntry和DirectorySearcher這二個(gè)類型讓我們可以方便地從托管代碼中訪問 Active Directory 域服務(wù)。
如果我們要在"test.corp”這個(gè)域中搜索某個(gè)用戶信息,我們可以使用下面的語句構(gòu)造一個(gè)DirectoryEntry對(duì)象:
DirectoryEntry entry = new DirectoryEntry("LDAP://test.corp");
在這段代碼中,我采用硬編碼的方式把域名寫進(jìn)了代碼。
我們?nèi)绾沃喇?dāng)前電腦所使用的是哪個(gè)域名呢?
答案是:查看“我的電腦”的屬性對(duì)話框:
注意:這個(gè)域名不一定與System.Environment.UserDomainName相同。
除了可以查看“我的電腦”的屬性對(duì)話框外,我們還可以使用代碼的方式獲取當(dāng)前電腦所使用的域名:
private static string GetDomainName() { // 注意:這段代碼需要在Windows XP及較新版本的操作系統(tǒng)中才能正常運(yùn)行。 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; }
當(dāng)構(gòu)造了DirectorySearcher對(duì)象后,我們便可以使用DirectorySearcher來執(zhí)行對(duì)Active Directory的搜索。
我們可以使用下面的步驟來執(zhí)行搜索:
1. 設(shè)置 DirectorySearcher.Filter 指示LDAP格式篩選器,這是一個(gè)字符串。
2. 多次調(diào)用PropertiesToLoad.Add() 設(shè)置搜索過程中要檢索的屬性列表。
3. 調(diào)用FindOne() 方法獲取搜索結(jié)果。
下面的代碼演示了如何從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()); } }
結(jié)果如下:
在前面的代碼,我在搜索Active Directory時(shí),只搜索了"name,givenName,samaccountname,mail"這4個(gè)屬性。 然而,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 ";
前面我在一個(gè)控制臺(tái)程序中演示了訪問Active Directory的方法,通過示例我們可以看到:在代碼中,我用Environment.UserName就可以得到當(dāng)前用戶的登錄名。 然而,如果是在ASP.NET程序中,訪問Environment.UserName就很有可能得不到真正用戶登錄名。 因?yàn)椋篍nvironment.UserName是使用WIN32API中的GetUserName獲取線程相關(guān)的用戶名,但ASP.NET運(yùn)行在IIS中,線程相關(guān)的用戶名就不一定是客戶端的用戶名了。 不過,ASP.NET可以模擬用戶方式運(yùn)行,通過這種方式才可以得到正確的結(jié)果。關(guān)于“模擬”的話題在本文的后面部分有說明。
在ASP.NET中,為了能可靠的獲取登錄用戶的登錄名,我們可以使用下面的代碼:
/// <summary> /// 根據(jù)指定的HttpContext對(duì)象,獲取登錄名。 /// </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; // 此時(shí)userName的格式為:UserDomainName\LoginName // 我們只需要后面的LoginName就可以了。 string[] array = userName.Split(new char[] { '\\' }, StringSplitOptions.RemoveEmptyEntries); if( array.Length == 2 ) return array[1]; return null; }
在ASP.NET中使用Windows身份認(rèn)證時(shí),IIS和WindowsAuthenticationModule已經(jīng)做了許多驗(yàn)證用戶的相關(guān)工作, 雖然我們可以使用前面的代碼獲取到用戶的登錄名,但用戶的其它信息即需要我們自己來獲取。 在實(shí)際使用Windows身份認(rèn)證時(shí),我們要做的事:基本上就是從Active Directory中根據(jù)用戶的登錄名獲取所需的各種信息。
比如:我的程序在運(yùn)行時(shí),還需要使用以下與用戶相關(guān)的信息:
public sealed class UserInfo { public string GivenName; public string FullName; public string Email; }
public static class UserHelper { /// <summary> /// 活動(dòng)目錄中的搜索路徑,也可根據(jù)實(shí)際情況來修改這個(gè)值。 /// </summary> public static string DirectoryPath = "LDAP://" + GetDomainName(); /// <summary> /// 獲取與指定HttpContext相關(guān)的用戶信息 /// </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> /// 根據(jù)指定的HttpContext對(duì)象,獲取登錄名。 /// </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; // 此時(shí)userName的格式為:UserDomainName\LoginName // 我們只需要后面的LoginName就可以了。 string[] array = userName.Split(new char[] { '\\' }, StringSplitOptions.RemoveEmptyEntries); if( array.Length == 2 ) return array[1]; return null; } /// <summary> /// 根據(jù)登錄名查詢活動(dòng)目錄,獲取用戶信息。 /// </summary> /// <param name="loginName"></param> /// <returns></returns> public static UserInfo GetUserInfoByLoginName(string loginName) { if( string.IsNullOrEmpty(loginName) ) return null; // 下面的代碼將根據(jù)登錄名查詢用戶在AD中的信息。 // 為了提高性能,可以在此處增加一個(gè)緩存容器(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中獲取其它的用戶信息,請(qǐng)參考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 { // 如果需要記錄異常,請(qǐng)?jiān)诖颂幪砑哟a。 } return null; } private static string GetDomainName() { // 注意:這段代碼需要在Windows XP及較新版本的操作系統(tǒng)中才能正常運(yùn)行。 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 ) { %> 當(dāng)前登錄全名:<%= 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 { %> 當(dāng)前用戶還未登錄。 <% } %> </body> </html>
程序運(yùn)行的效果如下:
另外,還可以從Active Directory查詢一個(gè)叫做memberof的屬性(它與Windows用戶組無關(guān)),有時(shí)候可以用它區(qū)分用戶,設(shè)計(jì)與權(quán)限相關(guān)的操作。
在設(shè)計(jì)數(shù)據(jù)持久化的表結(jié)構(gòu)時(shí),由于此時(shí)沒有“用戶表”,那么我們可以直接保存用戶的登錄名。 剩下的開發(fā)工作就與Forms身份認(rèn)證沒有太多的差別了。
前面介紹了ASP.NET Windows身份認(rèn)證,在這種方式下,IIS和WindowsAuthenticationModule為我們實(shí)現(xiàn)了用戶身份認(rèn)證的過程。 然而,有時(shí)可能由于各種原因,需要我們以編程的方式使用Active Directory驗(yàn)證用戶身份,比如:在WinForm程序,或者其它的驗(yàn)證邏輯。
我們不僅可以從Active Directory中查詢用戶信息,也可以用它來實(shí)現(xiàn)驗(yàn)證用戶身份,這樣便可以實(shí)現(xiàn)自己的登錄驗(yàn)證邏輯。
不管是如何使用Active Directory,我們都需要使用DirectoryEntry和DirectorySearcher這二個(gè)對(duì)象。DirectoryEntry還提供一個(gè)構(gòu)造函數(shù)可讓我們輸入用戶名和密碼:
// 摘要: // 初始化 System.DirectoryServices.DirectoryEntry 類的新實(shí)例。 // // 參數(shù): // Password: // 在對(duì)客戶端進(jìn)行身份驗(yàn)證時(shí)使用的密碼。DirectoryEntry.Password 屬性初始化為該值。 // // username: // 在對(duì)客戶端進(jìn)行身份驗(yàn)證時(shí)使用的用戶名。DirectoryEntry.Username 屬性初始化為該值。 // // Path: // 此 DirectoryEntry 的路徑。DirectoryEntry.Path 屬性初始化為該值。 public DirectoryEntry(string path, string username, string password);
要實(shí)現(xiàn)自己的登錄檢查,就需要使用這個(gè)構(gòu)造函數(shù)。
以下是我寫用WinForm寫的一個(gè)登錄檢查的示例:
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 ) { // 如果用戶名或者密碼不正確,也會(huì)拋出異常。 MessageBox.Show(ex.Message, this.Text, MessageBoxButtons.OK, MessageBoxIcon.Stop); } }
程序運(yùn)行的效果如下:
在ASP.NET Windows身份認(rèn)證環(huán)境中,與用戶相關(guān)的安全上下文對(duì)象保存在HttpContext.User屬性中,是一個(gè)類型為WindowsPrincipal的對(duì)象, 我們還可以訪問HttpContext.User.Identity來獲取經(jīng)過身份認(rèn)證的用戶標(biāo)識(shí),它是一個(gè)WindowsIdentity類型的對(duì)象。
在.NET Framework中,我們可以通過WindowsIdentity.GetCurrent()獲取與當(dāng)前線程相關(guān)的WindowsIdentity對(duì)象, 這種方法獲取的是當(dāng)前運(yùn)行的Win32線程的安全上下文標(biāo)識(shí)。 由于ASP.NET運(yùn)行在IIS進(jìn)程中,因此ASP.NET線程的安全標(biāo)識(shí)其實(shí)是從IIS的進(jìn)程中繼承的, 所以此時(shí)用二種方法得到的WindowsIdentity對(duì)象其實(shí)是不同的。
在Windows操作系統(tǒng)中,許多權(quán)限檢查都是基于Win32線程的安全上下文標(biāo)識(shí), 于是前面所說的二種WindowsIdentity對(duì)象會(huì)造成編程模型的不一致問題, 為了解決這個(gè)問題,ASP.NET提供了“模擬”功能,允許線程以特定的Windows帳戶的安全上下文來訪問資源。
為了能更好的理解模擬的功能,我準(zhǔn)備了一個(gè)示例(ShowWindowsIdentity.ashx):
public class ShowWindowsIdentity : IHttpHandler { public void ProcessRequest (HttpContext context) { // 要觀察【模擬】的影響, // 可以啟用,禁止web.config中的設(shè)置:<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中設(shè)置:
<authentication mode="Windows" />
注意:要把網(wǎng)站部署在IIS中,否則看不出效果。
此時(shí),訪問ShowWindowsIdentity.ashx,將看到如下圖所示的結(jié)果:
現(xiàn)在修改一下web.config中設(shè)置:(注意:后面加了一句配置)
<authentication mode="Windows" /> <identity impersonate="true"/>
此時(shí),訪問ShowWindowsIdentity.ashx,將看到如下圖所示的結(jié)果:
說明:
1. FISH-SRV2003是我的計(jì)算機(jī)名。它在一個(gè)沒有域的環(huán)境中。
2. fish-li是我的一個(gè)Windows賬號(hào)的登錄名。
3. 網(wǎng)站部署在IIS6中,進(jìn)程以NETWORK SERVICE賬號(hào)運(yùn)行。
4. 打開網(wǎng)頁時(shí),我輸入的用戶名是fish-li
前面二張圖片的差異之處其實(shí)也就是ASP.NET的“模擬”所發(fā)揮的功能。
關(guān)于模擬,我想說四點(diǎn):
1. 在ASP.NET中,我們應(yīng)該訪問HttpContext.User.Identity獲取當(dāng)前用戶標(biāo)識(shí),那么就不存在問題(此時(shí)可以不需要模擬),例如FileAuthorizationModule就是這樣處理的。
2. 模擬只是在ASP.NET應(yīng)用程序訪問Windows系統(tǒng)資源時(shí)需要應(yīng)用Windows的安全檢查功能才會(huì)有用。
3. Forms身份認(rèn)證也能配置模擬功能,但只能模擬一個(gè)Windows帳戶。
4. 絕大多數(shù)情況下是不需要模擬的。
與使用Forms身份認(rèn)證的程序不同,使用Windows身份認(rèn)證的程序需要額外的配置步驟。 這個(gè)小節(jié)將主要介紹在IIS中配置Windows身份認(rèn)證,我將常用的IIS6和IIS7.5為例分別介紹這些配置。
IIS6的配置 請(qǐng)參考下圖:
IIS7.5的配置 請(qǐng)參考下圖:
注意:Windows身份認(rèn)證是需要安裝的,方法請(qǐng)參考下圖:
當(dāng)我們用瀏覽器訪問一個(gè)使用Windows身份認(rèn)證的網(wǎng)站時(shí),瀏覽器都會(huì)彈出一個(gè)對(duì)話框(左IE,右Safari):
此時(shí),要求我們輸入Windows的登錄賬號(hào),然后交給IIS驗(yàn)證身份。
首次彈出這個(gè)對(duì)話框很正常:因?yàn)槌绦蛞?yàn)證用戶的身份。
然而,每次關(guān)閉瀏覽器下次重新打開頁面時(shí),又會(huì)出現(xiàn)此對(duì)話框,此時(shí)感覺就很不方便了。
雖然有些瀏覽器能記住用戶名和密碼,但我發(fā)現(xiàn)FireFox,Opera,Chrome仍然會(huì)彈出這個(gè)對(duì)話框,等待我們點(diǎn)擊確定, 只有Safari才不會(huì)打擾用戶直接打開網(wǎng)頁。IE的那個(gè)“記住我的密碼”復(fù)選框完全是個(gè)擺設(shè),它根本不會(huì)記住密碼!
因此,我所試過的所有瀏覽器中,只有Safari是最人性化的。
雖然在默認(rèn)情況下,雖然IE不會(huì)記住密碼,每次都需要再次輸入。
不過,IE卻可以支持不提示用戶輸入登錄賬號(hào)而直接打開網(wǎng)頁,此時(shí)IE將使用用戶的當(dāng)前Windows登錄賬號(hào)傳遞給IIS驗(yàn)證身份。
要讓IE打開一個(gè)Windows身份認(rèn)證的網(wǎng)站不提示登錄對(duì)話框,必須滿足以下條件:
1. 必須在 IIS 的 Web 站點(diǎn)屬性中啟用 Windows 集成身份驗(yàn)證。
2. 客戶端和Web服務(wù)器都必須在基于Microsoft Windows的同一個(gè)域內(nèi)。
3. Internet Explorer 必須把所請(qǐng)求的 URL 視為 Intranet(本地)。
4. Internet Explorer 的 Intranet 區(qū)域的安全性設(shè)置必須設(shè)為“只在 Intranet 區(qū)域自動(dòng)登錄”。
5. 請(qǐng)求Web頁的用戶必須具有訪問該Web頁以及該Web頁中引用的所有對(duì)象的適當(dāng)?shù)奈募到y(tǒng)(NTFS)權(quán)限。
6. 用戶必須用域賬號(hào)登錄到Windows 。
在這幾個(gè)條件中,如果網(wǎng)站是在一個(gè)Windows域中運(yùn)行,除了第3條可能不滿足外,其它條件應(yīng)該都容易滿足(第4條是默認(rèn)值)。 因此,要讓IE不提示輸入登錄賬號(hào),只要確保第3條滿足就可以了。 下面的圖片演示了如何完成這個(gè)配置:(注意:配置方法也適合用域名訪問的情況)
另外,除了在IE中設(shè)置Intranet外,還可以在訪問網(wǎng)站時(shí),用計(jì)算機(jī)名代替IP地址或者域名, 那么IE始終認(rèn)為是在訪問Intranet內(nèi)的網(wǎng)站,此時(shí)也不會(huì)彈出登錄對(duì)話框。
在此,我想再啰嗦三句:
1. IE在集成Windows身份認(rèn)證時(shí),雖然不提示登錄對(duì)話框,但是不表示不安全,它會(huì)自動(dòng)傳遞登錄憑據(jù)。
2. 這種行為只有IE才能支持。(其它的瀏覽器只是會(huì)記住密碼,在實(shí)現(xiàn)上其實(shí)是不一樣的。)
3. 集成Windows身份認(rèn)證,也只適合在Intranet的環(huán)境中使用。
在上篇博客中,我演示了如何用代碼訪問一個(gè)使用Forms身份認(rèn)證的網(wǎng)站中的受限頁面,方法是使用CookieContainer對(duì)象接收服務(wù)端生的登錄Cookie。 然而,在Windows身份認(rèn)證的網(wǎng)站中,身份驗(yàn)證的過程發(fā)生在IIS中,而且根本不使用Cookie保存登錄狀態(tài),而是需要在請(qǐng)求時(shí)發(fā)送必要的身份驗(yàn)證信息。
在使用代碼做為客戶端訪問Web服務(wù)器時(shí),我們?nèi)匀恍枰褂肏ttpWebRequest對(duì)象。 為了能讓HttpWebRequest在訪問IIS時(shí)發(fā)送必要的身份驗(yàn)證信息,HttpWebRequest提供二個(gè)屬性都可以完成這個(gè)功能:
// 獲取或設(shè)置請(qǐng)求的身份驗(yàn)證信息。 // // 返回結(jié)果: // 包含與該請(qǐng)求關(guān)聯(lián)的身份驗(yàn)證憑據(jù)的 System.Net.ICredentials。默認(rèn)為 null。 public override ICredentials Credentials { get; set; } // 獲取或設(shè)置一個(gè) System.Boolean 值,該值控制默認(rèn)憑據(jù)是否隨請(qǐng)求一起發(fā)送。 // // 返回結(jié)果: // 如果使用默認(rèn)憑據(jù),則為 true;否則為 false。默認(rèn)值為 false。 public override bool UseDefaultCredentials { get; set; }
下面是我準(zhǔn)備的完整的示例代碼(注意代碼中的注釋):
static void Main(string[] args) { try { // 請(qǐng)把WindowsAuthWebSite1這個(gè)網(wǎng)站部署在IIS中, // 開啟Windows認(rèn)證方式,并禁止匿名用戶訪問。 // 然后修改下面的訪問地址。 HttpWebRequest request = (HttpWebRequest)WebRequest.Create("http://localhost:33445/Default.aspx"); // 下面三行代碼,啟用任意一行都是可以的。 request.UseDefaultCredentials = true; //request.Credentials = CredentialCache.DefaultCredentials; //request.Credentials = CredentialCache.DefaultNetworkCredentials; // 如果上面的三行代碼全被注釋了,那么將會(huì)看到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("異常發(fā)生了。"); Console.WriteLine("====================================="); Console.WriteLine(wex.Message); } }
其實(shí)關(guān)鍵部分還是設(shè)置UseDefaultCredentials或者Credentials,代碼中的三種方法是有效的。
這三種方法的差別:
1. Credentials = CredentialCache.DefaultCredentials; 表示在發(fā)送請(qǐng)求會(huì)帶上當(dāng)前用戶的身份驗(yàn)證憑據(jù)。
2. UseDefaultCredentials = true; 此方法在內(nèi)部會(huì)調(diào)用前面的方法,因此與前面的方法是一樣的。
3. Credentials = CredentialCache.DefaultNetworkCredentials; 是在.NET 2.0中引用的新方法。
關(guān)于DefaultCredentials和DefaultNetworkCredentials的更多差別,請(qǐng)看我整理的表格:
Credentials屬性 | 申明類型 | 實(shí)例類型 | .NET支持版本 |
DefaultCredentials | ICredentials | SystemNetworkCredential | 從1.0開始 |
DefaultNetworkCredentials | NetworkCredential | SystemNetworkCredential | 從2.0開始 |
三個(gè)類型的繼承關(guān)系:
1. NetworkCredential實(shí)現(xiàn)了ICredentials接口,
2. SystemNetworkCredential繼承自NetworkCredential。
在結(jié)束這篇博客之前,我想我應(yīng)該感謝新蛋。
在新蛋的網(wǎng)絡(luò)環(huán)境中,讓我學(xué)會(huì)了使用Windows身份認(rèn)證。
除了感謝之外,我現(xiàn)在還特別懷念 fl45 這個(gè)登錄名......
聯(lián)系客服