詳解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


免責聲明!

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



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