ASP.NET Core 3.0 一個 jwt 的輕量角色/用戶、單個API控制的授權認證庫

来源:https://www.cnblogs.com/whuanle/archive/2019/10/26/11743406.html
-Advertisement-
Play Games

[TOC] 說明 ASP.NET Core 3.0 一個 jwt 的輕量角色/用戶、單個API控制的授權認證庫 最近得空,重新做一個角色授權庫,而之前做了一個角色授權庫,是利用微軟的預設介面做的,查閱了很多文檔,因為理解不夠,所以最終做出了有問題。 之前的舊版本 https://github.com ...


目錄

說明

ASP.NET Core 3.0 一個 jwt 的輕量角色/用戶、單個API控制的授權認證庫

最近得空,重新做一個角色授權庫,而之前做了一個角色授權庫,是利用微軟的預設介面做的,查閱了很多文檔,因為理解不夠,所以最終做出了有問題。

之前的舊版本 https://github.com/whuanle/CZGL.Auth/tree/1.0.0

如果要使用微軟的預設介面,我個人認為過於繁雜,而且對於這部分的資料較少。。。

使用預設介面實現授權認證,可以參考我另一篇文章

ASP.NET Core 使用 JWT 自定義角色/策略授權需要實現的介面

得益於大笨熊哥的引導,利用放假時間重新做了一個,利用微軟本身的授權認證,在此基礎上做拓展。特點是使用十分簡便,無需過多配置;因為本身沒有“造輪子”,所以如果需要改造,也十分簡單。

此庫更新到 .Net Core 3.0 了,如果需要在 2.2X 上使用,可以到倉庫下載項目,然後把 Nuget 包換成 2.2 的。

感謝大笨熊哥的指導。

項目倉庫地址 https://github.com/whuanle/CZGL.Auth

一、定義角色、API、用戶

隨便新建一個網站或API項目,例如 MyAuth。

Nuget 里搜索 CZGL.Auth,按照 2.0.1 版本,或者使用 Package Manager 命令

Install-Package CZGL.Auth -Version 2.0.1

CZGL.Auth 設計思路是,網站可以存在多個角色、多個用戶、多個API,

一個角色擁有一些 API,可以添加或刪除角色或修改角色所有權訪問的 API;

一個用戶可以同時屬於幾個角色。

第一步要考慮網站的角色、用戶、API設計,

CZGL.Auth 把這些信息存儲到記憶體中,一個用戶擁有那幾個角色、一個角色具有哪些API的訪問許可權。

角色跟 API 是對應關係,用戶跟角色是多對多關係。

新建一個類 RoleService.cs ,引入 using CZGL.Auth.Services;,RoleService 繼承 ManaRole。

通過以下介面操作角色許可權信息

        protected bool AddRole(RoleModel role);
        protected bool AddUser(UserModel user);
        protected bool RemoveRole(string roleName);
        protected bool RemoveUser(string userName);

很明顯,添加/移除一個角色,添加/移除一個用戶

假如有 A、B、C 三個角色,
有 /A、/B、/C、/AB、/AC、/BC、/ABC 共7個API,設定許可權

A 可以訪問 A、AB、AC、ABC

B 可以訪問 B、AB、BC、ABC

C 可以訪問 C、AC、BC、ABC

這裡採用模擬數據的方法,不從資料庫裡面載入實際數據。

在 RoleService 裡面增加一個方法

        /// <summary>
        /// 用於載入角色和API
        /// </summary>
        public void UpdateRole()
        {
            List<RoleModel> roles = new List<RoleModel>
            {
                new RoleModel
                {
                    RoleName="A",
                    Apis=new List<OneApiModel>
                    {
                        new OneApiModel
                        {
                            ApiName="A",
                            ApiUrl="/A"
                        },
                        new OneApiModel
                        {
                            ApiName="AB",
                            ApiUrl="/AB"
                        },
                        new OneApiModel
                        {
                            ApiName="AC",
                            ApiUrl="/AC"
                        },
                        new OneApiModel
                        {
                            ApiName="ABC",
                            ApiUrl="/ABC"
                        }
                    }
                },
                new RoleModel
                {
                    RoleName="B",
                    Apis=new List<OneApiModel>
                    {
                        new OneApiModel
                        {
                            ApiName="B",
                            ApiUrl="/B"
                        },
                        new OneApiModel
                        {
                            ApiName="AB",
                            ApiUrl="/AB"
                        },
                        new OneApiModel
                        {
                            ApiName="BC",
                            ApiUrl="/BC"
                        },
                        new OneApiModel
                        {
                            ApiName="ABC",
                            ApiUrl="/ABC"
                        }
                    }
                },
                new RoleModel
                {
                    RoleName="A",
                    Apis=new List<OneApiModel>
                    {
                        new OneApiModel
                        {
                            ApiName="A",
                            ApiUrl="/A"
                        },
                        new OneApiModel
                        {
                            ApiName="AB",
                            ApiUrl="/AB"
                        },
                        new OneApiModel
                        {
                            ApiName="AC",
                            ApiUrl="/AC"
                        },
                        new OneApiModel
                        {
                            ApiName="ABC",
                            ApiUrl="/ABC"
                        }
                    }
                }
            };
            foreach (var item in roles)
            {
                AddRole(item);
            }

        }

