Microsoft.AspNetCore.Authentication.Cookies從入門到精通 (二)
目錄
- Microsoft.AspNetCore.Authentication.Cookies從入門到精通 (二)
Microsoft.AspNetCore.Authentication.Cookies從入門到精通 (二)
上篇文章我們介紹了Microsoft.AspNetCore.Authentication.Cookies
的部分內容,由於篇幅問題,不得不分多篇進行介紹,本篇我們繼續之前的介紹。
ofollow,noindex" target="_blank"> Demo原始碼地址
Cookie加密
在之前的Demo中,瀏覽器Cookie中的.AspNetCore.Cookies是一個加密字串,預設Asp.Net Core是對該值做過加密處理的,至於加密方式我們不得而知(翻看原始碼是可以找到一些蛛絲馬跡的)
, 如果我們要修改加密方式只有一種辦法就是通過DataProtectionProvider
屬性設定我們自己的實現:
Startup.cs
public void ConfigureServices(IServiceCollection services) { ...... services.AddAuthentication() .AddCookie(options=>{ options.SlidingExpiration = true; options.ExpireTimeSpan = TimeSpan.FromMinutes(10); options.Cookie.Expiration = TimeSpan.FromDays(365); //設定我們自己的資料加密實現 options.DataProtectionProvider = new MyDataProtectionProvider(); }) .AddCookie("Admin","Admin",options=>{ options.SlidingExpiration = true; options.ExpireTimeSpan = TimeSpan.FromDays(1); options.Cookie.Expiration = TimeSpan.FromDays(365); }); ...... }
MyDataProtectionProvider
public class MyDataProtector : IDataProtector { private string _prupose; public MyDataProtector() : this(null) { } public MyDataProtector(string purpose) { _prupose = purpose; } public IDataProtector CreateProtector(string purpose) { _prupose = purpose; //我們也可以在這裡返回多個例項 //return new MyDataProtector(purpose); return this; } public byte[] Protect(byte[] plaintext) { return plaintext } public byte[] Unprotect(byte[] protectedData) { return protectedData; } }
MyDataProtector是我們自定義的加密類,這裡我們沒有實現Protect
和Unprotect
方法,只是簡單的做了一個return
操作,具體要使用什麼加密方式,還是交給需要他的人吧,這裡我們只演示一下如何自定義加密類就行了。
Cookie偽加密(序列化/反序列化)
偽加密說的是TicketDataFormat
屬性,上文我們介紹了IDataProtector
介面中定義的加密解密方法,這裡我們介紹ISecureDataFormat
介面中也定義的Protect
和Unprotect
方法,從字面兩上看兩個介面的方法意思一樣,但是從實現程式碼看,完全不是一回事,ISecureDataFormat
介面處理的並不是加密,準確的說它的作用是序列化與反序列化,我們可以看一下Asp.Net Coro中的預設實現SecureDataFormat
。
Startup.cs
public void ConfigureServices(IServiceCollection services) { ...... services.AddAuthentication() .AddCookie(options=>{ options.SlidingExpiration = true; options.ExpireTimeSpan = TimeSpan.FromMinutes(10); options.Cookie.Expiration = TimeSpan.FromDays(365); options.DataProtectionProvider = new MyDataProtector(); //使用我們自己的序列化實現,這裡的程式碼看起來有點彆扭,上一行我們已經設定MyDataProtector例項,這裡又設定一次,原因是Asp.Net Core框架本身在這裡設計的有點問題,如果通過依賴注入的方式就可以避免這種讓人費解的寫法。 options.TicketDataFormat =new MyTicketDataFormat(new MyDataProtector()); }) .AddCookie("Admin","Admin",options=>{ options.SlidingExpiration = true; options.ExpireTimeSpan = TimeSpan.FromDays(1); options.Cookie.Expiration = TimeSpan.FromDays(365); }); ...... }
MyTicketDataFormat
public class MyTicketDataFormat : TicketDataFormat { public MyTicketDataFormat(IDataProtector dataProtector):base(dataProtector) {} public new string Protect(AuthenticationTicket data) { return base.Protect(data); } public new string Protect(AuthenticationTicket data, string purpose) { return base.Protect(data, purpose); } public new AuthenticationTicket Unprotect(string protectedText) { return base.Unprotect(protectedText); } public new AuthenticationTicket Unprotect(string protectedText, string purpose) { return base.Unprotect(protectedText, purpose); } }
我們定義了一個序列化類MyTicketDataFormat
,這裡我們繼承的父類是TicketDataFormat
,TicketDataFormat
是Asp.Net Core的預設實現,如果我們沒有設定options.TicketDataFormat
,預設使用的序列化類就是TicketDataFormat
,如果在真實的專案中你需要自定義序列化時,你應該實現ISecureDataFormat<AuthenticationTicket>
介面而不是像我這樣偷懶,我這裡純粹是為了演示,我們看一下Asp.Net Core是如何實現SecureDataFormat
類的(SecureDataFormat是TicketDataFormat的父類)
。這裡有一點需要注意,如果你實現了ISecureDataFormat<AuthenticationTicket>
介面別忘了在建構函式中注入IDataProtector
,如果你真的忘記了,那麼你上文設定的options.DataProtectionProvider = new MyDataProtectionProvider();
將不會起任何作用(我認為這裡是一個大坑)
。
減少Cookie中Value的大小
預設情況下Asp.Net Core的實現是將AuthenticationTicket
物件序列化並加密儲存在Cookie中,那麼這會有一個隱患,隨著AuthenticationTicket
物件儲存資料越來越多,Cookie也會越來越大,但是瀏覽器只給了我們4K的儲存空間,有可能在某一天你的Cookie就裝不下你的資料流,所以減少Cookie的儲存是我們或許終將考慮的一個問題;從另一個方面看,減少Cookie的資料量也利於網路的傳輸。
那麼我們怎麼減小Cookie的儲存空間呢?!我們不妨借鑑一下Asp.Net Core中Session的實現原理,將Cookie資訊,寫入記憶體、快取伺服器(redis)中,然後通過唯一標識與它對應,這樣便可以極大的減少Cookie的儲存傳輸體積。
Startup.cs
public void ConfigureServices(IServiceCollection services) { ...... services.AddAuthentication() .AddCookie(options=>{ options.SlidingExpiration = true; options.ExpireTimeSpan = TimeSpan.FromMinutes(10); options.Cookie.Expiration = TimeSpan.FromDays(365); options.DataProtectionProvider = new MyDataProtectionProvider(); options.TicketDataFormat =new MyTicketDataFormat(new MyDataProtector()); //新增Cookie儲存實現 options.SessionStore = new MyTicketStore(); }) .AddCookie("Admin","Admin",options=>{ options.SlidingExpiration = true; options.ExpireTimeSpan = TimeSpan.FromDays(1); options.Cookie.Expiration = TimeSpan.FromDays(365); }); ...... }
MyTicketStore
public class MyTicketStore : ITicketStore { private Dictionary<string, AuthenticationTicket> _cache = new Dictionary<string, AuthenticationTicket>(); public Task RemoveAsync(string key) { _cache.Remove(key); return Task.FromResult(0); } public Task RenewAsync(string key, AuthenticationTicket ticket) { _cache[key]= ticket; return Task.FromResult(0); } public Task<AuthenticationTicket> RetrieveAsync(string key) { _cache.TryGetValue(key, out AuthenticationTicket ticket); return Task.FromResult(ticket); } public Task<string> StoreAsync(AuthenticationTicket ticket) { var key = Guid.NewGuid().ToString("n"); _cache.TryAdd(key, ticket); return Task.FromResult(key); } }
上面的程式碼我們通過實現ITicketStore
的自定義型別MyTicketStore
來自定義AuthenticationTicket
資料的儲存,在MyTicketStore
中我們通過Dictionary<string, AuthenticationTicket>
來儲存認證資料,這裡其實應該使用記憶體,或者外部快取系統,例如:用Redis來儲存認證資料。
自定義Cookie管理功能
我們這裡說的Cookie管理指的是ICookieManager
介面,該介面主要是用來新增,刪除,獲取Cookie的資訊,也就是Microsoft.AspNetCore.Authentication.Cookies
真正將Cookie寫入http頭,從http頭獲取Cookie的入口。
Startup.cs
public void ConfigureServices(IServiceCollection services) { ...... services.AddAuthentication() .AddCookie(options=>{ options.SlidingExpiration = true; options.ExpireTimeSpan = TimeSpan.FromMinutes(10); options.Cookie.Expiration = TimeSpan.FromDays(365); options.DataProtectionProvider = new MyDataProtectionProvider(); options.TicketDataFormat =new MyTicketDataFormat(new MyDataProtector()); options.SessionStore = new MyTicketStore(); options.CookieManager = new MyCookieManager(); }) .AddCookie("Admin","Admin",options=>{ options.SlidingExpiration = true; options.ExpireTimeSpan = TimeSpan.FromDays(1); options.Cookie.Expiration = TimeSpan.FromDays(365); }); ...... }
MyCookieManager
public class MyCookieManager : ICookieManager { public void AppendResponseCookie(HttpContext context, string key, string value, CookieOptions options) { context.Response.Cookies.Append(key, value, options); } public void DeleteCookie(HttpContext context, string key, CookieOptions options) { context.Response.Cookies.Delete(key, options); } public string GetRequestCookie(HttpContext context, string key) { return context.Request.Cookies[key]; } }
我們這裡的實現很簡單,僅僅呼叫了一下HttpContext上的物件來操作Cookie,在實現上可能會存在一些問題,更安全的實現請看ChunkingCookieManager 。
總結
-
我們介紹瞭如何通過
IDataProtector
介面實現自己的加密方式 。 -
我們介紹瞭如何通過
ISecureDataFormat<AuthenticationTicket>
介面實現自己的序列化方式,並提到了這裡可能有坑,當我們自定義序列化方式的時候,需要再建構函式中新增IDataProtector
引數,不然可能導致DataProtectionProvider
屬性失去意義。 -
我們介紹如何通過
ITicketStore
介面實現類似Session的儲存機制,來減少Cookie在瀏覽器中的儲存以及傳輸量。 -
我們介紹瞭如何通過
ICookieManager
介面實現Cookie的自定義管理功能。