Microsoft.AspNetCore.Authentication.Cookies從入門到精通 (二)


Microsoft.AspNetCore.Authentication.Cookies從入門到精通 (二)

​ 上篇文章我們介紹了Microsoft.AspNetCore.Authentication.Cookies的部分內容,由於篇幅問題,不得不分多篇進行介紹,本篇我們繼續之前的介紹。

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是我們自定義的加密類,這里我們沒有實現ProtectUnprotect方法,只是簡單的做了一個return操作,具體要使用什么加密方式,還是交給需要他的人吧,這里我們只演示一下如何自定義加密類就行了。

Cookie偽加密(序列化/反序列化)

​ 偽加密說的是TicketDataFormat屬性,上文我們介紹了IDataProtector接口中定義的加密解密方法,這里我們介紹ISecureDataFormat接口中也定義的ProtectUnprotect方法,從字面兩上看兩個接口的方法意思一樣,但是從實現代碼看,完全不是一回事,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,這里我們繼承的父類是TicketDataFormatTicketDataFormat是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

總結

  1. 我們介紹了如何通過IDataProtector接口實現自己的加密方式 。
  2. 我們介紹了如何通過ISecureDataFormat<AuthenticationTicket>接口實現自己的序列化方式,並提到了這里可能有坑,當我們自定義序列化方式的時候,需要再構造函數中添加IDataProtector參數,不然可能導致DataProtectionProvider屬性失去意義。
  3. 我們介紹如何通過ITicketStore接口實現類似Session的存儲機制,來減少Cookie在瀏覽器中的存儲以及傳輸量。
  4. 我們介紹了如何通過ICookieManager接口實現Cookie的自定義管理功能。

未完待續......


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM