本文共 11512 字,大约阅读时间需要 38 分钟。
目录
这篇主要介绍这个类库的作用。
IResponseCookies
接口,添加、删除cookie时加入自定义控制方法,并支持全局cookie属性设置。CookieOptions.IsEssential
的功能,该属性标识当前属性是否必须的或是否绕过ITrackingConsentFeature
的检查。ITrackingConsentFeature
接口,该接口主要是向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
方法的重载版本,我们先从这个无参的UseCookiePolicy
开始介绍,在无参方法中通过UseMiddleware方法添加了一个CookiePolicyMiddleware
中间件,如下代码:
//C#扩展类,可以为已有类库添加扩展方法public static class CookiePolicyAppBuilderExtensions{ //为IApplicationBuilder添加扩展方法 public static IApplicationBuilder UseCookiePolicy(this IApplicationBuilder app) { ... //为http管道添加中间件 return app.UseMiddleware(); } }
下面我们看中间件做了些什么。
通过context.Features.Set
方法,分别设置了,的实现类
public class CookiePolicyMiddleware{ public Task Invoke(HttpContext context) { var feature = context.Features.Get() ?? new ResponseCookiesFeature(context.Features); var wrapper = new ResponseCookiesWrapper(context, Options, feature, _logger); //这个类中我们主要看下面这两个Set context.Features.Set (new CookiesWrapperFeature(wrapper)); //实现gdrp context.Features.Set (wrapper); return _next(context); } //IResponseCookiesFeature实现 private class CookiesWrapperFeature : IResponseCookiesFeature { public CookiesWrapperFeature(ResponseCookiesWrapper wrapper) { Cookies = wrapper; } public IResponseCookies Cookies { get; } }}
大家可能注意到context.Features
这个对象,如果不明白请移步。
通过Set方法将IReponseCookiesFeature
的实现设置成了CookiesWrapperFeature
,CookiesWrapperFeature
有一个参数类型为的构造函数,而这个类实现了两个接口,, ,接下来我们来介绍这两个接口的作用。
通过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
对象的OnAppendCookie
、OnDeleteCookie
的属性设置方法可以实现在每次添加、删除cookie时触发改方法的调用,通常我们可以在这里面做一些全局的过滤,配置什么,最终我们需要设置CookiePolicyOptions.IssueCookie
来确认是否要将改cookie发送到浏览器中。
该接口定义了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(); }}
还记得UseCookiePolicy
方法有一个参数的重载版本吗?没错,这个参数就是类型。
通过配置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 FuncCheckConsentNeeded { get; set; } //当添加cookie时触发该事件 public Action OnAppendCookie { get; set; } //当删除cookie时触发该事件 public Action OnDeleteCookie { get; set; }}
该类是中的一个重要类,我需要的cookie修改监控,gdrp配置等都需要靠该类实现。
IResponseCookies
接口,可以实现添加、删除的功能CookiePolicyOptions
类,我们可以修改cookie的全局配置,并在添加、删除cookie时接受到通知,然后做一些你希望做的任何事情ITrackingConsentFeature
接口,可以实现检索、设置cookie的跟踪配置,改配置主要用于 转载地址:http://aennx.baihongyu.com/