zoukankan      html  css  js  c++  java
  • 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的自定义管理功能。

    未完待续......

  • 相关阅读:
    multiprocessing总结
    CPython在CPU密集型应用下的并发
    多线程交互
    线程等待与守护线程
    Python多线程(1)
    一个简单的单线程异步服务器
    多线程与多进程的实现
    socket的功能分割到不同函数
    数据处理项目Postmortem
    M2 终审
  • 原文地址:https://www.cnblogs.com/guodf/p/9716262.html
Copyright © 2011-2022 走看看