原文:Why isn’t my session state working in ASP.NET Core? Session state, GDPR, and non-essential cookies
作者:Andrew Lock
譯文:https://www.cnblogs.com/lwqlun/p/10526380.html
譯者:Lamond Lu
在本篇部落格中,我將描述一個關於會話狀態(Session State)的問題, 這個問題我已經被詢問了好幾次了。這個問題的場景如下:
-
建立一個新的ASP.NET Core應用程式
-
一個使用者在會話狀態中設定了一個字串值,例如
HttpContext.Session.SetString("theme", "Dark");
-
在下一次請求中,嘗試從會話中讀取這個自字串的值
HttpContext.Session.GetString("theme");
, 但是得到的結果卻是null
! -
“額,這個愚蠢的框架不工作了”(╯°□°)╯︵ ┻━┻
這個問題的原因是ASP.NET Core 2.1中引入的GDPR功能與會話狀態互相影響了。在本篇部落格中,我將描述為什麼你會看到這種行為,以及一些處理它的方法。
GDPR中ASP.NET Core 2.1中引入的一個特性,如果你使用NET Core 1.x或2.0版本,你將不會遇到這個問題。但是請記住,自2019年6月27起,1.x版本即將失去支援,2.0版本已經不受支援了,因此你應該考慮升級到2.1及以上版本。
說明:
《通用資料保護條例》(General Data Protection Regulation,簡稱GDPR)為歐洲聯盟的條例,前身是歐盟在1995年制定的《計算機資料保護法》。
2018年5月25日,歐洲聯盟出臺《通用資料保護條例》。
ASP.NET Core中的會話狀態
就像我前面所說的,如果你使用的是ASP.NET Core 2.0及以前的版本,你不會遇到這個問題。這裡我將藉助ASP.NET Core 2.0展示一下預期的行為,以便說明遇到這個問題的人期望的會話狀態行為。然後我將在ASP.NET Core 2.2中建立等效的應用程式,並顯示會話狀態不再起作用了。
什麼是會話狀態?
會話狀態是一種可以回溯到ASP.NET(非核心)的功能,你可以使用它為瀏覽站點的使用者儲存和檢索伺服器端的值。 會話狀態經常在ASP.NET應用程式中廣泛使用,但經常由於一些原因而出現問題,主要是效能和可伸縮性。
ASP.NET Core中的你應該把會話狀態看作針對每使用者的快取。 從技術角度來看,ASP.NET Core中的會話狀態的功能需要2個獨立的部分來完成:
-
一個Cookie。 用來指定每個使用者的唯一ID(Session ID)
-
一個分散式快取。用來儲存與每個使用者唯一ID關聯的資料項
在一般的情況下,我會儘量避免使用會話狀態,使用會話狀態可能會有很多陷阱,如果不註意,就會引起一起不必要的問題。例如:
-
會話是針對每個瀏覽器的,而不是每個登入使用者的
-
會話結束的時候,應該刪除會話Cookie,但可能不會
-
如果會話中沒有任何值,它將會被刪除,並重新生成一個新的會話ID
-
本文中即將描述的GDPR問題
這裡我們講解了什麼是會話狀態,以及其工作的原理。在下一節中,我將建立一個小程式,這個小程式會使用會話狀態儲存你訪問過的頁面,然後在首頁上顯示該串列。
在ASP.NET Core 2.0專案中使用會話狀態
為了說明ASP.NET Core 2.0版本和2.1以上版本的行為變化,我將先建立一個ASP.NET Core 2.0專案,因為我的電腦上安裝了許多.NET Core SDK, 這裡我將使用2.0 SDK(版本號2.1.202)來構建一個2.0專案模板。
這裡我們首先建立一個global.json, 將當前app目錄的SDK版本固定為2.1.202版本。
dotnet new globaljson --sdk-version 2.1.202
然後使用dotnet new
命令建立一個新的ASP.NET Core MVC 2.0應用程式
dotnet new mvc --framework netcoreapp2.0
會話狀態預設情況下是沒有啟用的,所以這裡你需要先新增必要的服務。我們修改Startup.cs檔案ConfigureServices
方法來新增會話服務。預設情況下,ASP.NET Core將使用記憶體來儲存會話資訊,這對於測試來說很友好,但是生產環境中可能就需要替換為其他方式。
public void ConfigureServices(IServiceCollection services)
{
services.AddMvc();
services.AddSession(); // add session
}
當然,只新增服務是沒有用的,我們還需要在管道中註冊會話中介軟體。只有註冊在會話中介軟體之後的中介軟體才可以訪問會話狀態,所以你需要將會話中介軟體放在MVC中介軟體之前。
public void Configure(IApplicationBuilder app, IHostingEnvironment env) { // ...其他配置 app.UseSession(); app.UseMvc(routes => { routes.MapRoute( name: "default", template: "{controller=Home}/{action=Index}/{id?}"); }); }
對於這個簡單的例子,我將使用會話金鑰”actions”來儲存並讀取一個字串型別的會話值,這個會話值中會儲存你訪問過的所有頁面。當你在不同的頁面間瀏覽時,我們會將你訪問過的頁面以分號分隔的形式儲存在”actions”會話值中。現在我們更新HomeController
的程式碼:
public class HomeController : Controller
{
public IActionResult Index()
{
RecordInSession("Home");
return View();
}
public IActionResult About()
{
RecordInSession("About");
return View();
}
private void RecordInSession(string action)
{
var paths = HttpContext.Session.GetString("actions") ?? string.Empty;
HttpContext.Session.SetString("actions", paths + ";" + action);
}
}
註意:
Session.GetString(key)
是Microsoft.AspNetCore.Http
名稱空間中的一個擴充套件方法。
最後,我們修改Index.cshtml頁面的程式碼如下,在頁面中顯示當前”actions”的會話值
@using Microsoft.AspNetCore.Http
@{
ViewData["Title"] = "Home Page";
}
@Context.Session.GetString("actions")
如果你現在執行應用程式並瀏覽幾次,你將看到會話頁面訪問歷史串列的構建。 在下麵的示例中,我訪問了主頁三次,關於頁面兩次:
如果檢視當前頁面關聯的Cookie資訊,你就會看到一個名為.AspNetCore.Session
的Cookie, 它的值就是一個加密會話ID, 如果你刪除這個Cookie, 你將會看到”actions”的值被重置,頁面訪問歷史串列丟失。
這種會話狀態的行為就是大部分人所期望的,所以這裡沒有問題。但是當你使用ASP.NET Core 2.1/2.2版本建立相同專案之後,情況就不一樣了。
在ASP.NET Core 2.2專案中使用會話狀態
為了建立ASP.NET Core 2.2應用程式,我使用了幾乎相同的行為,但這次我沒有固定SDK。 我安裝了ASP.NET Core 2.2 SDK(2.2.102),因此以下命令會生成一個ASP.NET Core 2.2 MVC應用程式:
dotnet new mvc
這裡你依然需要顯示註冊會話服務,並啟用會話中介軟體,這一部分程式碼和前面一模一樣。
與以前的版本相比,較新的2.2模板已經簡化,因此為了保持一致性,我從2.0應用程式複製了HomeController。 我還複製了Index.chtml,About.chtml和Contact.cshtml檢視檔案。 最後,我更新了Layout.cshtml,為標題中的About和Contact頁面添加了連結。
這2個應用程式,除了使用的ASP.NET Core版本不一樣,其他的部分基本都是一樣的。然而這次執行的時候,當你瀏覽一些頁面之後,首頁只會顯示你訪問過首頁,而不會顯示你訪問過其他頁面。
不要點選隱私政策的橫幅 – 後面你將馬上知道原因
現在如果你去檢視一下你的Cookies, 你會發現加密會話ID.AspNetCore.Session
不存在。
一切都顯然配置正確,並且會話本身似乎也在工作(因為可以在Index.cshtml中成功檢索HomeController.Index中設定的值)。 但當頁面重新載入,或者在導航之間跳轉的時候,沒有儲存會話狀態。
那麼為什麼會話狀態在ASP.NET Core 2.0中正常工作, 在ASP.NET Core 2.1/2.2中反而沒有正常工作了呢?
到底發生了什麼?GDPR
問題的原因,是因為ASP.NET Core 2.1版本之後,引入了一些新功能。為了幫助開發人員遵守2018年生效的GDPR規則,ASP.NET Core 2.1版本引入了一些擴充套件點,以及模板的更新。
針對這些新功能的官方檔案寫的都很詳細,這裡我只做簡單總結:
-
同意Cookie對話方塊 – 預設情況下,在使用者點選同意對話方塊之前,ASP.NET Core不會將“非必要”的cookies寫入響應中
-
Cookie可以被設定為必要或者非必要的 – 無論使用者是否同意,必要的Cookies都會傳送給瀏覽器,非必要的Cookies需要得到使用者的同意
-
會話Cookie被認為是非必要的 – 因此,在使用者同意之前,無法跨導航或頁面重新載入跟蹤會話。
-
臨時資料(Temp Data)是非必要的 – ASP.NET Core 2.0以上版本中,臨時資料提供器使用Cookie來儲存資料項,所以在使用者同意之前,臨時資料功能是不可用的
所以問題是我們需要使用者同意使用Cookie。 如果單擊隱私橫幅上的“Accept”,則ASP.NET Core可以編寫會話cookie,並恢復預期的功能。
如何在ASP.NET Core 2.1及以上版本中使用會話狀態
根據你正在構建的程式,你可以使用多種選項。哪一個最適合你取決於你的使用場景,但是請註意,這些功能是為了幫助開發人員遵守GDPR而新增的。
如果你不在歐洲國家,或者你認為GDPR對自己沒有什麼影響,最好請閱讀一下https://andrewlock.net/session-state-gdpr-and-non-essential-cookies/ – GDPR可能依然適用於你
這裡主要的可選項如下:
-
在使用者同意Cookie之前,接受該會話狀態可能不可用。
-
在使用者同意Cookie之前,禁用需要會話狀態的功能。
-
禁用Cookie同意功能
-
將會話Cookie標記為必要的
我將在下麵詳細介紹每個選項,請記住考慮你的選擇可能會影響你是否遵守GDPR!
接受當前的行為
“最簡單”的選擇就是接受現有的行為。 ASP.NET Core中的會話狀態通常只應用於臨時資料,因此你的應用程式需要能夠處理會話狀態不可用的情況。
這取決於你使用會話的目的,可能可以實現或可能不能實現,但這是使用現有模板的最簡單方法,並且將你接觸GDPR問題方面風險降到了最低。
禁用需要會話的功能
第二種選擇和第一種選擇類似,應為你需要保持現有的行為。區別在於第一種選項會將會話簡單的視為快取,因此你始終需要假設會話值是可以讀取和儲存的。而第二種選項略有不同,因為你需要明確知道系統中哪些部分是需要會話狀態的,併在使用者同意Cookie之前,禁用它們。
例如, 你可以需要一個會話狀態儲存當前頁面選擇的主題。如果使用者沒有同意Cookie, 那麼你只需要隱藏主題選擇的功能。只要使用者同意,再將它顯示出來。
這感覺就像是針對選擇一的改進,因為它主要改善了使用者體驗。如果你不考慮哪些功能是需要會話的,使用者可能會產生一些疑惑。例如,如果你使用選項一,使用者在切換主題的時候,程式永遠不會記住它們的選擇,這就很讓人沮喪。
禁用Cookie同意功能
如果你確定不需要Cookie同意功能,你也可以很容易的禁用它。 預設模板在Startup.ConfigureServices
中顯式啟用了Cookie同意功能。
public void ConfigureServices(IServiceCollection services)
{
services.Configure<CookiePolicyOptions>(options =>
{
options.CheckConsentNeeded = context => true;
options.MinimumSameSitePolicy = SameSiteMode.None;
});
services.AddSession(); // added to enable session
services.AddMvc().SetCompatibilityVersion(CompatibilityVersion.Version_2_2);
}
這裡CheckConsentNeeded
屬性是一個標記,它用於檢查是否應將非必要的cookie寫入響應。 如果函式傳回true(如上所述,模板中的預設值),則跳過非必要的cookie。 將此更改為false並且會話狀態將起作用,而不需要使用者明確同意cookie。
標記會話Cookie是必要的
完全禁用cookie同意功能可能會對你的應用程式造成一定的負擔。 如果是這種情況,你可以將會話cookie標記為必要。
services.AddSession
的多載方法,允許你傳入一個會話配置物件。你可以使用它設定會話的超時時間,以及自定義會話Cookie。為了將會話Cookie標記為必要的,我們需要顯式配置IsEssential
的值是true。
public void ConfigureServices(IServiceCollection services)
{
services.Configure<CookiePolicyOptions>(options =>
{
options.CheckConsentNeeded = context => true;
options.MinimumSameSitePolicy = SameSiteMode.None;
});
services.AddSession(opts =>
{
opts.Cookie.IsEssential = true;
});
services.AddMvc().SetCompatibilityVersion(CompatibilityVersion.Version_2_2);
}
使用這種方法,雖然應用程式依然會顯示Cookie同意橫幅,並且在點選之前不會寫入非必要的Cookie。 但會議狀態將在使用者同意Cookie之前立即生效,因為它被認為是必要的。
總結
在這篇文章中,我描述了一個曾經多次被問過問題。開發人員發現他們的會話狀態沒有正確儲存。 這通常是由於ASP.NET Core 2.1中引入的Cookie同意和非必要cookie的GDPR功能引起的。
最後,我描述了處理這種行為的四種方法:
-
什麼也不做,接受它
-
禁用依賴會話狀態的功能,直到同意為止
-
取消同意要求
-
標記會話Cookie為必要的Cookie。
哪種選擇最適合你將取決於你正在構建的應用程式,以及你對GDPR和類似法規的認識。