ession 是保存用戶和 Web 應用的會話狀態的一種方法,ASP.NET Core 提供了一個用于管理會話狀態的中間件。在本文中我將會簡單介紹一下 ASP.NET Core 中的 Session 的使用方法。
nuget 添加引用 Microsoft.AspNetCore.Session
ession 是基于 IDistributedCache 構建的,所以必須引用一種 IDistributedCache 的實現,ASP.NET Core 提供了多種 IDistributedCache 的實現 (Redis、SQL Server、In-memory)
services.AddDistributedMemoryCache();
services.AddSession();
nuget 添加引用 Microsoft.Extensions.Caching.SqlServer
SqlServerCache實現允許分布式緩存使用SQL Server數據庫作為其后備存儲。要創建SQL Server表,您可以使用sql-cache工具,該工具將使用您指定的名稱和模式創建一個表。
要使用sql-cache工具,請添加SqlConfig.Tools到.csproj文件的<ItemGroup>元素并運行dotnet恢復。
<ItemGroup>
<DotNetCliToolReference Include="Microsoft.Extensions.Caching.SqlConfig.Tools" Version="1.0.0-msbuild3-final" />
</ItemGroup>
通過運行以下命令來測試SqlConfig.Tools
C:\DistCacheSample\src\DistCacheSample>dotnet sql-cache create --help
sql-cache工具將顯示用法,選項和命令幫助,現在你可以創建表到sql server中,運行“sql-cache create”命令:
C:\DistCacheSample\src\DistCacheSample>dotnet sql-cache create "Data Source=(localdb)\v11.0;Initial Catalog=DistCache;Integrated Security=True;" dbo TestCache
info: Microsoft.Extensions.Caching.SqlConfig.Tools.Program[0]
Table and index were created successfully.
創建的表格具有以下架構:
注意的ConnectionString(以及可選地,SchemaName和TableName)通常應該被存儲的源控制(如UserSecrets)以外,因為它們可能包含憑證。
像所有的緩存實現一樣,你的應用程序應該使用一個實例來獲取和設置緩存值IDistributedCache,而不是SqlServerCache。該示例SqlServerCache在Production環境中實現(因此已配置ConfigureProductionServices)。
// Microsoft SQL Server implementation of IDistributedCache.
// Note that this would require setting up the session state database.
services.AddDistributedSqlServerCache(o =>
{
o.ConnectionString = "Server=.;Database=ASPNET5SessionState;Trusted_Connection=True;";
o.SchemaName = "dbo";
o.TableName = "Sessions";
});
services.AddSession();
nuget 添加引用 Microsoft.Extensions.Caching.Redis
Redis是一款開源的內存數據存儲,通常用作分布式緩存。您可以在本地使用它,并且可以為Azure托管的ASP.NET Core應用程序配置Azure Redis緩存。您的ASP.NET Core應用程序使用RedisDistributedCache實例配置緩存實施。
您可以ConfigureServices通過請求一個實例IDistributedCache(參見上面的代碼)來配置Redis實現并在您的應用代碼中訪問它。
在示例代碼中,RedisCache當為服務器配置Staging環境時使用實現。因此該ConfigureStagingServices方法配置RedisCache:
services.AddDistributedRedisCache(options => { options.Configuration = "localhost"; options.InstanceName = "SampleInstance"; });
接著在 Startup.cs 的 Config 方法中配置使用 Session 中間件,所有中間件的配置順序非常重要,必須在 UseSession 調用后才能訪問 Session 。
// 必須在 UseMvc 之前調用
app.UseSession();
app.UseMvc(routes =>
{
routes.MapRoute(
name: "default",
template: "{controller=Home}/{action=Index}/{id?}");
});
在 AddSession 和 UseSession 方法中可以傳入一個 SessionOptions 參數,通過該參數可以設置 Session 的 Cookie name, Cookie path 等信息。
配置完成后,就可以使用 Session 保存數據了。
具體實現redis實現 https://www.cnblogs.com/liuxiaoji/p/9259747.html
Session 安裝配置好就可以通過 HttpContext.Session 來保存和讀取數據了。由于 Session 是基于 IDistributedCache 構建的,因此 Session 只能存儲 byte[] 數據,這樣使用起來很不方便,好在有很多擴展方法可以用來直接讀取和保存 string、int 等類型數據。
一個 Session 使用的簡單示例:
public IActionResult Index()
{
HttpContext.Session.SetString("SessionStartedTime", "Session started time:" + DateTime.Now.ToString());
return View();
}
public IActionResult About()
{
ViewData["CurrentTime"] = "Current time:" + DateTime.Now.ToString();
ViewData["SessionStartedTime"] = HttpContext.Session.GetString("SessionStartedTime");
return View();
}
或者設置一個擴展類也可以直接將實體類序列化成json存儲
essionStorage只能保存在單一的瀏覽器窗口或分頁(tab), 關閉瀏覽器后存儲的數據就消失了, 其最大的用途在于保存一些臨時的數據, 防止用戶重新整理
網頁時不小心丟失這些數據。sessionStorage的操作方法與localStorage相同;
1 存儲
window.sessionStorage.setItem("userdata","Hello HTML5");
window.sessionStorage["userdata"] = "Hello HTML5";
window.sessionStorage.userdata = "Hello HTML5";
2 讀取
var value1 = window.sessionStorage.getItem("userdata");
var value1 = window.sessionStorage["userdata"];
var value1 = window.sessionStorage.userdata;
3 清除
window.sessionStorage.removeItem("userdata");
delete window.sessionStorage.userdata;
delete window.sessionStorage["userdata"];
//全部清除
sessionStorage.clear();
session聲明周期包括刷新網頁的頁面
文鏈接: https://blog.by24.cn/archives/about-session.html
Web 開發中,Session 是經常用到的概念,但是在日常交流中,似乎又經常引起誤解。在我看來,引發誤解的原因主要有兩個:
下面,我就嘗試著捋一下整個問題,看看能不能盡量消除這些誤會。
注:其它通訊領域也有使用 Session 這個詞匯,本文僅探討 Web 開發中的 Session 使用。
本文確實冗長無比,此處列出簡要觀點方便『太長不看』(Too Long; Didn't Read.)的讀者。
由于大家使用的『簡稱』含義往往差別很大,在此我們先約定一些稱呼。
這些稱呼可能和你慣用的不太一樣,但統一定義是高效討論的重要基礎,還請不要抗拒。
Session 經常被翻譯為 『會話』,其實還是很貼切的,它代表了『一次』相互溝通,這次會話中,可能包括了多次通信。
需要注意的是:此處 Session 是一個整體性的虛擬概念,不代指任何實體或數據。
看到 Data 后綴就知道,這個詞代表了一次 Session 中,暫存或使用 的一些數據。
具體是什么數據取決于業務實現,例如可能存儲了一些用戶的 ID 信息或短效配置等。
需要注意的是,此處指的是數據的『實體』,而不是『索引』。
上面說到的數據實體 Session-Data ,未必會直接放在程序內部,而是可能存儲在某一個具體的位置(后文會詳細討論)。
在大部分 Server Side Session 實現中(即 Session-Data 存儲在服務端時),為了方便取用,就需要記錄一個『索引』,用來找到具體的 Session-Data ,我們將這個『索引』稱為 Session-ID 。
需要注意的是:在某些實現中,可能并沒有 Session-ID 的概念(例如 Client Side Session)。
正如我前面所強調的,在不同的實現中,Session-Data 可能存儲在不同的位置:可能在客戶端,也可能在服務端。
在大部分實現中,并不會直接傳輸 Session-Data ,而是傳輸它的索引:Session-ID ,服務端程序收到 Session-ID 后,從存儲著 Session-Data 的地方取出具體的 Session-Data 來使用。
在這種模式下,更容易被我們接觸到的,其實是存儲在 Cookies 里的 Session-ID ,而許多人存在的一個誤區,便是將 Session-ID 直接等同于 Session 本身,無視了 Session-Data 的存在,這其實是不正確的。
上圖表示了這種模式的大致運行方式,綠色的 Session 是一個老用戶會話, Alice 在訪問時發送了自己持有的 Session-ID AA01 來標記當前會話,服務器程序根據相應的 Session-id 查詢出了相應的 Session Data ,并在程序中應用。
對于新用戶,因為自己沒有存儲 Session-id,只好不發送,服務器為了標記本次會話,生成了新的 Session-ID NN02 并返回給客戶端,這樣就開啟了一個新的會話,圖中用紅色 Session 表示。
除了上面提到的常見模式,其實還存在一種實現被許多人忽視,服務端并不存儲 Session-Data,而是直接將 Session-Data 存儲在客戶端(瀏覽器)的 Cookies 里。
在這種情況下,你甚至找不到 Session-ID 的存在,許多人也會因此而搞不清 Session 究竟存儲在哪里。
上圖表示了這種模式的大致運行方式,與上面那種方式不同的地方在于,客戶端直接告訴服務端,我自己是 Alice ,我的基本信息是 xxx ,于是服務端使用這些數據返回了相應的頁面。
對于新用戶,本地一丁點兒信息都沒有,于是服務端返回了一個空的 Session-Data 用來初始化,在后續的通信中,這個 Session-Data 會被繼續填充上有意義的數據。
很顯然,這里存在著巨大的安全問題:我直接修改存儲在本地的 Session-Data,聲稱我自己是管理員怎么辦?
為此,Client side session 需要加入加密機制或簽名機制來進行校驗,以確保所有的 Session-Data 都是由服務端生成的,具體會在下文討論。
提到 Session 就不能不提 Cookies,畢竟很多人會條件反射的把這二者弄混(苦笑臉)。
Cookies 自身就有非常多的玩兒法,拋開那些權限相關的內容,此處我們先只它最基礎的幾個特性:
正是這些特征,讓 Cookies 成為存儲 Session-Data / Session-ID 的絕佳場所,在幾乎所有的常見實現中,均默認使用 Cookies 來存儲 Session 相關信息。
但是需要注意,Session-Data / Session-ID 選擇 Cookie 作為容器只是因為『方便』,但這并不是必然情況:
我們完全可以實現一個 Web 框架,它使用 LocalStorage 來存儲 Session-Data / Session-ID ,在發起 XHR 請求時,主動將其加進 Header 內(甚至 URL 內),服務端則從相應位置讀取信息完成業務邏輯,在這套框架體系內這是完全沒有問題的。
實際上,許多框架都支持 Session with out Cookies 的實現,雖然存在各種各樣的小問題或限制,但是確實可用。
因此,需要再次強調:對于 Session 機制來說,Session-Data / Session-ID 才是本體,Cookies 只是一個存儲容器,這二者沒有捆綁關系。
對于 Session 機制,之所以總是產生種種誤解, 和各大 語言 / 框架 的實現有著很大的關聯,由于大部分人都更熟悉自己常用的框架,導致很容易被當前框架的思路帶進去。
那么接下來,就看一下各種常用 語言 / 框架 在 Session 機制上的實現與差異。
注:以下只是各個 語言 / 框架 默認支持的實現,實際中我們完全可以自定義任何形式的實現。
PHP 不愧是為 Web 而生的語言,在語言層面就提供了 Session 機制的支持,按默認配置就可以使用 $_SESSION 這個超全局變量。
原生 PHP 默認使用的 Session-Data 存儲方式為 Server Side Session ,使用 Session-ID 進行索引,Session-ID 默認存儲在 Cookies 的 PHPSESSID 字段。
Session-Data 默認以文件形式存儲在 session_save_path 所配置的路徑中, PHPSESSID字段的內容就是文件名,文件內容就是序列化后的 Session-Data 數據。
通過配置 session.save_handler 字段,我們可以將存儲方式切換為 redis 或者 memcache 。
以 redis 為例,Session-Data 會被存儲在PHPREDIS_SESSION:PHPSESSID 中,PHPREDIS_SESSION:為默認的統一前綴。
除此之外,還有一些其它的配置可以參考官方文檔,原生 PHP 也支持其它的相關擴展,不再一一贅述。
Laravel 果然也很強,不但默認提供了常規的 Server Side Session ,也支持配置為 Client Side Session 形式。
通過配置 config/session.php 的 driver 字段,可以切換 file 、cookie 、database 、memcached 、redis 、array 這 6 種不同的存儲方式。
這里面, file 、memcached 、redis 都是比較常見的存儲方式,就不再贅述。
database 可以將 Session-Data 存儲在你定義的數據庫內,但是你需要先建好一張表,里面配置好要存儲的字段。
array 是一種比較奇怪的方式,只是用在開發階段,它并沒有任何持久化功能,隨著 PHP 腳本的運行結束而消失。
cookie 則是比較少見的** Client Side Session**了,Laravel 使用 EncryptedStore.php 對 Session-Data 進行 『加密』后存儲于客戶端瀏覽器的 Cookies 中。
這里需要注意,Laravel 在實現 **Client Side Session **的時候使用的是 『加密』方式來保障信息安全。
接下來看看老牌框架 CI 怎么樣,翻看 配置文檔 可以看到,CI4 默認支持 file 、database 、memcached 、redis 這幾種 Server Side Session,看起來似乎平平無奇,都是常見套路。
但是文檔上有一行備注:
In previous CodeIgniter versions, a different, “cookie driver” was the only option and we have received negative feedback on not providing that option. While we do listen to feedback from the community, we want to warn you that it was dropped because it is unsafe and we advise you NOT to try to replicate it via a custom driver.
這就很有意思了,看起來 CI 框架以前也支持Client Side Session 嘛?翻出了 CI2 的文檔 ,果然里面默認使用 Cookies 存儲 Session-Data ,也提供了加密的選項。
那為什么 CI2 當時會因為這個被錘呢?我找到一篇相關的文章:(IN)secure session data in CodeIgniter - Websec ,文章中的觀點來看:
其實這兩個問題都更應該歸咎于用戶的配置不當,但是畢竟和框架的默認配置有關,被錘也就在情理之中了。
可能也是因為這件事,才導致了 CI 現在只提供 Server Side Session 實現吧。
翻看了一下 Symfony、Yii 、CakePHP、ThinkPHP 等熱門框架關于 Session 的文檔,都使用了比較常規的 Server Side Session 方式,也都支持將 Session-Data 存儲在文件、數據庫、內存數據庫等,在此就不一一介紹了,有需要的可以點鏈接查看相應文檔。
Tomcat 也是 Server Side Session 一派,默認在 Cookies 中使用 JSESSIONID 字段存儲 Session-ID,Session-Data 默認存儲在內存中 ,翻閱 org.apache.catalina.session.ManagerBase 的源碼,可以看到它使用了一個ConcurrentHashMap 結構來存儲所有的 Session-Data 實例。
Tomcat 默認使用 StandardManager 來管理 Session-Data,當容器退出時,在退出時將所有的數據進行持久化;你也可以選擇使用 PersistentManager 來管理,它可以更加靈活的執行持久化(但是 Session-Data 還是會存儲在你的內存里)。
Jetty 也比較中規中矩,參照文檔來看,支持將 Session-Data 配置為存儲在 內存、文件、JDBC 等位置。
比較不同的是,Jetty 支持為 Session-Data 配置 L1、 L2 兩級緩存,在對性能有需求的場景下還是挺友好。
果然是家大業大的框架,Spring Session 自己就是個完整的組件了,毫無意外的支持把 Session-Data 儲存在 redis 、mongodb ,jdbc 等地方,方便擴展。
不知如此,它還有一些特有的特性:
在舊一些的版本中,Spring Session 還支持 在一個瀏覽器會話中,維護多個 Session ,不過在新版本中,這個特性被刪除了,也許以后才會加回來。
這個巨無霸框架其實還支持其它一大堆特性,這里就不太多介紹了。
作為一個功能完善的重量級框架,Django 默認直接將 Session-Data 存儲在了數據庫,當然,你也可以配置為其它的存儲方式,不再贅述。
這些存儲方式中,Client Side Session 的代表 Cookies 再次出現,Session-Data 在序列化并『簽名』后,存儲在客戶端 Cookies 中。
這里需要注意,此處是『簽名』而不是『加密』,也就是說,客戶端雖然不能篡改 Session-Data,但是可以自己反序列化后查看內容。
另外,Django 的 Session-data 還支持使用 Pickle 進行序列化,對于某些場景會非常有用(同時也帶來了一些潛在風險)。
翻下文檔,專注于輕量的框架 Flask ,毫無疑問的選擇了實現起來最簡單的方案:直接用Client Side Session 就行了。
把 Session-Data 序列化并『簽名』后,存儲在客戶端 Cookies 中就搞掂啦。這里同樣需要注意,是『簽名』而不是『加密』。
不過這畢竟是個 Python 框架,人家包多啊,使用 Flask-Session 就可以獲得更多功能,支持將 Session-Data 存儲在Cookies、文件、Redis、數據庫等各種地方。
這個已經接近涼涼的框架表示:不好意思,我們沒有現成的 Session 模塊,想要用?那你自己寫去~
Why doesn't Tornado have session - Stack Exchange
Express 自身是最小化實現,Session 相關的功能由各種中間件來實現,此處主要提兩個中間件。
express-session 是典型的 Server Side Session 實現,依靠強大的包數量支持很多種存儲后端。
比較有意思的是,雖然是 Server Side Session 實現,express-session 依然不放心,給 Cookies 里的 Session-ID 加了一層『簽名』做校驗,可以說非常心細了。
cookie-session 相對就比較普通,就是一個常規的 Client Side Session 實現, Session-Data 在序列化并『簽名』后,存儲在客戶端的 Cookies 內。
包多就是好啊,koa 也有一大堆 Session 相關的中間件,在此就不一一介紹了。
翻了下 Gin 比較常用的中間件是 gin-contrib/sessions ,同時支持 Server / Client Side Session ,也支持多種儲存后端。
這個中間件默認支持在一個瀏覽器會話中,維護多個 Session ,對特定的需求會方便一些。
Echo 的文檔中,默認使用了 gorilla/sessions 這個中間件,也是同時支持 Server / Client Side Session ,可以擴展支持其它存儲后端。
這個中間件也默認支持在一個瀏覽器會話中,維護多個 Session多個 Session ,對特定的需求會方便一些。
按照 Beego 的英文文檔來看,默認只支持 Server Side Session,但是奇怪的是中文文檔上卻又寫著支持的后端引擎包括 Cookie ,可能是文檔寫錯了。
Rails 默認使用 Client Side Session 實現,Session-Data 『加密』并『簽名』后存儲在客戶端瀏覽器的 Cookies 中。也支持配置為 Server Side Session 實現,可以使用多種后端存儲。
在其它許多框架的 Session 實現中,都可以看到一個叫做 Flash 的特殊數據,用于實現輕量的通知反饋等,這個功能應該是 Rails 首創的。
另外,Rails 關于 Session 安全的文檔寫的非常非常細致,強烈建議閱讀。
Sever Side Session 是目前最常見的 Session 實現,以至于不少人會誤以為它是唯一一種 Session 實現。
優點:
劣勢:
Client Side Session 相對小眾一些,但也可以看到許多框架都保留了對它的支持,Rails 甚至在文檔中專門寫了一大段來描述為什么自己默認這么用。
優點:
劣勢:
對于 Server Side Session 來說,絕大部分實現都會選擇將 Session-ID 放在 Cookies 中,在發起請求時自動帶上,這樣也是很符合直覺的做法,但是凡事都有例外,還是有一些特殊的實現。
例如,PHP 默認就支持拋開 Cookies,使用 URL 傳遞 Session-ID,相關的配置為 use_cookies 、 use_only_cookies 字段。
在 Tomcat 6.0 中,也支持類似的功能,但似乎是通過當前會話是否存在 Cookies 來判斷的,也可以通過配置 disableURLRewriting 來關閉。
當然也存在不同的聲音,比如 Django 就明確拒絕這么做,為此還在文檔中專門寫了原因 ,其一是make URLs ugly,其二是不安全。
除此之外,在 WAP 時代,由于早期手機瀏覽器很多不支持 Cookie,這種行為也非常常見。
必須要說的是,這樣確實很不安全,比如可以參考這篇 9 年前的文章:淺談WAP網站安全 - 空虛浪子心 。
其實前面在說到 Spring 的時候就有提到,它支持將 Session-ID 放進 Header 中的X-Auth-Token 字段中。
實際上,只要前后端協調一致,其實放在任何一個字段都是沒問題的。
這種處理方式我只在資料中看到過,大意就是在輸出頁面時,為每一個 Form 都補一個隱藏的 Session-ID 字段。
<form name="anyform" action="/xxx">
<input type="hidden" name="jsessionid" value="ByOK2vjFD43aPnrF6C2HmdnV6QZcEbzWoWiBYEnLerjQ22zWpBng!-1582381342">
<input type="text">
</form>
這種方式存在非常多的弊端,也難怪現在基本上看不到了。
對于 Client Side Session 來說,因為不需要索引,只需要看好 Session-Data 就好,在這件事上,大家也有不一樣的思路。
大部分實現都選擇只對 Session-Data 做『簽名』處理,這樣就算客戶端知道 Session-Data 內部的數據是什么,也無法簡單的篡改。
但是并不是所有的開發者都會注意這些,保不齊會有人存敏感信息在里面,所以部分框架不但做了簽名,還做了『加密』,看也不讓看。
需要注意的是,這樣仍然存在重放攻擊的風險,用戶可以將其替換為舊的合法 Session-Data,此時服務端是無法鑒別出差異的。
另外,Rails 關于 Session 安全的文檔寫的非常非常細致,再次強烈建議閱讀。
Session 的概念并不復雜,洋洋灑灑寫了這么多,其實還是因為討論時遇到的各種誤解,這里我們復盤一下。
不能等同,Session-ID 只是 Server Side Session 實現中,常用的索引信息。
Cookies 經常用于承載 Session 的信息(ID 或 Data),但并不是必然情況。
這是經常被誤解的一點,在此說下我個人的理解。
Token 并不是一個以技術手段定義的詞匯,它的定義來源于『用途』。
但凡用于認證鑒權的 憑據,我認為都可以稱作 Token ,與這個憑據的生成方式無關。
基于此,如果 Session-ID 或 Session-Data 被用作認證,那么我認為這條數據就可以被稱為 Token。
但是需要注意,Session 自身是個虛擬概念,與 Token 毫不相關。
但凡提到 Session,就不可避免的會有人搬出 JWT ,也會有很多人把 Session 和 JWT 擺到對立面來討論,但是這兩個壓根就不在一個維度。
先來看看 JWT 到底是什么,它的全名是 JSON Web Token ,從名字就可以看出,它是特定的一種 Token 生成方式。
JWT 的生成方式大致是這樣:將數據按特定格式進行序列化,標記過期時間,對數據進行『簽名』后編碼為 URL Safe 的 Base64URL 。
誒?好像有點眼熟?這怎么和 Client Side Session 那邊對 Session-Data 的處理那么像呢?
除此之外,這里有一篇蠻有名的文章:Stop using JWT for sessions 也可以一起看看。
看完文章會發現,作者提到的那些將 JWT 用做 Session 實現的人,實際上說的都是** Client Side Session**啊。
那么問題變的比較清晰了:
而那些關于 JWT 與 Session 優劣的爭論,實際上大部分都是在爭論 Sever Side Session 和 Client Side Session 的優劣。
扯了冗長的上萬字,其實就是為了說明一個『定義』的問題,Session 本身并不復雜,只要不把各種簡稱搞混。
很多時候由于大量使用簡稱,或者模糊定義,導致我們對概念本身失去了掌握,也經常讓討論變的低效。
另一方面,不能把自己所熟悉的那些實現當作全部,時不時的看看其它語言或框架,也許能發現許多好玩兒的東西。
最后,如果本文有哪些地方出現了疏漏或錯誤,還請不吝賜教,一起交流進步。
*請認真填寫需求信息,我們會在24小時內與您取得聯系。