zoukankan      html  css  js  c++  java
  • 详解Microsoft.AspNetCore.CookiePolicy

    详解Asp.Net Core中的Cookie策略

    这篇主要介绍Microsoft.AspNetCore.CookiePolicy这个类库的作用。

    功能介绍

    1. 实现IResponseCookies接口,添加、删除cookie时加入自定义控制方法,并支持全局cookie属性设置。
    2. 实现CookieOptions.IsEssential的功能,该属性标识当前属性是否必须的或是否绕过ITrackingConsentFeature的检查。
    3. 实现ITrackingConsentFeature接口,该接口主要是向cookie中添加并检索用户确认设置。

    使用Cookie策略

    Asp.Net Core是一个高度组件化的框架,很多功能比如授权,认证,回话状态等都是通过中间件的方式引入的,而Microsoft.AspNetCore.CookiePolicy扩展也是通过中间件的方式引入的。
    

    在项目的Startup中添加如下代码:

    public class Startup
    {
    	public void Configure(IApplicationBuilder app)
        {
        	...
        	//cookie策略提供了UseCookiePolicy的两个重载版本
        	app.UseCookiePolicy();        
            //app.UseCookiePolicy(new CookiePolicyOptions
            //{
            //    CheckConsentNeeded = _ => true,
            //    HttpOnly = Microsoft.AspNetCore.CookiePolicy.HttpOnlyPolicy.None,
            //    MinimumSameSitePolicy = SameSiteMode.Strict,
            //    Secure = CookieSecurePolicy.SameAsRequest,
            //    OnAppendCookie = (context) =>
            //    {
            //        context.IssueCookie = true;
            //    },
            //    OnDeleteCookie = (context) =>
            //    {
            //    }
            //});
        	...
        	app.UseMvc();
        }
    }
    

    从UseCookiePolicy方法入手

    打开CookiePolicyAppBuilderExtensions文件,可以看到有两个UseCookiePolicy方法的重载版本,我们先从这个无参的UseCookiePolicy开始介绍,在无参方法中通过UseMiddleware方法添加了一个CookiePolicyMiddleware中间件,如下代码:

    //C#扩展类,可以为已有类库添加扩展方法
    public static class CookiePolicyAppBuilderExtensions
    {
      	//为IApplicationBuilder添加扩展方法
        public static IApplicationBuilder UseCookiePolicy(this IApplicationBuilder app)
        {
            ...
            //为http管道添加中间件
            return app.UseMiddleware<CookiePolicyMiddleware>();
        }	    
    }
    

    下面我们看中间件CookiePolicyMiddleware做了些什么。详解中间件

    通过context.Features.Set方法,分别设置了IResponseCookiesFeatureITrackingConsentFeature的实现类

    public class CookiePolicyMiddleware
    {
        public Task Invoke(HttpContext context)
        {
            var feature = context.Features.Get<IResponseCookiesFeature>() ?? new ResponseCookiesFeature(context.Features);
            var wrapper = new ResponseCookiesWrapper(context, Options, feature, _logger);
            //这个类中我们主要看下面这两个Set   
            context.Features.Set<IResponseCookiesFeature>(new CookiesWrapperFeature(wrapper));
            //实现gdrp
            context.Features.Set<ITrackingConsentFeature>(wrapper);
    
            return _next(context);
        }
    	
        //IResponseCookiesFeature实现
        private class CookiesWrapperFeature : IResponseCookiesFeature
        {
            public CookiesWrapperFeature(ResponseCookiesWrapper wrapper)
            {
                Cookies = wrapper;
            }
    
            public IResponseCookies Cookies { get; }
        }
    }
    

    大家可能注意到context.Features这个对象,如果不明白请移步详解Features

    通过Set方法将IReponseCookiesFeature的实现设置成了CookiesWrapperFeatureCookiesWrapperFeature有一个参数类型为RespnseCookiesWrapper的构造函数,而这个类实现了两个接口,IResponseCookiesITrackingConsentFeature ,接下来我们来介绍这两个接口的作用。

    实现IResponseCookies接口

    通过IReponseCookies接口ResponseCooiesWraper重写了Append、Delete方法,接下来我们看看它是如何实现的:

    internal class ResponseCookiesWrapper : IResponseCookies, ITrackingConsentFeature
    {
    	private CookiePolicyOptions Options { get; }
    
        public ResponseCookiesWrapper(HttpContext context, CookiePolicyOptions options, IResponseCookiesFeature feature, ILogger logger)
        {
        	...
            Options = options;
            ...
        }
    	public void Append(string key, string value)
        {
        	//如果我们绑定了OnAppendCookie事件,则每次添加cookie的时候都会触发该事件
            if (CheckPolicyRequired() || Options.OnAppendCookie != null)
            {
                Append(key, value, new CookieOptions());
            }
            else
            {
                Cookies.Append(key, value);
            }
        }
    
        public void Append(string key, string value, CookieOptions options)
        {
            if (options == null)
            {
                throw new ArgumentNullException(nameof(options));
            }
        	//使用全局cookie配置修改options
            if (ApplyAppendPolicy(ref key, ref value, options))
            {
                Cookies.Append(key, value, options);
            }
            else
            {
                _logger.CookieSuppressed(key);
            }
        }
        //这个方法判断如果cookie不能被跟踪、设置了相同站点要求、设置了cookie只读、设置cookie安全等任何一个选项则采用Cookie策略的实现
    	private bool CheckPolicyRequired()
        {
            return !CanTrack
                || Options.MinimumSameSitePolicy != SameSiteMode.None
                || Options.HttpOnly != HttpOnlyPolicy.None
                || Options.Secure != CookieSecurePolicy.None;
        }
        private bool ApplyAppendPolicy(ref string key, ref string value, CookieOptions options)
        {	
        	//如果启用了跟踪或这个cookie是必须的
            var issueCookie = CanTrack || options.IsEssential;
            //这里去修改options的配置为全局cookies配置
            ApplyPolicy(key, options);
            //如果我们绑定了添加cookie事件
            if (Options.OnAppendCookie != null)
            {
                var context = new AppendCookieContext(Context, options, key, value)
                {
                    IsConsentNeeded = IsConsentNeeded,
                    HasConsent = HasConsent,
                    IssueCookie = issueCookie,
                };
                //这里执行我们在statup中绑定的方法
                Options.OnAppendCookie(context);
        
                key = context.CookieName;
                value = context.CookieValue;
                //通过设置context.IssueCookie为true可以将cookie写入到浏览器cookie
                issueCookie = context.IssueCookie;
            }
        
            return issueCookie;
        }
        //Delete方法的实现与Append方法相同,具体请查看Append方法中的注释
        public void Delete(string key)
        {
    		...
        }
        
        public void Delete(string key, CookieOptions options)
        {
     		...
        }
        //使用全局cookie配置替换options参数对应的属性
        private void ApplyPolicy(string key, CookieOptions options)
        {
            switch (Options.Secure)
            {
                case CookieSecurePolicy.Always:
                    if (!options.Secure)
                    {
                        options.Secure = true;
                        _logger.CookieUpgradedToSecure(key);
                    }
                    break;
                case CookieSecurePolicy.SameAsRequest:
                    // Never downgrade a cookie
                    if (Context.Request.IsHttps && !options.Secure)
                    {
                        options.Secure = true;
                        _logger.CookieUpgradedToSecure(key);
                    }
                    break;
                case CookieSecurePolicy.None:
                    break;
                default:
                    throw new InvalidOperationException();
            }
            switch (Options.MinimumSameSitePolicy)
            {
                case SameSiteMode.None:
                    break;
                case SameSiteMode.Lax:
                    if (options.SameSite == SameSiteMode.None)
                    {
                        options.SameSite = SameSiteMode.Lax;
                        _logger.CookieSameSiteUpgraded(key, "lax");
                    }
                    break;
                case SameSiteMode.Strict:
                    if (options.SameSite != SameSiteMode.Strict)
                    {
                        options.SameSite = SameSiteMode.Strict;
                        _logger.CookieSameSiteUpgraded(key, "strict");
                    }
                    break;
                default:
                    throw new InvalidOperationException($"Unrecognized {nameof(SameSiteMode)} value {Options.MinimumSameSitePolicy.ToString()}");
            }
            switch (Options.HttpOnly)
            {
                case HttpOnlyPolicy.Always:
                    if (!options.HttpOnly)
                    {
                        options.HttpOnly = true;
                        _logger.CookieUpgradedToHttpOnly(key);
                    }
                    break;
                case HttpOnlyPolicy.None:
                    break;
                default:
                    throw new InvalidOperationException($"Unrecognized {nameof(HttpOnlyPolicy)} value {Options.HttpOnly.ToString()}");
            }
        }
    }
    

    通过代码,我们可以看出,给CookiePolicyOptions对象的OnAppendCookieOnDeleteCookie的属性设置方法可以实现在每次添加、删除cookie时触发改方法的调用,通常我们可以在这里面做一些全局的过滤,配置什么,最终我们需要设置CookiePolicyOptions.IssueCookie来确认是否要将改cookie发送到浏览器中。

    实现ITrackingConsentFeature接口

    该接口定义了cookie在什么情况才会被跟踪,并将在浏览器中设置对应的cookie值。

    public interface ITrackingConsentFeature
    {
    	bool IsConsentNeeded { get; }
    
    	bool HasConsent { get; }
    
    	bool CanTrack { get; }
    
    	void GrantConsent();
    
    	void WithdrawConsent();
    	string CreateConsentCookie();
    }
    
    internal class ResponseCookiesWrapper : IResponseCookies, ITrackingConsentFeature
    {
    	//是否需要用户确认显式确认
    	public bool IsConsentNeeded
        {
            get
            {
                if (!_isConsentNeeded.HasValue)
                {
                    _isConsentNeeded = Options.CheckConsentNeeded == null ? false
                    //从CookiePolicyOptions的CheckConsentNeeded方法获取是否需要用户确认操作
                        : Options.CheckConsentNeeded(Context);
                    _logger.NeedsConsent(_isConsentNeeded.Value);
                }
    
                return _isConsentNeeded.Value;
            }
        }
    	//判断用户之前是否开启了确认
        public bool HasConsent
        {
            get
            {
                if (!_hasConsent.HasValue)
                {
                	//从我们之前设置的cookie中获取确认cookie的值
                    var cookie = Context.Request.Cookies[Options.ConsentCookie.Name];
                    _hasConsent = string.Equals(cookie, ConsentValue, StringComparison.Ordinal);
                    _logger.HasConsent(_hasConsent.Value);
                }
    
                return _hasConsent.Value;
            }
        }
    	//能否跟踪,如果浏览器没有开启用户确认或者浏览器已经确认过了则允许跟踪
        public bool CanTrack => !IsConsentNeeded || HasConsent;
    	
    	//向cookie中写入跟踪启用标识
        public void GrantConsent()
        {
            if (!HasConsent && !Context.Response.HasStarted)
            {
                var cookieOptions = Options.ConsentCookie.Build(Context);
                
                Append(Options.ConsentCookie.Name, ConsentValue, cookieOptions);
                _logger.ConsentGranted();
            }
            _hasConsent = true;
        }
    	//撤销之前cookie中写入的跟踪启用标识
        public void WithdrawConsent()
        {
            if (HasConsent && !Context.Response.HasStarted)
            {
                var cookieOptions = Options.ConsentCookie.Build(Context);
               	//删除之前的cookie确认信息
                Delete(Options.ConsentCookie.Name, cookieOptions);
                _logger.ConsentWithdrawn();
            }
            _hasConsent = false;
        }
    	//返回跟踪cookie的字符串值
        public string CreateConsentCookie()
        {
            var key = Options.ConsentCookie.Name;
            var value = ConsentValue;
            var options = Options.ConsentCookie.Build(Context);
            ApplyAppendPolicy(ref key, ref value, options);
    
            var setCookieHeaderValue = new Net.Http.Headers.SetCookieHeaderValue(
                Uri.EscapeDataString(key),
                Uri.EscapeDataString(value))
                {
                    Domain = options.Domain,
                    Path = options.Path,
                    Expires = options.Expires,
                    MaxAge = options.MaxAge,
                    Secure = options.Secure,
                    SameSite = (Net.Http.Headers.SameSiteMode)options.SameSite,
                    HttpOnly = options.HttpOnly
                };
    
            return setCookieHeaderValue.ToString();
        }
    }
    

    CookiePolicyOptions类的功能

    还记得UseCookiePolicy方法有一个参数的重载版本吗?没错,这个参数就是CookiePolicyOptions类型。

    通过配置CookiePolicyOptions我们可以设置一些全局的cookie约定信息,并允在每次添加、删除cookie时触发指定的方法已完成一些特殊的cookie配置。CookiePolicyOptions代码如下:

    public class CookiePolicyOptions
    {
        //设置全局cookie通用配置
        public SameSiteMode MinimumSameSitePolicy { get; set; } = SameSiteMode.Lax;
        public HttpOnlyPolicy HttpOnly { get; set; } = HttpOnlyPolicy.None;
        public CookieSecurePolicy Secure { get; set; } = CookieSecurePolicy.None;
    	//设置gdpr在浏览器中保存的cookie信息
        public CookieBuilder ConsentCookie { get; set; } = new CookieBuilder()
        {
            Name = ".AspNet.Consent",
            Expiration = TimeSpan.FromDays(365),
            IsEssential = true,
        };
        //是否需要用户确认才能将部分cookie发送到客户端
        public Func<HttpContext, bool> CheckConsentNeeded { get; set; }
    
    	//当添加cookie时触发该事件
        public Action<AppendCookieContext> OnAppendCookie { get; set; }
    
    	//当删除cookie时触发该事件
        public Action<DeleteCookieContext> OnDeleteCookie { get; set; }
    }
    

    该类是Microsoft.AspNetCore.CookiePolicy中的一个重要类,我需要的cookie修改监控,gdrp配置等都需要靠该类实现。

    总结

    1. cookie策略通过继承IResponseCookies接口,可以实现添加、删除的功能
    2. 通过CookiePolicyOptions类,我们可以修改cookie的全局配置,并在添加、删除cookie时接受到通知,然后做一些你希望做的任何事情
    3. cookie策略通过继承ITrackingConsentFeature接口,可以实现检索、设置cookie的跟踪配置,改配置主要用于GDPR
  • 相关阅读:
    程序员代码面试指南:IT名企算法与数据结构题目最优解
    经典排序算法
    Log4j输出格式控制--log4j的PatternLayout参数含义
    常用数据库4 mongodb
    常用数据库2 sqlite及SQL注入
    面试常问-数据库索引实现原理
    自定义web框架
    HTML|CSS之布局相关总结
    C++模板类练习题
    C++中的运算符重载练习题
  • 原文地址:https://www.cnblogs.com/guodf/p/9624238.html
Copyright © 2011-2022 走看看