有了角色和對應的API信息,就要添加用戶了,

假設有 aa、bb、cc 三個用戶,密碼都是 123456,aa 屬於 A 角色, bb 屬於 B角色...

        public void UpdateUser()
        {
            AddUser(new UserModel { UserName = "aa", BeRoles = new List<string> { "A" } });
            AddUser(new UserModel { UserName = "bb", BeRoles = new List<string> { "B" } });
            AddUser(new UserModel { UserName = "cc", BeRoles = new List<string> { "C" } });
        }

為了能夠把角色和用戶載入進 CZGL.Auth ,你需要在程式啟動時,例如在 Program 里,使用

            RoleService roleService = new RoleService();
            roleService.UpdateRole();
            roleService.UpdateUser();

二、添加自定義事件

授權是,可能會有各種情況,你可以添加自定義事件記錄下用戶訪問的授權信息、影響授權結果。

引用 using CZGL.Auth.Interface;

添加一個類 RoleEvents 繼承 IRoleEventsHadner

    public class RoleEvents : IRoleEventsHadner
    {
        public async Task Start(HttpContext httpContext)
        {
            await Task.CompletedTask;
        }
        public void TokenEbnormal(object eventsInfo)
        {
        }
        public void TokenIssued(object eventsInfo)
        {
        }
        public void NoPermissions(object eventsInfo)
        {
        }
        public void Success(object eventsInfo)
        {
        }
        public async Task End(HttpContext httpContext)
        {
            await Task.CompletedTask;
        }
    }

在 CZGL.Auth 開始驗證授權前調用 Start,結束時調用 End,傳入傳參數是 HttpContext 類型,你可以在裡面添加自定義授權的信息,在裡面可以影響請求管道。

其他幾個方法含義如下:

TokenEbnormal 客戶端攜帶的 Token 不是有效的 Jwt 令牌,將不能被解析

TokenIssued 令牌解碼後,issuer 或 audience不正確

NoPermissions 無權訪問此 API

在授權認證的各個階段將會調用上面的方法。

三、註入授權服務和中間件

使用 CZGL.Auth ,你需要註入以下兩個服務

            services.AddRoleService(authOptions);
            services.AddSingleton<IRoleEventsHadner, RoleEvents>();

AddRoleService 是註入授權服務,AddSingleton 註入你的事件。

AddRoleService 需要一個 AuthConfigModel 類型作參數。

你可以這樣配置

            var authOptions = new AuthBuilder()
                .Security("aaaafsfsfdrhdhrejtrjrt", "ASPNETCORE", "ASPNETCORE")
                .Jump("accoun/login", "account/error", false, false)
                .Time(TimeSpan.FromMinutes(20))
                .InfoScheme(new CZGL.Auth.Models.AuthenticateScheme
                {
                    TokenEbnormal = "Login authentication failed!",
                    TokenIssued = "Login authentication failed!",
                    NoPermissions = "Login authentication failed!"
                }).Build();
            services.AddRoleService(authOptions);

            services.AddSingleton<IRoleEventsHadner, RoleEvents>();

Security 配置密鑰相關,參數分別是密鑰字元串、頒發者、訂閱者。

Jump 配置授權失敗時,跳轉地址。參數分別是未授權時跳轉、授權無效跳轉,後面兩個 bool 可以設置跳轉或跳轉。

Time 配置 Token 有效期。

InfoScheme 授權失敗提示信息,例如

上圖的是時間過期的提示消息,用戶請求API失敗時返回 401 狀態碼,Header 會攜帶提示消息,CZGL.Auth 裡面設置了三種情況下,自定義頭部:

TokenEbnormal 客戶端攜帶的 Token 不是有效的 Jwt 令牌,將不能被解析

TokenIssued 令牌解碼後,issuer 或 audience不正確

