aspnet core 2.1中使用jwt從原理到精通二


目錄

  1. 在aspnet core中,自定義jwt管道驗證;
  2. 在aspnet core中,自定義策略驗證CommonAuthorizeHandler : AuthorizationHandler<CommonAuthorize>

學有所得

  1. 如何在項目中實現自定義的復雜jwt驗證
  2. 如何通過中間件形式和自定義策略形式實現自定義jwt驗證

在aspnet core中,自定義jwt管道驗證

有了上一節的內容作為基礎,那這點也是非常容易的,關鍵點在中間件,只是把上一級在測試類中的自定義驗證放到中間件中來即可,

不過需要注意:中間件 的位置很重要,只有它后面的管道才會收到影響;

那我們先建一個自定義中間件類:(中間件的詳細內容這里就不講了,大家可以參考官網和其他博文)

/// <summary>
    /// 自定義授權中間件
    /// </summary>
    public class JwtCustomerAuthorizeMiddleware
    {
        private readonly RequestDelegate next;

        public JwtCustomerAuthorizeMiddleware(RequestDelegate next, string secret, List<string> anonymousPathList)
        {
            #region   設置自定義jwt 的秘鑰
            if(!string.IsNullOrEmpty(secret))
            {
                TokenContext.securityKey = secret;
            }
            #endregion
            this.next = next;
            UserContext.AllowAnonymousPathList.AddRange(anonymousPathList);
        }

        public async Task Invoke(HttpContext context, UserContext userContext,IOptions<JwtOption> optionContainer)
        {
            if (userContext.IsAllowAnonymous(context.Request.Path))
            {
                await next(context);
                return;
            }

            var option = optionContainer.Value;

            #region 身份驗證,並設置用戶Ruser值
 
            var result = context.Request.Headers.TryGetValue("Authorization", out StringValues authStr);
            if (!result || string.IsNullOrEmpty(authStr.ToString()))
            {
                throw new UnauthorizedAccessException("未授權");
            }
            result = TokenContext.Validate(authStr.ToString().Substring("Bearer ".Length).Trim(), payLoad =>
            {
                var success = true;
                //可以添加一些自定義驗證,用法參照測試用例
                //驗證是否包含aud 並等於 roberAudience
                success = success && payLoad["aud"]?.ToString() == option.Audience;
                if (success)
                {
                    //設置Ruse值,把user信息放在payLoad中,(在獲取jwt的時候把當前用戶存放在payLoad的ruser鍵中)
                    //如果用戶信息比較多,建議放在緩存中,payLoad中存放緩存的Key值
                    userContext.TryInit(payLoad["ruser"]?.ToString());
                }
                return success;
            });
            if (!result)
            {
                throw new UnauthorizedAccessException("未授權");
            }

            #endregion
            #region 權限驗證
            if (!userContext.Authorize(context.Request.Path))
            {
                throw new UnauthorizedAccessException("未授權");
            }
            #endregion

            await next(context);
        }
    }

上面這個中間件中有個UserContext上線文,這個類主要管理當前用戶信息和權限,其他信息暫時不管;我們先看一下這個中間件的驗證流程如何:

該中間件主要是針對訪問的路徑進行驗證,當然你也可以針對其他信息進行驗證,比如(控制器名稱,動作名稱,等)

  1. 檢查當前url是否可以匿名訪問,如果可以就直接通過,不做驗證了;如果不是可以匿名訪問的路徑,那就繼續
  2. 獲取當前http頭部攜帶的jwt(存放在頭部的 Authorization中);
  3. 使用上一節的講的TokenContext做必須的驗證和自定義復雜驗證;
  4. 獲取當前訪問用戶信息,我們把用戶的基本信息放在payLoad["ruser"]中,請看代碼如何操作
  5. 到這里為止,都是做的身份驗證,表明你是一個有身份的的人;接下來是做權限驗證,你是一個有身份的人,並不代表你是一個隨便到處訪問的人;你能訪問哪些url或者action,就要得到權限驗證的認可
  6. 我們把權限驗證放到 userContext.Authorize方法中(這里怎么操作,這里就不深入講解,基本原理是從數據庫或者緩存中獲取當前用戶對應的權限列表,也就是url列表,進行對比);

