細(xì)說(shuō)ASP.NET Windows身份認(rèn)證
要使用Windows身份認(rèn)證模式,需要在web.config設(shè)置:
- <authentication mode="Windows" />
Windows身份認(rèn)證做為ASP.NET的默認(rèn)認(rèn)證方式,與Forms身份認(rèn)證在許多基礎(chǔ)方面是一樣的。 上篇博客我說(shuō)過(guò):我認(rèn)為ASP.NET的身份認(rèn)證的最核心部分其實(shí)就是HttpContext.User這個(gè)屬性所指向的對(duì)象。 在接下來(lái)的部分,我將著重分析這個(gè)對(duì)象在二種身份認(rèn)證中有什么差別。
在ASP.NET身份認(rèn)證過(guò)程中,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訪問(wèn)令牌(Token)創(chuàng)建一個(gè)WindowsIdentity對(duì)象,Token通過(guò)調(diào)用context.WorkerRequest.GetUserToken()獲得,然后再根據(jù)WindowsIdentity 對(duì)象創(chuàng)建WindowsPrincipal對(duì)象,然后把它賦值給HttpContext.User。
在Forms身份認(rèn)證中,我們需要?jiǎng)?chuàng)建登錄頁(yè)面,讓用戶提交用戶名和密碼,然后檢查用戶名和密碼的正確性,接下來(lái)創(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è)面,不用編寫登錄驗(yàn)證邏輯,因此更容易使用。
在授權(quán)階段,UrlAuthorizationModule仍然會(huì)根據(jù)當(dāng)前用戶檢查將要訪問(wèn)的資源是否得到許可。接下來(lái),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ù)(通過(guò) P/Invoke)來(lái)確定是否授權(quán)經(jīng)過(guò)身份驗(yàn)證的客戶端訪問(wèn)請(qǐng)求的文件。如果該文件的安全描述符的隨機(jī)訪問(wèn)控制列表 (DACL) 中至少包含一個(gè) Read 訪問(wèn)控制項(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)證的更多說(shuō)明可查看MSDN技術(shù)文章:解釋:ASP.NET 2.0 中的 Windows 身份驗(yàn)證。在我看來(lái),IIS最終使用哪種Windows身份認(rèn)證方式并不影響我們的開(kāi)發(fā)過(guò)程,因此本文不會(huì)討論這個(gè)話題。
根據(jù)我的實(shí)際經(jīng)驗(yàn)來(lái)看,使用Windows身份認(rèn)證時(shí),主要的開(kāi)發(fā)工作將是根據(jù)登錄名從Active Directory獲取用戶信息。因?yàn)椋藭r(shí)不需要我們?cè)僭O(shè)計(jì)登錄過(guò)程,IIS與ASP.NET已經(jīng)為我們準(zhǔn)備好了WindowsPrincipal和WindowsIdentity這二個(gè)與用戶身份相關(guān)的對(duì)象。
訪問(wèn) Active Directory
我們通常使用LDAP協(xié)議來(lái)訪問(wèn)Active Directory,在.net framework中提供了DirectoryEntry和DirectorySearcher這二個(gè)類型讓我們可以方便地從托管代碼中訪問(wèn) Active Directory 域服務(wù)。
如果我們要在"test.corp”這個(gè)域中搜索某個(gè)用戶信息,我們可以使用下面的語(yǔ)句構(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來(lái)執(zhí)行對(duì)Active Directory的搜索。
我們可以使用下面的步驟來(lái)執(zhí)行搜索:
1. 設(shè)置 DirectorySearcher.Filter 指示LDAP格式篩選器,這是一個(gè)字符串。
2. 多次調(diào)用PropertiesToLoad.Add() 設(shè)置搜索過(guò)程中要檢索的屬性列表。
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
- 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中訪問(wèn)Active Directory
前面我在一個(gè)控制臺(tái)程序中演示了訪問(wèn)Active Directory的方法,通過(guò)示例我們可以看到:在代碼中,我用Environment.UserName就可以得到當(dāng)前用戶的登錄名。然而,如果是在ASP.NET程序中,訪問(wèn)Environment.UserName就很有可能得不到真正用戶登錄名。因?yàn)椋篍nvironment.UserName是使用WIN32API中的GetUserName獲取線程相關(guān)的用戶名,但ASP.NET運(yùn)行在IIS中,線程相關(guān)的用戶名就不一定是客戶端的用戶名了。不過(guò),ASP.NET可以模擬用戶方式運(yùn)行,通過(guò)這種方式才可以得到正確的結(jié)果。關(guān)于“模擬”的話題在本文的后面部分有說(shuō)明。
在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)工作,雖然我們可以使用前面的代碼獲取到用戶的登錄名,但用戶的其它信息即需要我們自己來(lái)獲取。在實(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;
- }
那么,我們可以使用這樣的代碼來(lái)獲取所需的用戶信息:
- public static class UserHelper
- {
- /// <summary>
- /// 活動(dòng)目錄中的搜索路徑,也可根據(jù)實(shí)際情況來(lái)修改這個(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的頁(yè)面代碼:
- <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用戶組無(wú)關(guān)),有時(shí)候可以用它區(qū)分用戶,設(shè)計(jì)與權(quán)限相關(guān)的操作。
在設(shè)計(jì)數(shù)據(jù)持久化的表結(jié)構(gòu)時(shí),由于此時(shí)沒(méi)有“用戶表”,那么我們可以直接保存用戶的登錄名。剩下的開(kāi)發(fā)工作就與Forms身份認(rèn)證沒(méi)有太多的差別了。
使用Active Directory驗(yàn)證用戶身份
前面介紹了ASP.NET Windows身份認(rèn)證,在這種方式下,IIS和WindowsAuthenticationModule為我們實(shí)現(xiàn)了用戶身份認(rèn)證的過(guò)程。然而,有時(shí)可能由于各種原因,需要我們以編程的方式使用Active Directory驗(yàn)證用戶身份,比如:在WinForm程序,或者其它的驗(yàn)證邏輯。
我們不僅可以從Active Directory中查詢用戶信息,也可以用它來(lái)實(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ì)象,我們還可以訪問(wèn)HttpContext.User.Identity來(lái)獲取經(jīng)過(guò)身份認(rèn)證的用戶標(biāo)識(shí),它是一個(gè)WindowsIdentity類型的對(duì)象。
在.NET Framework中,我們可以通過(guò)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í),于是前面所說(shuō)的二種WindowsIdentity對(duì)象會(huì)造成編程模型的不一致問(wèn)題,為了解決這個(gè)問(wèn)題,ASP.NET提供了“模擬”功能,允許線程以特定的Windows帳戶的安全上下文來(lái)訪問(wèn)資源。
為了能更好的理解模擬的功能,我準(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í),訪問(wèn)ShowWindowsIdentity.ashx,將看到如下圖所示的結(jié)果:
現(xiàn)在修改一下web.config中設(shè)置:(注意:后面加了一句配置)
- <authentication mode="Windows" />
- <identity impersonate="true"/>
此時(shí),訪問(wèn)ShowWindowsIdentity.ashx,將看到如下圖所示的結(jié)果:
說(shuō)明:
1. FISH-SRV2003是我的計(jì)算機(jī)名。它在一個(gè)沒(méi)有域的環(huán)境中。
2. fish-li是我的一個(gè)Windows帳號(hào)的登錄名。
3. 網(wǎng)站部署在IIS6中,進(jìn)程以NETWORK SERVICE帳號(hào)運(yùn)行。
4. 打開(kāi)網(wǎng)頁(yè)時(shí),我輸入的用戶名是fish-li
前面二張圖片的差異之處其實(shí)也就是ASP.NET的“模擬”所發(fā)揮的功能。
關(guān)于模擬,我想說(shuō)四點(diǎn):
1. 在ASP.NET中,我們應(yīng)該訪問(wèn)HttpContext.User.Identity獲取當(dāng)前用戶標(biāo)識(shí),那么就不存在問(wèn)題(此時(shí)可以不需要模擬),例如FileAuthorizationModule就是這樣處理的。
2. 模擬只是在ASP.NET應(yīng)用程序訪問(wèn)Windows系統(tǒng)資源時(shí)需要應(yīng)用Windows的安全檢查功能才會(huì)有用。
3. Forms身份認(rèn)證也能配置模擬功能,但只能模擬一個(gè)Windows帳戶。
4. 絕大多數(shù)情況下是不需要模擬的。
在IIS中配置Windows身份認(rèn)證
與使用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)參考下圖:
關(guān)于瀏覽器的登錄對(duì)話框問(wèn)題
當(dāng)我們用瀏覽器訪問(wèn)一個(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)閉瀏覽器下次重新打開(kāi)頁(yè)面時(shí),又會(huì)出現(xiàn)此對(duì)話框,此時(shí)感覺(jué)就很不方便了。
雖然有些瀏覽器能記住用戶名和密碼,但我發(fā)現(xiàn)FireFox,Opera,Chrome仍然會(huì)彈出這個(gè)對(duì)話框,等待我們點(diǎn)擊確定,只有Safari才不會(huì)打擾用戶直接打開(kāi)網(wǎng)頁(yè)。 IE的那個(gè)“記住我的密碼”復(fù)選框完全是個(gè)擺設(shè),它根本不會(huì)記住密碼!
因此,我所試過(guò)的所有瀏覽器中,只有Safari是最人性化的。
雖然在默認(rèn)情況下,雖然IE不會(huì)記住密碼,每次都需要再次輸入。
不過(guò),IE卻可以支持不提示用戶輸入登錄帳號(hào)而直接打開(kāi)網(wǎng)頁(yè), 此時(shí)IE將使用用戶的當(dāng)前Windows登錄帳號(hào)傳遞給IIS驗(yàn)證身份。
要讓IE打開(kāi)一個(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頁(yè)的用戶必須具有訪問(wèn)該Web頁(yè)以及該Web頁(yè)中引用的所有對(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è)配置:(注意:配置方法也適合用域名訪問(wèn)的情況)
另外,除了在IE中設(shè)置Intranet外,還可以在訪問(wèn)網(wǎng)站時(shí),用計(jì)算機(jī)名代替IP地址或者域名,那么IE始終認(rèn)為是在訪問(wè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)境中使用。
在客戶端代碼中訪問(wèn)Windows身份認(rèn)證的頁(yè)面
在上篇博客中,我演示了如何用代碼訪問(wèn)一個(gè)使用Forms身份認(rèn)證的網(wǎng)站中的受限頁(yè)面,方法是使用CookieContainer對(duì)象接收服務(wù)端生的登錄Cookie。然而,在Windows身份認(rèn)證的網(wǎng)站中,身份驗(yàn)證的過(guò)程發(fā)生在IIS中,而且根本不使用Cookie保存登錄狀態(tài),而是需要在請(qǐng)求時(shí)發(fā)送必要的身份驗(yàn)證信息。
在使用代碼做為客戶端訪問(wèn)Web服務(wù)器時(shí),我們?nèi)匀恍枰褂肏ttpWebRequest對(duì)象。為了能讓HttpWebRequest在訪問(wèn)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中,
- // 開(kāi)啟Windows認(rèn)證方式,并禁止匿名用戶訪問(wèn)。
- // 然后修改下面的訪問(wè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開(kāi)始 |
DefaultNetworkCredentials | NetworkCredential | SystemNetworkCredential | 從2.0開(kāi)始 |
三個(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è)登錄名......
原文鏈接:http://www.cnblogs.com/fish-li/archive/2012/05/07/2486840.html