NoPermissions 無權訪問此 API

添加三個中間件

            app.UseAuthentication();
            app.UseAuthorization();
            app.UseMiddleware<RoleMiddleware>();

app.UseAuthorization();是微軟授權認證的中間件,CZGL.Auth 會先讓,預設的驗證管道過濾一些無效請求和認證信息,再由 CZGL.Auth 來校驗授權。

三、如何設置API的授權

很簡單,CZGL.Auth 的認證授權,你只需在 Controller 或 Action上 添加 [Authorize]

CZGL.Auth 只會對使用了 [Authorize] 特性的 Controller 或 Action 生效。

如果一個 Controller 已經設置了 [Authorize] ,但是你想裡面的 Action 跳過授權認證,則使用 [AllowAnonymous] 修飾 Action。

使用方法跟微軟的預設的完全一致。這樣無需過多配置。

如果你想另外定義一個特性用來另外設置 授權的話,可以到我的倉庫提 Issue 或者直接聯繫我微信。

添加一個 APIController ,

    [Authorize]
    [Route("api/[controller]")]
    [ApiController]
    public class TestController : ControllerBase
    {

        [HttpGet("/A")]
        public JsonResult A()
        {
            return new JsonResult(new { Code = 200, Message = "Success!" });
        }

        [HttpGet("/B")]
        public JsonResult B()
        {
            return new JsonResult(new { Code = 200, Message = "Success!" });
        }

        [HttpGet("/C")]
        public JsonResult C()
        {
            return new JsonResult(new { Code = 200, Message = "Success!" });
        }
        [HttpGet("/AB")]
        public JsonResult AB()
        {
            return new JsonResult(new { Code = 200, Message = "Success!" });
        }
        [HttpGet("/BC")]
        public JsonResult BC()
        {
            return new JsonResult(new { Code = 200, Message = "Success!" });
        }
        [HttpGet("/AC")]
        public JsonResult AC()
        {
            return new JsonResult(new { Code = 200, Message = "Success!" });
        }

        [HttpGet("/ABC")]
        public JsonResult ABC()
        {
            return new JsonResult(new { claims = User.Claims });
        }


        /// <summary>
        /// 任何人都不能訪問
        /// </summary>
        /// <returns></returns>
        [HttpGet("D")]
        public JsonResult D()
        {
            return new JsonResult(new { Code = 200, Message = "Success!" });
        }

        [HttpGet("error")]
        public JsonResult Denied()
        {
            return new JsonResult(
                new
                {
                    Code = 0,
                    Message = "訪問失敗!",
                    Data = "此賬號無權訪問!"
                });
        }
    }

四、添加登錄頒發 Token

添加一個 AccountController.cs 用來頒發登錄、 Token。

    [Route("api/[controller]")]
    [ApiController]
    public class AccountController : ControllerBase
    {
        [HttpPost("/Login")]
        public async Task<JsonResult> Login([FromQuery]string username, string password, string rolename)
        {
            // 用戶名密碼是否正確
            if (string.IsNullOrWhiteSpace(username) || string.IsNullOrWhiteSpace(password) || string.IsNullOrWhiteSpace(rolename))
            {
                return new JsonResult(new 
                {
                    Code = 0,
                    Message = "尼瑪,上傳什麼垃圾信息",
                });
            }

            if(!((username=="aa"||username=="bb"||username=="cc")&&password=="123456"))
            {
                return new JsonResult(new
                {
                    Code = 0,
                    Message = "賬號或密碼錯誤",
                });
            }

            // 你自己定義的角色/用戶信息服務
            RoleService roleService = new RoleService();

            // 檢驗用戶是否屬於此角色
            var role = roleService.IsUserToRole(username,rolename);

            // CZGL.Auth 中一個用於加密解密的類
            EncryptionHash hash = new EncryptionHash();

            // 設置用戶標識
            var userClaims = hash.BuildClaims(username, rolename);

            //// 自定義構建配置用戶標識
            /// 自定義的話,至少包含如下標識
            //var userClaims = new Claim[]
            //{
            //new Claim(ClaimTypes.Name, userName),
            //    new Claim(ClaimTypes.Role, roleName),
            //    new Claim(JwtRegisteredClaimNames.Aud, Audience),
            //    new Claim(ClaimTypes.Expiration, TimeSpan.TotalSeconds.ToString()),
            //    new Claim(JwtRegisteredClaimNames.Iat, new DateTimeOffset(DateTime.Now).ToUnixTimeSeconds().ToString())
            //};
            /*
            iss (issuer):簽發人
            exp (expiration time):過期時間
            sub (subject):主題
            aud (audience):受眾
            nbf (Not Before):生效時間
            iat (Issued At):簽發時間
            jti (JWT ID):編號
            */

            // 方法一,直接頒發 Token
            ResponseToken token = hash.BuildToken(userClaims);


            //方法二,拆分多步,頒發 token,方便調試
            //var identity = hash.GetIdentity(userClaims);
            //var jwt = hash.BuildJwtToken(userClaims);
            //var token = hash.BuildJwtResponseToken(jwt);

            return new JsonResult(token);
        }
    }

