会话状态(转)
掌握了會話狀態基本知識之后,讓我們通過分析會話狀態管理的技術細節,增強我們的技能。會話狀態處理是一個可以用以下三個步驟概括的任務:分配一個會話ID;從一個提供程序那里獲取會話數據;把它填充到頁面的上下文中。如前所述,會話狀態模塊控制所有這些任務的執行。這樣做時,它利用兩個額外組件:會話ID生成器和會話狀態提供程序。在ASP.NET 2.0中,這兩個組件都可以用定制組件代替,后文將對此進行介紹。現在,要解決使用會話狀態時面臨的實際問題。
13.3.1 標識一個會話
每個活動的ASP.NET會話使用一個只由URL允許的字符組成的120位字符串進行標識。會話ID被保證是惟一的并且是隨機產生的,以避免數據沖突和防止惡意攻擊。根據現有ID通過算法來獲得一個有效的會話ID實際上并不可行。在ASP.NET 1.x中,會話ID生成器是一個埋藏在框架中的系統組件,對外部不可見。在ASP.NET 2.0中,會話ID生成器變成了一個可定制的組件,開發人員可以有選擇地替換它。
注意
一個古老的諺語提醒了我們,任何事情都不能僅僅因為可行就去做(nothing should be done only because it is doable)。這個諺語特別適合這里的情形,因為我們在討論ASP.NET 2.0中可定制的會話狀態管理。這些子系統(諸如會話ID生成器)只有在理由充分的情況下才進行定制,并且確保這么做不會使事情變得更糟或者降低安全水平。稍后我將更詳細地討論這一點。1. 創建會話ID
根據設計,會話ID長15字節(15×8=120位)。會話ID是使用隨機數生成器(Random Number Generator,簡稱RNG)密碼提供程序生成。該服務提供程序返回一個由15個隨機生成的數值組成的數組。然后把該數組映射到有效的URL字符,并作為一個字符串返回。
如果會話沒有包含任何數據,則為每個請求生成一個新的會話ID,并且會話狀態并不持久地存儲在狀態提供程序中。然而,如果使用一個 Session_Start處理程序,則會話狀態總是被保存,即使它為空。由于這個原因,特別是在沒有使用進程內會話提供程序時,定義 Session_Start處理程序要特別小心,并且只有在確實需要時才定義。
相反,在一個非空的會話字典超時或被丟棄之后,會話ID仍然不變。根據設計,即使會話狀態到期,會話ID一直延續到瀏覽器會話結束為止。這就是說,只要瀏覽器實例保持相同,則同一個會話ID用來表示一段時間內的多個會話。
2. 會話cookie
SessionID字符串傳遞給瀏覽器,然后以如下兩種方法之一返回給服務器應用程序:使用一個cookie或一個修改過的URL。在默認情況下,會話狀態模塊在客戶端創建一個HTTP cookie,但也可以使用一個修改過的URL,其中嵌入SessionID字符串,特別針對無cookie的瀏覽器。具體采取哪種方法取決于應用程序的 web.config文件中存儲的配置設置。在默認情況下,會話狀態使用cookie。
實際上,cookie只不過是網頁放在客戶端硬盤上的一個文本文件。在ASP.NET中,cookie由HttpCookie類的一個實例表示。一個cookie通常有一個名稱、一個值集合和一個到期時間。此外,我們可以對cookie進行配置,以通過安全連接作用于特定的虛擬路徑(例如, HTTPS)。
重要提示
ASP.NET 2.0利用HTTP-only特征建立支持會話cookie的瀏覽器上的會話cookie。例如,在Microsoft Internet Explorer 6.0 sp1或安裝了Windows XP SP2的系統上支持會話cookie。HTTP-only特征防止這些cookie被用于客戶端腳本,從而豎起一道屏障,以防可能發生的旨在偷取會話ID 的跨站點腳本 攻擊。當cookie被啟用時,會話狀態模塊實際創建一個具有特定名稱的cookie,并把會話ID保存在它那里。cookie的創建如下面的偽碼所示:
HttpCookie sessionCookie;
sessionCookie = new HttpCookie("ASP.NET_SessionId", sessionID);
sessionCookie.Path = "/";
ASP.NET_SessionId是cookie的名稱,而sessionID字符串是它的值。cookie也與當前域的根關聯。Path屬性描述應用cookie的相對URL。會話cookie被給予一個非常短的到期期限,并在每個成功的請求結束時續訂。cookie的Expires屬性指出該 cookie在客戶端到期的時間。如果沒有顯式地設置該屬性(會話cookie通常就是這樣),則Expires屬性默認為 DateTime.MinValue——即.NET Framework中可能的最小時間單位。
注意
需要寫入一個cookie的服務器端模塊添加一個HttpCookie對象到Response.Cookies集合中。客戶端存在的并與被請求領域關聯的所有cookie都被上傳,并使其可以通過Request.Cookies集合讀取。3. 無cookie會話
為了使會話狀態起作用,客戶端必須能夠把會話ID傳遞給服務器端應用程序。這種情況如何發生依賴于應用程序的配置。ASP.NET應用程序通過配置文件的<sessionState>節定義它們的會話特有的設置。為了決定cookie支持,將cookieless屬性設置為表13.7中的一個值。表中所列的值屬于HttpCookieMode枚舉類型。
表13.7 HttpCookieMode枚舉類型
| 模 式 | 描 述 |
| AutoDetect | 只有在請求瀏覽器支持cookie時才使用cookie |
| UseCookies | 使用cookie持久地存儲會話ID,而不管瀏覽器是否支持cookie。這是默認選項 |
| UseDeviceProfile | 根據配置文件的設備配置文件節所列的瀏覽器能力作出決策 |
| UseUri | 將會話ID存儲在URL中,不管瀏覽器是否支持cookie。如果在任何情況下都不使用cookie,則使用該選項 |
使用AutoDetect時,ASP.NET將查詢瀏覽器以確定它是否支持cookie。如果瀏覽器支持cookie,會話ID就存儲在一個 cookie中;否則,會話ID將存儲在URL中。另一方面,使用UseDeviceProfile模式時,不檢查瀏覽器的有效能力。為了讓HTTP會話模塊決定使用cookie還是URL,使用由HttpBrowserCapabilities對象的 SupportsRedirectWithCookie屬性產生的瀏覽器所聲明的功能。要注意的是,即使瀏覽器能夠支持cookie,但是用戶可能會禁用 cookie。在這種情況下,會話狀態不能正確起作用。
注意
在ASP.NET 1.x中,可以選擇的方案更少。<sessionState>節的cookieless屬性只能接收一個布爾值。若要禁用會話中的cookie,則將該屬性設置為true。在禁用cookie支持下,假設請求如下URL處的一個頁面:
http://www.contoso.com/test/sessions.aspx
在瀏覽器的地址欄中顯示的內容略有不同,而且現在包含會話ID,如下所示:
http://www.contoso.com/test/(S(5ylg0455mrvws1uz5mmaau45))/sessions.aspx
實例化時,會話狀態模塊檢查cookieless屬性的值。如果該值為true,則請求被重定向(HTTP 302狀態碼)到一個修改過的虛擬URL(正好在頁面名稱前包含會話ID)。再次被處理時,該請求嵌入會話ID。一個專用的ISAPI篩選器 (aspnet_filter.exe組件)預處理該請求,解析URL,并在該URL融入一個會話ID時重寫正確的URL。所檢測到的會話ID也存儲在一個稱為AspFilterSessionId的額外HTTP頭部,并在以后進行檢索。
4. 無cookie會話的問題
無cookie會話旨在使有狀態的應用程序也可以在一個不支持cookie的瀏覽器或者在一個沒有啟用cookie的瀏覽器上運行,但它也存在問題。首先,當會話開始時,以及每當用戶從一個應用程序的頁面內跟隨一個絕對URL時,它們會導致重定向。
使用cookie時,我們可以清除地址欄,進入另一個應用程序,然后轉向前一個應用程序,并檢索相同的會話值。如果在禁用會話時這么做,會話數據會丟失。該特征對于頁面回發沒有什么問題,因為這是使用相對URL自動實現的,但若使用絕對URL進行鏈接,則會引發一個嚴重的問題。在這種情況下,總是會創建一個新的會話。例如,如下代碼中斷會話:
<a runat="server" href="/test/sessions.aspx">Click</a>
有沒有一種辦法自動打破鏈接或超鏈接中的絕對URL,以便它們融入會話信息嗎?我們可以使用如下技巧,它使用了HttpResponse類的ApplyAppPathModifier方法:
<a href='<% =Response.ApplyAppPathModifier("test/page.aspx")%>' >Click</a>
ApplyAppPathModifier方法取一個表示相對URL的字符串,并返回一個絕對URL,其中嵌入會話信息。需要從一個HTTP頁面重定向到一個HTTPS頁面(其中強制要求完整的絕對地址)時,則上述技巧特別有用。要注意的是,如果會話cookie被啟用,并且路徑是絕對路徑,則 ApplyAppPathModifier返回原始URL。
警告 在服務器端表達式(即標記了runat=server屬性的表達式)中,不能使用<%...%>代碼塊。之所以在上述代碼中可行,是因為<a>標簽被逐字發出,沒有設置runat屬性。
5. 無cookie會話和安全性
使用無cookie會話的另一個問題與安全性有關。會話欺詐是最流行的攻擊類型之一,牽涉到通過為另一個合法用戶生成的會話ID訪問外部系統。請嘗試下面的做法:將應用程序設置為在無cookie下工作,并訪問一個頁面。當會話ID在瀏覽器的地址欄中出現時,獲取它的URL,并立即通過電子郵件發給一位朋友。讓你的朋友把URL粘貼到自己的機器上并單擊“轉到”。只要會話有效,你的朋友就能訪問你的會話狀態。毫無疑問,會話ID并沒有得到良好的保護。為了系統的安全性,關鍵是有一個不可預測的ID生成器,因為它使猜測有效的會話ID更難了。對于無cookie會話,會話ID在地址欄中提供給外界,并且對所有的人都是可見的。由于這個原因,如果把專用的或機密的信息存儲在會話狀態中,建議使用安全套接字層(Secure Sockets Layer,SSL)或傳輸層安全性(Transport Layer Security,TLS)加密瀏覽器和服務器之間的包含會話ID的任何通信。
此外,當用戶認為這么做違反了安全性時,始終應當使他們能夠退出登錄,并調用Abandon方法。對使用會話ID來竊取會話狀態中所存儲數據的人來說這種設計減少了他們的時間,而說到安全性,重要的是在使用無cookie會話時要對系統進行配置,以免重用過期的會話ID。此行為在ASP.NET中可以通過<sessionState>節進行配置,下一節將詳細 介紹。
6. 配置會話狀態
從ASP.NET 1.x遷移到ASP.NET 2.0以后,<sessionState>節的內容顯著增多了。該節的內容如下所示:
<sessionState
mode="Off|InProc|StateServer|SQLServer|Custom"
timeout="number of minutes"
cookieName="session cookie name"
cookieless="http cookie mode"
regenerateExpiredSessionId="true|false"
sqlConnectionString="sql connection string"
sqlCommandTimeout="number of seconds"
allowCustomSqlDatabase="true|false"
useHostingIdentity="true|false"
partitionResolverType=""
sessionIDManagerType="custom session ID generator"
stateConnectionString="tcpip=server:port"
stateNetworkTimeout="number of seconds"
customProvider="custom provider name">
<providers>
...
</providers>
</sessionState>
表13.8詳細描述了各個屬性的目標和特征。要注意,只有mode,timeout,stateConnectionString和 sqlConnectionString屬性在ASP.NET 1.x中有對等的屬性。cookieless屬性在ASP.NET 1.x中也存在,但是它接受布爾值。所有其他屬性都是ASP.NET 2.0新引入的。
表13.8 <sessionState>屬性
| 屬 性 | 描 述 |
| allowCustomSqlDatabase | 如果為true,則能夠指定一個定制數據庫表來存儲會話數據,而不必使用標準的ASPState |
| Cookieless | 指定如何把會話ID傳遞給客戶端 |
| cookieName | cookie的名稱(如果cookie用于會話ID的話) |
| customProvider | 定制的會話狀態存儲提供程序的名稱,用于存儲和檢索會話狀態數據 |
| mode | 指定把會話狀態存儲在哪里 |
| partitionResolverType | 當會話狀態工作于SQLServer或StateServer模式時,指明被加載以提供連接信息的分區解析器組件的類型和程序集。如果可以正確地加載分區解析器,則忽略sqlConnectionString和stateConnectionString屬性 |
| regenerateExpiredSessionId | 用一個已過期的會話ID發出一個請求時,如果該屬性為true,則生成一個新的會話ID;否則,重新使用過期的會話ID。默認值為false |
| sessionIDManagerType | 默認為Null。如果設置了該屬性,則指明用作會話ID的生成器的組件 |
| sqlCommandTimeout | 指定一個SQL命令在被取消前可以空閑的秒數。默認為30秒 |
| sqlConnectionString | 指定SQL Server的連接字符串 |
| stateConnectionString | 指定用來遠程存儲會話狀態的服務器名稱或地址和端口 |
| stateNetworkTimeout | 指定Web服務器和狀態服務器之間的TCP/IP網絡連接在請求被取消前可以空閑的秒數。默認為10秒 |
| timeout | 指定一個會話在被放棄前可以空閑的秒數。默認為20秒 |
| useHostingIdentity | 默認值為True。它指明在訪問一個定制的狀態提供程序或者進行了集成安全性配置的SQLServer提供程序時,ASP.NET進程標識被假冒 |
此外,子<providers>節列出了定制的會話狀態存儲提供程序。ASP.NET會話狀態旨在使我們能夠輕松地把用戶會話數據存儲在不同的源中,如Web服務器的內存中或SQL Server。存儲提供程序是一個管理會話狀態信息的存儲并把它存儲在另一種介質(例如,Oracle)或布局中的組件。我們將在本章后面重新討論該主題。
13.3.2 會話的生命期
只有在第一個數據項添加到內存中的會話字典時,會話狀態的生命才開始。如下代碼說明了如何修改會話字典中的一個數據項。“MyData”是惟一地標識該值的鍵。如果該字典中已經存在一個稱為“MyData”的鍵,則重寫現有的值:
Session["MyData"] = "I love ASP.NET";
Session字典通常包含object類型;要讀回數據,需要將返回值轉換為一種更具體的 類型:
string tmp = (string) Session["MyData"];
當頁面把數據保存到Session字典中時,返回值被加載到一個內存中的字典——即,SessionDictionary內部類的一個實例(參見圖13.2)。其他并發運行的頁面不能訪問該會話,直到正在進行的請求完成為止。
1. Session_Start事件
會話啟動事件與會話狀態無關。當會話狀態模塊服務給定用戶發出的要求新會話ID的第一個請求時,Session_Start事件激發。ASP.NET運行庫可以在一個會話上下文中服務多個請求,但是只對它們中的第一個請求激發Session_Start事件。
每當請求一個不把數據寫入會話字典中的頁面時,創建一個新的會話ID,并激發一個新的Session_Start事件。會話狀態的體系結構非常復雜,因為它必須支持各種狀態提供程序。總體方案要求在請求完成時把會話字典的內容序列化到狀態提供程序。然而,為了優化性能,只有在字典的內容不空時該過程才會真正執行。然而,如前所述,如果應用程序定義了一個Session_Start事件處理程序,則無論如何都會發生序列化。
2. Session_End事件
Session_End事件表示會話的結束,用來執行終止會話所需的任何清除代碼。然而要注意的是,只有在Inproc模式下,即當會話數據存儲在ASP.NET工作進程中時,才支持該 事件。
為了使Session_End事件激發,會話狀態必須先存在。這就是說,我們必須把一些數據存儲在會話狀態中,并且至少必須完成一個請求。第一個值添加到會話字典中時,一個數據項插入ASP.NET緩存——上述的Cache對象將在下一章詳細介紹。該行為是進程中的狀態提供程序所特有的;進程外狀態提供程序和SQL Server狀態服務器都不使用Cache 對象。
然而,更有趣的是,添加到緩存中的數據項(每個活動會話只有一個數據項)被賦予一個特殊的到期策略。我們將在下一章學習ASP.NET緩存及其相關的到期策略。而現在,只要知道添加到該緩存中的會話狀態項被賦予一個活動到期時間,其間隔時間設置為會話超時時間。只要在會話內還有請求要處理,則滑動到期期限自動續訂。會話狀態模塊在處理EndRequest事件時重置超時時間。只要通過對緩存執行一次讀取操作,它就能獲得期望的結果!給定 ASP.NET Cache對象的內部結構,這就等于續訂滑動到期期限。因而,當緩存項到期時,該會話已經超時。
到期的緩存項自動地從緩存中刪除。作為該項的到期策略的一部分,狀態會話模塊還指示一個刪除回調函數。緩存自動地調用該刪除方法,而后者激發Session_End事件。
注意
Cache中表示一個會話的狀態的數據項,不能從system.web程序集的外部進行訪問,更不能進行列舉,因為它們被存放在一個系統保留的緩存區域。換句話說,我們不能以編程的方式訪問駐留在另一個會話中的數據,更不能刪除它。3. 為什么我的會話狀態會丟失?
當會話超時或被放棄時,Session對象中存儲的值要么以編程的方式從內存中刪掉,要么被系統從內存中刪掉。然而,在某些情況下,會話狀態會不知不覺地丟失。如何解釋這種奇怪的行為呢?
當工作模型是InProc時,會話狀態在正在服務該頁面請求的AppDomain的內存空間中被映射。根據該規定,會話狀態受進程回收和 AppDomain重啟支配。ASP.NET工作進程定期重啟,以維護良好的平均性能;當ASP.NET工作進程重啟時,會話狀態丟失。進程回收取決于內存消耗百分比,還有可能取決于被服務的請求量。雖然這是周期性的,但是不能對周期的間隔時間做出一般的估計。在設計基于會話的、進程內應用程序時,要注意這一點。通常要記住的是,試圖訪問會話狀態,它可能沒有。盡量使用適合自己的應用程序的異常處理或還原技術。
考慮到一些反病毒軟件可能把web.config或Global.asax文件標記為已修改,導致一個新的應用程序啟動,從而丟失會話狀態。如果我們或我們的代碼修改那些文件的時間戳,或者修改其中一個專用文件夾(諸如Bin或App_Code等)的內容,也會丟失會話狀態。
注意
當一個正在運行的頁面碰到一個錯誤時,會話狀態會發生什么呢?在請求結束時,如果頁面產生一個錯誤——即,Server對象的 GetLastError方法返回一個異常,則該會話的狀態不會被保存。然而,如果在異常處理程序中通過調用Server.ClearError重置錯誤狀態,則好像沒有發生任何錯誤一樣有規律地保存會話的值。總結
- 上一篇: 新技术 新体验 - 北京.NET俱乐部V
- 下一篇: CCNA1 - Final Exam A