自定義中間件使用jwt驗證就這些內容,是不是感覺很清晰,很簡單,有木有;

中間已經完成了,那接下來我們來使用它,我們再startup中的Configure方法中添加如下代碼

app.UseMiddleware<JwtCustomerAuthorizeMiddleware>(Configuration["JwtOption:SecurityKey"], new List<string>() { "/api/values/getjwt","/" });

當然上面可匿名訪問的url也可以定義在appsetting.json文件中,可以自行嘗試

如何通過自定義策略形式實現自定義jwt驗證

創建自定義策略的詳細介紹可以參考官網,這里就不詳細介紹,

首先我們上代碼,創建自定義策略非常重要的兩個類,如下:

 public class CommonAuthorizeHandler : AuthorizationHandler<CommonAuthorize>
    {
        /// <summary>
        /// 常用自定義驗證策略,模仿自定義中間件JwtCustomerauthorizeMiddleware的驗證范圍
        /// </summary>
        /// <param name="context"></param>
        /// <param name="requirement"></param>
        /// <returns></returns>
        protected override Task HandleRequirementAsync(AuthorizationHandlerContext context, CommonAuthorize requirement)
        {
            var httpContext = (context.Resource as AuthorizationFilterContext).HttpContext;
            var userContext = httpContext.RequestServices.GetService(typeof(UserContext)) as UserContext;

            var jwtOption = (httpContext.RequestServices.GetService(typeof(IOptions<JwtOption>)) as IOptions<JwtOption>).Value;

            #region 身份驗證,並設置用戶Ruser值

            var result = httpContext.Request.Headers.TryGetValue("Authorization", out StringValues authStr);
            if (!result || string.IsNullOrEmpty(authStr.ToString()))
            {
                return Task.CompletedTask;
            }
            result = TokenContext.Validate(authStr.ToString().Substring("Bearer ".Length).Trim(), payLoad =>
            {
                var success = true;
                //可以添加一些自定義驗證,用法參照測試用例
                //驗證是否包含aud 並等於 roberAudience
                success = success && payLoad["aud"]?.ToString() == jwtOption.Audience;
                if (success)
                {
                    //設置Ruse值,把user信息放在payLoad中,(在獲取jwt的時候把當前用戶存放在payLoad的ruser鍵中)
                    //如果用戶信息比較多,建議放在緩存中,payLoad中存放緩存的Key值
                    userContext.TryInit(payLoad["ruser"]?.ToString());
                }
                return success;
            });
            if (!result)
            {
                return Task.CompletedTask;
            }

            #endregion
            #region 權限驗證
            if (!userContext.Authorize(httpContext.Request.Path))
            {
                return Task.CompletedTask;
            }
            #endregion

            context.Succeed(requirement);
            return Task.CompletedTask;
        }
    }
    public class CommonAuthorize: IAuthorizationRequirement
    {

    }