五、部分說明

註入 Jwt 服務、頒發 Token

CZGL.Auth 把使用 jwt 的服務和頒發 Token 的代碼封裝好了,這個庫不是在“造輪子”,所以實際上你可以很輕鬆的把這部分的代碼抽出來,另外設計。

這部分的代碼所在位置 RoleServiceExtension.cs 、EncryptionHash.cs。

授權中間件

            app.UseAuthentication();
            app.UseAuthorization();
            app.UseMiddleware<RoleMiddleware>();

我的寫法是利用 ASP.NET Core 的 jwt 完成基礎的認證授權,然後在下一個管道中實現拓展的認證。但是本身的認證是在 app.UseAuthorization(); 做了拓展,所以使用 CZGL.Auth,只需要按照平常 jwt 的方式去使用,只是加了一個 RoleMiddleware 中間件。

CZGL.Auth 只是我受到新思路啟發臨時寫出來的。。。最好不要直接用於生產,去 github 庫把項目下載下來,按照自己應用場景改一下~。

六、驗證

先使用 aa 用戶登錄,登錄時選擇 A 角色。

因為 A 用戶只能訪問 “帶有 A ” 的API, "/A"、"/AB" 等,所以我們可以試試。

繼續用這個 Token 訪問一下 "/B"

可以繼續嘗試添加 API 或者使用其他用戶登錄,訪問不同的 API。

由於別人對前端不熟,所以就不寫帶頁面的示例了~。

可以用 Postman 就行測試。

什麼示例的 項目可以到倉庫里下載,名稱是 MyAuth。

一般上,用戶許可權、角色許可權信息是存儲在資料庫裡面的,另一個示例是 CZGL.Auth.Sample2。

這個庫只是較為粗略的授權認證,與更豐富的需求請自行下載源碼修改~

有問題要討論,可以在俱樂部裡面找到我。

深圳、廣州、長沙、上海的群等我都在,嘿嘿嘿,嘿嘿嘿。


您的分享是我們最大的動力!

