详解Microsoft.AspNetCore.CookiePolicy-阿里云开发者社区

开发者社区> 开发与运维> 正文

详解Microsoft.AspNetCore.CookiePolicy

简介: 目录 详解Asp.Net Core中的Cookie策略 功能介绍 使用Cookie策略 从UseCookiePolicy方法入手 实现IResponseCookies接口 实现ITrackingConsentFeature接口 CookiePolicyOptions类的功能 总结 详解Asp.Net Core中的Cookie策略 这篇主要介绍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

版权声明:本文内容由阿里云实名注册用户自发贡献,版权归原作者所有,阿里云开发者社区不拥有其著作权,亦不承担相应法律责任。具体规则请查看《阿里云开发者社区用户服务协议》和《阿里云开发者社区知识产权保护指引》。如果您发现本社区中有涉嫌抄袭的内容,填写侵权投诉表单进行举报,一经查实,本社区将立刻删除涉嫌侵权内容。

分享:
开发与运维
使用钉钉扫一扫加入圈子
+ 订阅

集结各类场景实战经验,助你开发运维畅行无忧

其他文章