其中兩個重要的類是哪兩個呢?他們的作用又是什么呢?

  1. CommonAuthorize: IAuthorizationRequirement,至於取什么名字,自己定義,但必須繼承IAuthorizationRequirement,這類主要是承載一些初始化值,讓后傳遞到Handler中去,給驗證做邏輯運算提供一些可靠的信息;我這里是空的;自己根據自身情況自己定義適當的屬性作為初始數據的承載容器;
  2. CommonAuthorizeHandler : AuthorizationHandler<CommonAuthorize>這個是重點,承載了驗證的邏輯運算
    需要重寫override Task HandleRequirementAsync方法,所有的邏輯都在該方法中,他的主要邏輯和上面的自定義中間件很相似,只少了上面的第一步;驗證流程如下:
    1. 獲取當前http頭部攜帶的jwt(存放在頭部的 Authorization中);
    2. 使用上一節的講的TokenContext做必須的驗證和自定義復雜驗證;
    3. 獲取當前訪問用戶信息,我們把用戶的基本信息放在payLoad["ruser"中,請看代碼如何操作
    4. 到這里為止,都是做的身份驗證,表明你是一個有身份的的人;接下來是做權限驗證,你是一個有身份的人,並不代表你是一個隨便到處訪問的人;你能訪問哪些url或者action,就要得到權限驗證的認可
    5. 我們把權限驗證放到 userContext.Authorize方法中(這里怎么操作,這里就不深入講解,基本原理是從數據庫或者緩存中獲取當前用戶對應的權限列表,也就是url列表,進行對比);
      context.Succeed(requirement);是驗證成功,如果沒有這個,就默認驗證失敗
      因為UserContext把負責了權限驗證,所以不會把流程搞得感覺很亂,並且可以重用,至於用那種形式驗證也很容易切換
  3. 是不是很簡單,和自定義管道驗證的的代碼幾乎一模一樣,

如何使用自定義定義策略呢?

  1. 在startup類中的ConfigureServices中加入如下代碼:
    services.AddAuthorization(option =>
                {
                    
    
                    #region 自定義驗證策略
                    option.AddPolicy("common", policy => policy.Requirements.Add(new CommonAuthorize()));
                    #endregion
    
    
                }).AddAuthentication(option =>
                {
                    option.DefaultScheme = JwtBearerDefaults.AuthenticationScheme;
                }).AddJwtBearer(option =>
                {
                    if (!string.IsNullOrEmpty(config["JwtOption:SecurityKey"]))
                    {
                        TokenContext.securityKey = config["JwtOption:SecurityKey"];
                    }
                    //設置需要驗證的項目
                    option.TokenValidationParameters = new TokenValidationParameters
                    {
                       
                        IssuerSigningKey = new SymmetricSecurityKey(Encoding.UTF8.GetBytes(TokenContext.securityKey))//拿到SecurityKey
                    };
                });
    
                //自定義策略IOC添加
                
                services.AddSingleton<IAuthorizationHandler, CommonAuthorizeHandler>();

    以上代碼主要分3個部分
    1、添加上面自定義的策略,並取名;
    2、設置秘鑰,這個秘鑰就是上一節中生成jwt的秘鑰,必須要要一樣,否則是簽名不正確
    3、注入上面建立的一個重要類CommonAuthorizeHandler,如上面代碼

  2. 在startup類中的Configure中添加 app.UseAuthentication();
  3. 在需要驗證的Controller或者Action中加上[Authorize(Policy = "common")]屬性,看下圖:

    到此為止你就可以使用自定義策略的驗證了;

 

使用管道和自定義策略兩種形式進行驗證有什么區別呢?

從效果上看都是一樣的,稍微有點區別

  1. 使用管道的方式,感覺方便點,清晰點
  2. 使用自定義策略的方式,效率稍微高一點,畢竟不是所有的請求都會進行是否可以匿名訪問運算和建立管道的消耗,只有加入Authorize屬性的Controller和Action的才會進入;當然這點損耗可以忽略不計,看自己的喜好;

至於你喜歡那種,就使用哪種吧,性能可以忽略不計;

不管使用哪種方式使用jwt作為身份和權限驗證是不是很簡單,關鍵這里也把權限驗證的邏輯抽出來了,這樣代碼就更清晰明了了;

至於Authorize的屬性形式,還有很多其他的策略,比如用戶、申明,角色等,可查看官網https://docs.microsoft.com/zh-cn/aspnet/core/security/authorization/?view=aspnetcore-2.0

下一章將講解 用戶,申明 ,角色的驗證,並這些怎么在自定義的驗證中實現,以便大家對他有個清晰的對比

 


免責聲明!

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



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