-Advertisement-
Play Games
更多相關文章
  • [TOC] 原文鏈接: "QCustomplot使用分享(九) 繪製圖表 多功能游標" 一、概述 上一篇文章 "QCustomplot使用分享(八) 層(完結)" 講述了第一篇QCustomPlot控制項的使用,主要是展示了多維度折線圖,並且有一個簡單的游標展示效果。本篇文章是在上一篇文章的基礎上進行 ...
  • 以下是我近些年收集的一些Python實用技巧和工具,希望能對你有所幫助。 交換變數 if 語句在行內 連接 下麵的最後一種方式在綁定兩個不同類型的對象時顯得很cool。 數字技巧 註意浮點數的除法 數值比較 這是我見過諸多語言中很少有的如此棒的簡便法 同時迭代兩個列表 帶索引的列表迭代 列表推導式 ...
  • 前言 web漏洞之首莫過於sql了,不管使用哪種語言進行web後端開發,只要使用了關係型資料庫,可能都會遇到sql註入攻擊問題。那麼在Python web開發的過程中sql註入是怎麼出現的呢,又是怎麼去解決這個問題的? 當然,我這裡並不想討論其他語言是如何避免sql註入的,網上關於PHP防註入的各種 ...
  • 1 #include 2 #include 3 typedef struct Lnode{ 4 int num; 5 struct Lnode * next; 6 }Lnode,*LinkList; 7 8 typedef struct Link{ 9 LinkList data; 10 struc... ...
  • 字元串拼接 實際場景:把列表中的數據拼接成一個字元串 解決方案:使用 str.join() 方法 推薦使用生成器表達式,如果列表很大,可以節省很多記憶體空間 拆分含有多種分隔符的字元串 實際場景:把某個字元串依據分割符號拆分不同的欄位,該字元串包含多種不同的分隔符 1.使用 python 中的 spl ...
  • Python 入門 之 print帶顏色輸出 1、print帶顏色輸出書寫格式: 開頭部分: \033[顯示方式; 前景色 ; 背景色 m 結尾部分: \033[0m 詳解: 開頭部分的三個參數: 顯示方式 字體顏色 背景色 ​ 這三個參數是可選參數,可以只寫其中的某一個,另外由於表示三個參數不同含 ...
  • #!/usr/bin/env python# -*- coding: utf-8 -*-# @Time : ${DATE} ${TIME}# @Author : Aries# @Site : ${SITE}# @File : ${NAME}.py# @Software: ${PRODUCT_NAME ...
  • SignalR是一個.NET Core/.NET Framework的實時通訊的框架,一般應用在ASP.NET上,當然也可以應用在Winform上實現服務端和客戶端的消息通訊,本篇隨筆主要基於SignalR的構建一個基於Winform的服務端和客戶端的通訊處理案例,介紹其中的處理過程。 ...
一周排行
    -Advertisement-
    Play Games
  • 移動開發(一):使用.NET MAUI開發第一個安卓APP 對於工作多年的C#程式員來說,近來想嘗試開發一款安卓APP,考慮了很久最終選擇使用.NET MAUI這個微軟官方的框架來嘗試體驗開發安卓APP,畢竟是使用Visual Studio開發工具,使用起來也比較的順手,結合微軟官方的教程進行了安卓 ...
  • 前言 QuestPDF 是一個開源 .NET 庫,用於生成 PDF 文檔。使用了C# Fluent API方式可簡化開發、減少錯誤並提高工作效率。利用它可以輕鬆生成 PDF 報告、發票、導出文件等。 項目介紹 QuestPDF 是一個革命性的開源 .NET 庫,它徹底改變了我們生成 PDF 文檔的方 ...
  • 項目地址 項目後端地址: https://github.com/ZyPLJ/ZYTteeHole 項目前端頁面地址: ZyPLJ/TreeHoleVue (github.com) https://github.com/ZyPLJ/TreeHoleVue 目前項目測試訪問地址: http://tree ...
  • 話不多說,直接開乾 一.下載 1.官方鏈接下載: https://www.microsoft.com/zh-cn/sql-server/sql-server-downloads 2.在下載目錄中找到下麵這個小的安裝包 SQL2022-SSEI-Dev.exe,運行開始下載SQL server; 二. ...
  • 前言 隨著物聯網(IoT)技術的迅猛發展,MQTT(消息隊列遙測傳輸)協議憑藉其輕量級和高效性,已成為眾多物聯網應用的首選通信標準。 MQTTnet 作為一個高性能的 .NET 開源庫,為 .NET 平臺上的 MQTT 客戶端與伺服器開發提供了強大的支持。 本文將全面介紹 MQTTnet 的核心功能 ...
  • Serilog支持多種接收器用於日誌存儲,增強器用於添加屬性,LogContext管理動態屬性,支持多種輸出格式包括純文本、JSON及ExpressionTemplate。還提供了自定義格式化選項,適用於不同需求。 ...
  • 目錄簡介獲取 HTML 文檔解析 HTML 文檔測試參考文章 簡介 動態內容網站使用 JavaScript 腳本動態檢索和渲染數據,爬取信息時需要模擬瀏覽器行為,否則獲取到的源碼基本是空的。 本文使用的爬取步驟如下: 使用 Selenium 獲取渲染後的 HTML 文檔 使用 HtmlAgility ...
  • 1.前言 什麼是熱更新 游戲或者軟體更新時,無需重新下載客戶端進行安裝,而是在應用程式啟動的情況下,在內部進行資源或者代碼更新 Unity目前常用熱更新解決方案 HybridCLR,Xlua,ILRuntime等 Unity目前常用資源管理解決方案 AssetBundles,Addressable, ...
  • 本文章主要是在C# ASP.NET Core Web API框架實現向手機發送驗證碼簡訊功能。這裡我選擇是一個互億無線簡訊驗證碼平臺,其實像阿裡雲,騰訊雲上面也可以。 首先我們先去 互億無線 https://www.ihuyi.com/api/sms.html 去註冊一個賬號 註冊完成賬號後,它會送 ...
  • 通過以下方式可以高效,並保證數據同步的可靠性 1.API設計 使用RESTful設計,確保API端點明確,並使用適當的HTTP方法(如POST用於創建,PUT用於更新)。 設計清晰的請求和響應模型,以確保客戶端能夠理解預期格式。 2.數據驗證 在伺服器端進行嚴格的數據驗證,確保接收到的數據符合預期格 ...