細說ASP.NET Forms身份認證 別人寫的不過很透徹就轉來了之後用時再看

用戶登陸是個很常見的業務需求,在ASP.NET中,這個過程被稱爲身份認證。 因爲很常見,所以,我認爲把這塊內容整理出來,與你們分享應該是件有意義的事。html

在開發ASP.NET項目中,咱們最經常使用的是Forms認證,也叫【表單認證】。 這種認證方式既能夠用於局域網環境,也可用於互聯網環境,所以,它有着很是普遍的使用。 這篇博客主要討論的話題是:ASP.NET Forms 身份認證。web

有一點我要申明一下:在這篇博客中,不會涉及ASP.NET的登陸系列控件以及membership的相關話題, 我只想用比較原始的方式來講明在ASP.NET中是如何實現身份認證的過程。算法

ASP.NET身份認證基礎

在開始今天的博客以前,我想有二個最基礎的問題首先要明確:
1. 如何判斷當前請求是一個已登陸用戶發起的?
2. 如何獲取當前登陸用戶的登陸名? 瀏覽器

在標準的ASP.NET身份認證方式中,上面二個問題的答案是:
1. 若是Request.IsAuthenticated爲true,則表示是一個已登陸用戶。
2. 若是是一個已登陸用戶,訪問HttpContext.User.Identity.Name可獲取登陸名(都是實例屬性)。 安全

接下來,本文將會圍繞上面二個問題展開,請繼續閱讀。 服務器

ASP.NET身份認證過程

在ASP.NET中,整個身份認證的過程其實可分爲二個階段:認證與受權。
1. 認證階段:識別當前請求的用戶是否是一個可識別(的已登陸)用戶。
2. 受權階段:是否容許當前請求訪問指定的資源。 cookie

這二個階段在ASP.NET管線中用AuthenticateRequest和AuthorizeRequest事件來表示。
在認證階段,ASP.NET會檢查當前請求,根據web.config設置的認證方式,嘗試構造HttpContext.User對象供咱們在後續的處理中使用。 在受權階段,會檢查當前請求所訪問的資源是否容許訪問,由於有些受保護的頁面資源可能要求特定的用戶或者用戶組才能訪問。 因此,即便是一個已登陸用戶,也有可能會不能訪問某些頁面。 當發現用戶不能訪問某個頁面資源時,ASP.NET會將請求重定向到登陸頁面。 數據結構

受保護的頁面與登陸頁面咱們均可以在web.config中指定,具體方法可參考後文。 app

在ASP.NET中,Forms認證是由FormsAuthenticationModule實現的,URL的受權檢查是由UrlAuthorizationModule實現的。

如何實現登陸與註銷

前面我介紹了可使用Request.IsAuthenticated來判斷當前用戶是否是一個已登陸用戶,那麼這一過程又是如何實現的呢?

爲了回答這個問題,我準備了一個簡單的示例頁面,代碼以下:

<fieldset><legend>用戶狀態</legend><form action="<%= Request.RawUrl %>" method="post"> <% if( Request.IsAuthenticated ) { %> 當前用戶已登陸,登陸名:<%= Context.User.Identity.Name.HtmlEncode() %> <br /> <input type="submit" name="Logon" value="退出" /> <% } else { %> <b>當前用戶還未登陸。</b> <% } %> </form></fieldset> 

頁面顯示效果以下:

根據前面的代碼,我想如今能看到這個頁面顯示也是正確的,是的,我目前尚未登陸(根本尚未實現這個功能)

下面我再加點代碼來實現用戶登陸。頁面代碼:

<fieldset><legend>普通登陸</legend><form action="<%= Request.RawUrl %>" method="post"> 登陸名:<input type="text" name="loginName" style="width: 200px" value="Fish" /> <input type="submit" name="NormalLogin" value="登陸" /> </form></fieldset> 

如今頁面的顯示效果:

登陸與退出登陸的實現代碼:

public void Logon()
{
    FormsAuthentication.SignOut(); } public void NormalLogin() { // ----------------------------------------------------------------- // 注意:演示代碼爲了簡單,這裏不檢查用戶名與密碼是否正確。 // ----------------------------------------------------------------- string loginName = Request.Form["loginName"]; if( string.IsNullOrEmpty(loginName) ) return; FormsAuthentication.SetAuthCookie(loginName, true); TryRedirect(); } 

如今,我可試一下登陸功能。點擊登陸按鈕後,頁面的顯示效果以下:

從圖片的顯示能夠看出,我前面寫的NormalLogin()方法確實能夠實現用戶登陸。
固然了,我也能夠在此時點擊退出按鈕,那麼就回到了圖片2的顯示。

寫到這裏,我想有必要再來總結一下在ASP.NET中實現登陸與註銷的方法:
1. 登陸:調用FormsAuthentication.SetAuthCookie()方法,傳遞一個登陸名便可。
2. 註銷:調用FormsAuthentication.SignOut()方法。

保護受限制的頁面

在一個ASP.NET網站中,有些頁面會容許全部用戶訪問,包括一些未登陸用戶,但有些頁面則必須是已登陸用戶才能訪問, 還有一些頁面可能會要求特定的用戶或者用戶組的成員才能訪問。 這類頁面所以也可稱爲【受限頁面】,它們通常表明着比較重要的頁面,包含一些重要的操做或功能。

爲了保護受限制的頁面的訪問,ASP.NET提供了一種簡單的方式: 能夠在web.config中指定受限資源容許哪些用戶或者用戶組(角色)的訪問,也能夠設置爲禁止訪問。

好比,網站有一個頁面:MyInfo.aspx,它要求訪問這個頁面的訪問者必須是一個已登陸用戶,那麼能夠在web.config中這樣配置:

<location path="MyInfo.aspx"> <system.web> <authorization> <deny users="?"/> </authorization> </system.web> </location> 

爲了方便,我可能會將一些管理相關的多個頁面放在Admin目錄中,顯然這些頁面只容許Admin用戶組的成員才能夠訪問。 對於這種狀況,咱們能夠直接針對一個目錄設置訪問規則:

<location path="Admin"> <system.web> <authorization> <allow roles="Admin"/> <deny users="*"/> </authorization> </system.web> </location> 

這樣就沒必要一個一個頁面單獨設置了,還能夠在目錄中建立一個web.config來指定目錄的訪問規則,請參考後面的示例。

在前面的示例中,有一點要特別注意的是:
1. allow和deny之間的順序必定不能寫錯了,UrlAuthorizationModule將按這個順序依次判斷。
2. 若是某個資源只容許某類用戶訪問,那麼最後的一條規則必定是 <deny users="*" />

在allow和deny的配置中,咱們能夠在一條規則中指定多個用戶:
1. 使用users屬性,值爲逗號分隔的用戶名列表。
2. 使用roles屬性,值爲逗號分隔的角色列表。
3. 問號 (?) 表示匿名用戶。
4. 星號 (*) 表示全部用戶。

登陸頁不能正常顯示的問題

有時候,咱們可能要開發一個內部使用的網站程序,這類網站程序要求 禁止匿名用戶的訪問,即:全部使用者必須先登陸才能訪問。 所以,咱們一般會在網站根目錄下的web.config中這樣設置:

<authorization> <deny users="?"/> </authorization> 

對於咱們的示例,咱們也能夠這樣設置。此時在瀏覽器打開頁面時,呈現效果以下:

從圖片中能夠看出:頁面的樣式顯示不正確,最下邊還多出了一行文字。

這個頁面的完整代碼是這樣的(它引用了一個CSS文件和一個JS文件):

頁面最後一行文字平時不顯示是由於JScript.js中有如下代碼:

document.getElementById("hideText").setAttribute("style", "display: none"); 

這段JS代碼能作什麼,我想就不用再解釋了。
雖然這段JS代碼沒什麼價值,但我主要是想演示在登陸頁面中引用JS的場景。

根據前面圖片,咱們能夠猜想到:應該是CSS和JS文件沒有正確加載形成的。
爲了確認就是這樣緣由,咱們能夠打開FireBug再來看一下頁面加載狀況:

根據FireBug提供的線索咱們能夠分析出,頁面在訪問CSS, JS文件時,實際上是被重定向到登陸頁面了,所以得到的結果確定也是無心義的, 因此就形成了登陸頁的顯示不正確。

還記得前所說的【受權】嗎?
是的,如今就是因爲咱們在web.config中設置了不容許匿名用戶訪問,所以,全部的資源也就不容許匿名用戶訪問了, 包括登陸頁所引用的CSS, JS文件。當受權檢查失敗時,請求會被重定向到登陸頁面, 因此,登陸頁自己所引用的CSS, JS文件最後獲得的響應內容實際上是登陸頁的HTML代碼, 最終致使它們不能發揮做用,表現爲登陸頁的樣式顯示不正確,以及引用的JS文件也不起做用。

不過,有一點比較奇怪:爲何訪問登陸頁面時,沒有發生重定向呢?
緣由是這樣的:在ASP.NET內部,當發現是在訪問登陸面時,會設置HttpContext.SkipAuthorization = true (實際上是一個內部調用), 這樣的設置會告訴後面的受權檢查模塊:跳過此次請求的受權檢查。 所以,登陸頁老是容許全部用戶訪問,可是CSS文件以及JS文件是在另外的請求中發生的,那些請求並不會要跳過受權模塊的檢查。

爲了解決登陸頁不能正確顯示的問題,咱們能夠這樣處理:
1. 在網站根目錄中的web.config中設置登陸頁所引用的JS, CSS文件都容許匿名訪問。
2. 也能夠直接針對JS, CSS目錄設置爲容許匿名用戶訪問。
3. 還能夠在CSS, JS目錄中建立一個web.config文件來配置對應目錄的受權規則。可參考如下web.config文件:

<?xml version="1.0"?> <configuration> <system.web> <authorization> <allow users="*"/> </authorization> </system.web> </configuration> 

第三種作法能夠不修改網站根目錄下的web.config文件。

注意:在IIS中看到的狀況就和在Visual Studio中看到的結果就不同了。 由於,像js, css, image這類文件屬於靜態資源文件,IIS能直接處理,不須要交給ASP.NET來響應,所以就不會發生受權檢查失敗, 因此,若是這類網站部署在IIS中,看到的結果又是正常的。

認識Forms身份認證

前面我演示瞭如何用代碼實現登陸與註銷的過程,下面再來看一下登陸時,ASP.NET到底作了些什麼事情, 它是如何知道當前請求是一個已登陸用戶的?

在繼續探索這個問題前,我想有必要來了解一下HTTP協議的一些特色。
HTTP是一個無狀態的協議,無狀態的意思能夠理解爲: WEB服務器在處理全部傳入請求時,根本就不知道某個請求是不是一個用戶的第一次請求與後續請求,或者是另外一個用戶的請求。 WEB服務器每次在處理請求時,都會按照用戶所訪問的資源所對應的處理代碼,從頭至尾執行一遍,而後輸出響應內容, WEB服務器根本不會記住已處理了哪些用戶的請求,所以,咱們一般說HTTP協議是無狀態的。

雖然HTTP協議與WEB服務器是無狀態,但咱們的業務需求卻要求有狀態,典型的就是用戶登陸, 在這種業務需求中,要求WEB服務器端能區分某個請求是否是一個已登陸用戶發起的,或者當前請求是哪一個用戶發出的。 在開發WEB應用程序時,咱們一般會使用Cookie來保存一些簡單的數據供服務端維持必要的狀態。 既然這是個一般的作法,那咱們如今就來看一下如今頁面的Cookie使用狀況吧,如下是我用FireFox所看到的Cookie列表:

這個名字:LoginCookieName,是我在web.config中指定的:

<authentication mode="Forms" > <forms cookieless="UseCookies" name="LoginCookieName" loginUrl="~/Default.aspx"></forms> </authentication> 

在這段配置中,我不只指定的登陸狀態的Cookie名,還指定了身份驗證模式,以及Cookie的使用方式。

爲了判斷這個Cookie是否與登陸狀態有關,咱們能夠在瀏覽器提供的界面刪除它,而後刷新頁面,此時頁面的顯示效果以下:

此時,頁面顯示當前用戶沒有登陸。

爲了確認這個Cookie與登陸狀態有關,咱們能夠從新登陸,而後再退出登陸。
發現只要是頁面顯示當前用戶未登陸時,這個Cookie就不會存在。

事實上,經過SetAuthCookie這個方法名,咱們也能夠猜得出這個操做會寫一個Cookie。
注意:本文不討論無Cookie模式的Forms登陸。

從前面的截圖咱們能夠看出:雖然當前用戶名是 Fish ,可是,Cookie的值是一串亂碼樣的字符串。
因爲安全性的考慮,ASP.NET對Cookie作過加密處理了,這樣能夠防止惡意用戶構造Cookie繞過登陸機制來模擬登陸用戶。 若是想知道這串加密字符串是如何獲得的,那麼請參考後文。

小結:
1. Forms身份認證是在web.config中指定的,咱們還能夠設置Forms身份認證的其它配置參數。
2. Forms身份認證的登陸狀態是經過Cookie來維持的。
3. Forms身份認證的登陸Cookie是加密的。

理解Forms身份認證

通過前面的Cookie分析,咱們能夠發現Cookie的值是一串加密後的字符串, 如今咱們就來分析這個加密過程以及Cookie對於身份認證的做用。

登陸的操做一般會檢查用戶提供的用戶名和密碼,所以登陸狀態也必須具備足夠高的安全性。 在Forms身份認證中,因爲登陸狀態是保存在Cookie中,而Cookie又會保存到客戶端,所以,爲了保證登陸狀態不被惡意用戶僞造, ASP.NET採用了加密的方式保存登陸狀態。 爲了實現安全性,ASP.NET採用【Forms身份驗證憑據】(即FormsAuthenticationTicket對象)來表示一個Forms登陸用戶, 加密與解密由FormsAuthentication的Encrypt與Decrypt的方法來實現。

用戶登陸的過程大體是這樣的:
1. 檢查用戶提交的登陸名和密碼是否正確。
2. 根據登陸名建立一個FormsAuthenticationTicket對象。
3. 調用FormsAuthentication.Encrypt()加密。
4. 根據加密結果建立登陸Cookie,並寫入Response。
在登陸驗證結束後,通常會產生重定向操做, 那麼後面的每次請求將帶上前面產生的加密Cookie,供服務器來驗證每次請求的登陸狀態。

每次請求時的(認證)處理過程以下:
1. FormsAuthenticationModule嘗試讀取登陸Cookie。
2. 從Cookie中解析出FormsAuthenticationTicket對象。過時的對象將被忽略。
3. 根據FormsAuthenticationTicket對象構造FormsIdentity對象並設置HttpContext.User
4. UrlAuthorizationModule執行受權檢查。

在登陸與認證的實現中,FormsAuthenticationTicket和FormsAuthentication是二個核心的類型, 前者能夠認爲是一個數據結構,後者可認爲是處理前者的工具類。

UrlAuthorizationModule是一個受權檢查模塊,其實它與登陸認證的關係較爲獨立, 所以,若是咱們不使用這種基於用戶名與用戶組的受權檢查,也能夠禁用這個模塊。

因爲Cookie自己有過時的特色,然而爲了安全,FormsAuthenticationTicket也支持過時策略, 不過,ASP.NET的默認設置支持FormsAuthenticationTicket的可調過時行爲,即:slidingExpiration=true 。 這兩者任何一個過時時,都將致使登陸狀態無效。

FormsAuthenticationTicket的可調過時的主要判斷邏輯由FormsAuthentication.RenewTicketIfOld方法實現,代碼以下:

Request.IsAuthenticated能夠告訴咱們當前請求是否已通過身份驗證, 咱們來看一下這個屬性是如何實現的:

public bool IsAuthenticated
{
    get { return (((this._context.User != null) && (this._context.User.Identity != null)) && this._context.User.Identity.IsAuthenticated); } } 

從代碼能夠看出,它的返回結果基本上來源於對Context.User的判斷。
另外,因爲User和Identity都是二個接口類型的屬性,所以,不一樣的實現方式對返回值也有影響。

因爲可能會常用HttpContext.User這個實例屬性,爲了讓它能正常使用, DefaultAuthenticationModule會在ASP.NET管線的PostAuthenticateRequest事件中檢查此屬性是否爲null, 若是它爲null,DefaultAuthenticationModule會給它一個默認的GenericPrincipal對象,此對象指示一個未登陸的用戶。

我認爲ASP.NET的身份認證的最核心部分其實就是HttpContext.User這個屬性所指向的對象。
爲了更好了理解Forms身份認證,我認爲本身從新實現User這個對象的接口會有較好的幫助。

實現自定義的身份認證標識

前面演示了最簡單的ASP.NET Forms身份認證的實現方法,即:直接調用SetAuthCookie方法。 不過調用這個方法,只能傳遞一個登陸名。 可是有時候爲了方便後續的請求處理,還須要保存一些與登陸名相關的額外信息。 雖然知道ASP.NET使用Cookie來保存登陸名狀態信息,咱們也能夠直接將前面所說的額外信息直接保存在Cookie中, 可是考慮安全性,咱們還須要設計一些加密方法,並且還須要考慮這些額外信息保存在哪裏才能方便使用, 並還要考慮隨登陸與註銷同步修改。 所以,實現這些操做仍是有點繁瑣的。

爲了保存與登陸名相關的額外的用戶信息,我認爲實現自定義的身份認證標識(HttpContext.User實例)是個容易的解決方法。
理解這個方法也會讓咱們對Forms身份認證有着更清楚地認識。

這個方法的核心是(分爲二個子過程):
1. 在登陸時,建立自定義的FormsAuthenticationTicket對象,它包含了用戶信息。
2. 加密FormsAuthenticationTicket對象。
3. 建立登陸Cookie,它將包含FormsAuthenticationTicket對象加密後的結果。
4. 在管線的早期階段,讀取登陸Cookie,若是有,則解密。
5. 從解密後的FormsAuthenticationTicket對象中還原咱們保存的用戶信息。
6. 設置HttpContext.User爲咱們自定義的對象。

如今,咱們仍是來看一下HttpContext.User這個屬性的定義:

//  爲當前 HTTP 請求獲取或設置安全信息。
//
// 返回結果:
//     當前 HTTP 請求的安全信息。
public IPrincipal User { get; set; } 

因爲這個屬性只是個接口類型,所以,咱們也能夠本身實現這個接口。
考慮到更好的通用性:不一樣的項目可能要求接受不一樣的用戶信息類型。因此,我定義了一個泛型類。

public class MyFormsPrincipal<TUserData> : IPrincipal where TUserData : class, new() { private IIdentity _identity; private TUserData _userData; public MyFormsPrincipal(FormsAuthenticationTicket ticket, TUserData userData) { if( ticket == null ) throw new ArgumentNullException("ticket"); if( userData == null ) throw new ArgumentNullException("userData"); _identity = new FormsIdentity(ticket); _userData = userData; } public TUserData UserData { get { return _userData; } } public IIdentity Identity { get { return _identity; } } public bool IsInRole(string role) { // 把判斷用戶組的操做留給UserData去實現。 IPrincipal principal = _userData as IPrincipal; if( principal == null ) throw new NotImplementedException(); else return principal.IsInRole(role); } 

與之配套使用的用戶信息的類型定義以下(能夠根據實際狀況來定義):

注意:表示用戶信息的類型並不要求必定要實現IPrincipal接口,若是不須要用戶組的判斷,能夠不實現這個接口。

登陸時須要調用的方法(定義在MyFormsPrincipal類型中):

/// <summary>
/// 執行用戶登陸操做 /// </summary> /// <param name="loginName">登陸名</param> /// <param name="userData">與登陸名相關的用戶信息</param> /// <param name="expiration">登陸Cookie的過時時間,單位:分鐘。</param> public static void SignIn(string loginName, TUserData userData, int expiration) { if( string.IsNullOrEmpty(loginName) ) throw new ArgumentNullException("loginName"); if( userData == null ) throw new ArgumentNullException("userData"); // 1. 把須要保存的用戶數據轉成一個字符串。 string data = null; if( userData != null ) data = (new JavaScriptSerializer()).Serialize(userData); // 2. 建立一個FormsAuthenticationTicket,它包含登陸名以及額外的用戶數據。 FormsAuthenticationTicket ticket = new FormsAuthenticationTicket( 2, loginName, DateTime.Now, DateTime.Now.AddDays(1), true, data); // 3. 加密Ticket,變成一個加密的字符串。 string cookieValue = FormsAuthentication.Encrypt(ticket); // 4. 根據加密結果建立登陸Cookie HttpCookie cookie = new HttpCookie(FormsAuthentication.FormsCookieName, cookieValue); cookie.HttpOnly = true; cookie.Secure = FormsAuthentication.RequireSSL; cookie.Domain = FormsAuthentication.CookieDomain; cookie.Path = FormsAuthentication.FormsCookiePath; if( expiration > 0 ) cookie.Expires = DateTime.Now.AddMinutes(expiration); HttpContext context = HttpContext.Current; if( context == null ) throw new InvalidOperationException(); // 5. 寫登陸Cookie context.Response.Cookies.Remove(cookie.Name); context.Response.Cookies.Add(cookie); } 

這裏有必要再補充一下: 登陸狀態是有過時限制的。Cookie有 有效期,FormsAuthenticationTicket對象也有 有效期。 這兩者任何一個過時時,都將致使登陸狀態無效。 按照默認設置,FormsAuthenticationModule將採用slidingExpiration=true的策略來處理FormsAuthenticationTicket過時問題。

登陸頁面代碼:

<fieldset><legend>包含【用戶信息】的自定義登陸</legend> <form action="<%= Request.RawUrl %>" method="post"> <table border="0"> <tr><td>登陸名:</td> <td><input type="text" name="loginName" style="width: 200px" value="Fish" /></td></tr> <tr><td>UserId:</td> <td><input type="text" name="UserId" style="width: 200px" value="78" /></td></tr> <tr><td>GroupId:</td> <td><input type="text" name="GroupId" style="width: 200px" /> 1表示管理員用戶 </td></tr> <tr><td>用戶全名:</td> <td><input type="text" name="UserName" style="width: 200px" value="Fish Li" /></td></tr> </table> <input type="submit" name="CustomizeLogin" value="登陸" /> </form></fieldset> 

登陸處理代碼:

public void CustomizeLogin()
{
    // ----------------------------------------------------------------- // 注意:演示代碼爲了簡單,這裏不檢查用戶名與密碼是否正確。 // ----------------------------------------------------------------- string loginName = Request.Form["loginName"]; if( string.IsNullOrEmpty(loginName) ) return; UserInfo userinfo = new UserInfo(); int.TryParse(Request.Form["UserId"], out userinfo.UserId); int.TryParse(Request.Form["GroupId"], out userinfo.GroupId); userinfo.UserName = Request.Form["UserName"]; // 登陸狀態100分鐘內有效 MyFormsPrincipal<UserInfo>.SignIn(loginName, userinfo, 100); TryRedirect(); } 

顯示用戶信息的頁面代碼:

<fieldset><legend>用戶狀態</legend><form action="<%= Request.RawUrl %>" method="post"> <% if( Request.IsAuthenticated ) { %> 當前用戶已登陸,登陸名:<%= Context.User.Identity.Name.HtmlEncode() %> <br /> <% var user = Context.User as MyFormsPrincipal<UserInfo>; %> <% if( user != null ) { %> <%= user.UserData.ToString().HtmlEncode() %> <% } %> <input type="submit" name="Logon" value="退出" /> <% } else { %> <b>當前用戶還未登陸。</b> <% } %> </form></fieldset> 

爲了能讓上面的頁面代碼發揮工做,必須在頁面顯示前從新設置HttpContext.User對象。
爲此,我在Global.asax中添加了一個事件處理器:

protected void Application_AuthenticateRequest(object sender, EventArgs e) { HttpApplication app = (HttpApplication)sender; MyFormsPrincipal<UserInfo>.TrySetUserInfo(app.Context); } 

TrySetUserInfo的實現代碼:

/// <summary>
/// 根據HttpContext對象設置用戶標識對象 /// </summary> /// <param name="context"></param> public static void TrySetUserInfo(HttpContext context) { if( context == null ) throw new ArgumentNullException("context"); // 1. 讀登陸Cookie HttpCookie cookie = context.Request.Cookies[FormsAuthentication.FormsCookieName]; if( cookie == null || string.IsNullOrEmpty(cookie.Value) ) return; try { TUserData userData = null; // 2. 解密Cookie值,獲取FormsAuthenticationTicket對象 FormsAuthenticationTicket ticket = FormsAuthentication.Decrypt(cookie.Value); if( ticket != null && string.IsNullOrEmpty(ticket.UserData) == false ) // 3. 還原用戶數據 userData = (new JavaScriptSerializer()).Deserialize<TUserData>(ticket.UserData); if( ticket != null && userData != null ) // 4. 構造咱們的MyFormsPrincipal實例,從新給context.User賦值。 context.User = new MyFormsPrincipal<TUserData>(ticket, userData); } catch { /* 有異常也不要拋出,防止攻擊者試探。 */ } } 

在多臺服務器之間使用Forms身份認證

默認狀況下,ASP.NET 生成隨機密鑰並將其存儲在本地安全機構 (LSA) 中, 所以,當須要在多臺機器之間使用Forms身份認證時,就不能再使用隨機生成密鑰的方式, 須要咱們手工指定,保證每臺機器的密鑰是一致的。

用於Forms身份認證的密鑰能夠在web.config的machineKey配置節中指定,咱們還能夠指定加密解密算法:

<machineKey 
  decryption="Auto" [Auto | DES | 3DES | AES]
  decryptionKey="AutoGenerate,IsolateApps" [String]
/>

關於這二個屬性,MSDN有以下解釋:


在客戶端程序中訪問受限頁面

這一小節送給全部對自動化測試感興趣的朋友。

有時咱們須要用代碼訪問某些頁面,好比:但願用代碼測試服務端的響應。

若是是簡單的頁面,或者頁面容許全部客戶端訪問,這樣不會有問題, 可是,若是此時咱們要訪問的頁面是一個受限頁面,那麼就必須也要像人工操做那樣: 先訪問登陸頁面,提交登陸數據,獲取服務端生成的登陸Cookie, 接下來才能去訪問其它的受限頁面(但要帶上登陸Cookie)。

注意:因爲登陸Cookie一般是加密的,且會發生變化,所以直接在代碼中硬編碼指定登陸Cookie會致使代碼難以維護。

在前面的示例中,我已在web.config爲MyInfo.aspx設置過禁止匿名訪問,若是我用下面的代碼去調用:

private static readonly string MyInfoPageUrl = "http://localhost:51855/MyInfo.aspx"; static void Main(string[] args) { // 這個調用獲得的結果實際上是default.aspx頁面的輸出,並不是MyInfo.aspx HttpWebRequest request = MyHttpClient.CreateHttpWebRequest(MyInfoPageUrl); string html = MyHttpClient.GetResponseText(request); if( html.IndexOf("<span>Fish</span>") > 0 ) Console.WriteLine("調用成功。"); else Console.WriteLine("頁面結果不符合預期。"); } 

此時,輸出的結果將會是:

頁面結果不符合預期。

若是我用下面的代碼:

private static readonly string LoginUrl = "http://localhost:51855/default.aspx"; private static readonly string MyInfoPageUrl = "http://localhost:51855/MyInfo.aspx"; static void Main(string[] args) { // 建立一個CookieContainer實例,供屢次請求之間共享Cookie CookieContainer cookieContainer = new CookieContainer(); // 首先去登陸頁面登陸 MyHttpClient.HttpPost(LoginUrl, "NormalLogin=aa&loginName=Fish", cookieContainer); // 此時cookieContainer已經包含了服務端生成的登陸Cookie // 再去訪問要請求的頁面。 string html = MyHttpClient.HttpGet(MyInfoPageUrl, cookieContainer); if( html.IndexOf("<span>Fish</span>") > 0 ) Console.WriteLine("調用成功。"); else Console.WriteLine("頁面結果不符合預期。"); // 若是還要訪問其它的受限頁面,能夠繼續調用。 } 

此時,輸出的結果將會是:

調用成功。

說明:在改進的版本中,我首先建立一個CookieContainer實例, 它能夠在HTTP調用過程當中接收服務器產生的Cookie,並能在發送HTTP請求時將已經保存的Cookie再發送給服務端。 在建立好CookieContainer實例以後,每次使用HttpWebRequest對象時, 只要將CookieContainer實例賦值給HttpWebRequest對象的CookieContainer屬性,便可實如今屢次的HTTP調用中Cookie的接收與發送, 最終能夠模擬瀏覽器的Cookie處理行爲,服務端也能正確識別客戶的身份。

相關文章
相關標籤/搜索