1. 程式人生 > >Asp.net Core 系列之--5.認證、授權與自定義許可權的實現

Asp.net Core 系列之--5.認證、授權與自定義許可權的實現

ChuanGoing 2019-11-24

  asp.net core系列已經來到了第五篇,通過之前的基礎介紹,我們瞭解了事件訂閱/釋出的eventbus整個流程,初探dapper ORM實現,並且簡單的介紹了領域模型、領域倉儲及服務實現,結合上一篇的日誌、錯誤處理及事務和本篇將要介紹的許可權,大致的可以形成一個簡單的後端系統架構。當然這些都是零散的一些技術概念的介紹,後面如果有時間的話,我想詳細的介紹下如何利用領域驅動來實現一個實際案例。

話不多講,下面來看下本篇的學習曲線:

1.認識Identityserver4

2.Identityserver4實現認證與授權

3.自定義許可權的實現

認識Identityserver4

  關於Identityserver4(ids4)的概念介紹,請檢視IdentityServer4 知多少-簡書一文。我這裡要說的是,asp.net core 下的ids4集成了認證與授權兩大功能,使得我們非常方便的實現一個開放的認證與授權平臺,比如公司內部多個系統的整合登入(單點登入)/第三方系統資料共享/統一的認證中心等。整個業務流程大致為:

1.使用者首先的有使用者中心的賬號資訊,因此需要註冊一個賬號

2.使用者訪問某個站點應用,需要去到使用者中心認證

3.認證通過,使用者得到其在使用者中心註冊的相應資訊及其許可權時限、範圍、大小

4.認證不通過,即非法使用者,提示使用者註冊

5.在第3步的前提下,若使用者訪問到另一個站點(採用同一認證平臺),這時使用者可以用之前認證通過後拿到的訪問令牌訪問此站點,若此令牌中包含此站點的相應許可權即可之前登入。

Identityserver4實現認證與授權

首先,新建一個asp.net core web 空專案,並且新增如下IdentityServer4 Nuget包

在ConfigureServices新增如下程式碼

註冊IdentityServer中介軟體,如下5個配置分別表示:

1.AddDeveloperSigningCredential:開發模式下的簽名證書,開發環境啟用即可

2.AddInMemoryApiResources:相關資源配置

public static IEnumerable<ApiResource> GetApiResources()
        {
            return new List<ApiResource>
            {
                new ApiResource("WebApi", "ChuanGoingWebApi"),
                new ApiResource("ProductApi", "ChuanGoingWebProduct")
            };
        }
GetApiResources

這裡配置了兩個Api資源

3.AddInMemoryIdentityResources:OpenID Connect相關認證資訊配置

 public static IEnumerable<IdentityResource> GetIdentityResources()
        {
            return new List<IdentityResource>
            {
                new IdentityResources.OpenId(),
                new IdentityResources.Profile()
            };
        }
GetIdentityResources

4.AddInMemoryClients:客戶端資訊配置

 public static IEnumerable<Client> GetClients(IConfiguration Configuration)
        {
            var OnlineConfig = Configuration.GetSection("OnlineClient");
            var List = new List<Client>
            {
                new Client()
                {
                    ClientId = "ClientCredentials",
                    AllowedGrantTypes = GrantTypes.ClientCredentials,
                    ClientSecrets = { new Secret("ClientSecret".Sha256()) },
                    AllowedScopes =
                    {
                        IdentityServerConstants.StandardScopes.OpenId,
                        IdentityServerConstants.StandardScopes.Profile,
                        "WebApi",
                        "ProductApi"
                    },
                    AccessTokenLifetime = 10 * 60 * 1
                },

                new Client()
                {
                    ClientId = "ResourceOwnerPassword",
                    AllowedGrantTypes = GrantTypes.ResourceOwnerPassword,
                    ClientSecrets = { new Secret("ClientSecret".Sha256()) },
                    AllowedScopes =
                    {
                        IdentityServerConstants.StandardScopes.OpenId,
                        IdentityServerConstants.StandardScopes.Profile,
                        "WebApi",
                        "ProductApi"
                    },
                    AccessTokenLifetime = 10 * 60 * 1
                },
                  /*
                  隱式模式:https://localhost:6005/connect/authorize?client_id=Implicit&redirect_uri=http://localhost:5000/Home&response_type=token&scope=WebApi
                  */
                new Client()
                {
                    ClientId = "Implicit",
                    ClientName = "ImplicitClient",
                    AllowedGrantTypes = GrantTypes.Implicit,
                    ClientSecrets = { new Secret("ImplicitSecret".Sha256()) },
                    RedirectUris ={OnlineConfig.GetValue<string>("RedirectUris") },
                    PostLogoutRedirectUris = {OnlineConfig.GetValue<string>("LogoutRedirectUris") },
                    AllowedScopes =
                    {
                        IdentityServerConstants.StandardScopes.OpenId,
                        IdentityServerConstants.StandardScopes.Profile,
                        "WebApi",
                        "ProductApi"
                    },
                    AccessTokenLifetime = 10 * 60 * 1,
                    //允許將token通過瀏覽器傳遞
                     AllowAccessTokensViaBrowser=true
                },
                /*
                 * 授權碼模式:https://localhost:6005/connect/authorize?client_id=GrantCode&redirect_uri=http://localhost:5000/Home&response_type=code&scope=WebApi
                 */
                new Client()
                {
                   //客戶端Id
                    ClientId="GrantCode",
                    ClientName="GrantCodeClient",
                    //客戶端密碼
                    ClientSecrets={new Secret("CodeSecret".Sha256()) },
                    //客戶端授權型別,Code:授權碼模式
                    AllowedGrantTypes=GrantTypes.Code,
                    //允許登入後重定向的地址列表,可以有多個
                     RedirectUris ={OnlineConfig.GetValue<string>("RedirectUris") }, 
                    //允許訪問的資源
                    AllowedScopes={
                        "WebApi",
                        "ProductApi"
                    }
                }
            };
            return List;
        }
GetClients

分別物件Auth2.0的四種模式,本篇將用到的是ResourceOwnerPassword模式,其他幾種可在篇尾github連結檢視原始碼的實現

5.AddTestUsers:使用者配置,可結合快取/持久化

public static List<TestUser> GetUsers()
        {
            return new List<TestUser>
            {
                new TestUser
                {
                    SubjectId = Guid.NewGuid().ToString(),
                    Username = "admin",
                    Password = "123456"

                    //Claims = new List<Claim>
                    //{
                    //    new Claim("name", "admin"),
                    //    new Claim("website", "https://www.cnblogs.com/chuangoing")
                    //}
                },
                new TestUser
                {
                    SubjectId = Guid.NewGuid().ToString(),
                    Username = "chuangoing",
                    Password = "123456"

                    //Claims = new List<Claim>
                    //{
                    //    new Claim("name", "chuangoing"),
                    //    new Claim("website", "https://github.com/chuangoing")
                    //}
                }
            };
        }
GetUsers

 定義兩個測試使用者,注意這裡的SubjectId,用作使用者中心註冊的openid(認證唯一),後面將會用到

然後,Configure中新增app.UseIdentityServer();//啟用ids4

至此,ids4 服務完成

用postman測試下:

 

返回jwt accesstoken:

 

 

將token內容解碼,如下:

 

 可以看到,裡面包含我們配置的ProductApi/WebApi的許可權

將token資訊加入到http的header中:

 

  注意Bearer後面有個空格,訪問order的獲取訂單資訊:

 

 

 自定義許可權的實現

  這裡,我們將api中的action分別定義一個許可權程式碼,使用者擁有了此action訪問許可權(擁有此許可權程式碼)即可訪問,簡單實現如下:

1.定義許可權特性標識,api的action指定某個標識

public class PermissionAttribute : Attribute
    {
        /// <summary>
        /// 許可權程式碼
        /// </summary>
        public string Code { get; }
        /// <summary>
        /// 
        /// </summary>
        /// <param name="code">許可權程式碼</param>
        public PermissionAttribute(string code)
        {
            Code = code;
        }
    }
PermissionAttribute

 

 此處,get action定義了訪問許可權標識為"XYZ"

同樣,我們這裡需要用到一個許可權過濾器,利用過濾器的Aop實現許可權過濾業務處理:

 public class PermissionFilter : ActionFilterAttribute
    {
        public override void OnActionExecuting(ActionExecutingContext context)
        {
            var user = context.HttpContext.User;
            if (user.Identity.IsAuthenticated)
            {
                //TODO:使用者自定義許可權驗證
                Guid userId = context.HttpContext.GetId();
                bool right;
                #region 自定義許可權驗證
                //根據userId判斷使用者內部系統許可權資訊

                //var userPermissions = repo.GetUserPermissions(userId);
                //var permissions = repo.GetPermissions();
                var metas = context.ActionDescriptor.EndpointMetadata;
                foreach (var meta in metas)
                {
                    if (meta is PermissionAttribute permission)
                    {
                        //if (!permissions.Any(p => permission.Code.Any(c => c == p.Code))
                        //    && !userPermissions.Any(p => permission.Code.Any(c => c == p.Code)))
                        //{
                        //    throw new WebException(HttpStatusCode.Forbidden, MessageCodes.AccessDenied, "你沒有訪問該資源的許可權");
                        //}
                        //break;
                    }
                }

                right = false;
                #endregion
                if (!right)
                {
                    context.Result = new ContentResult() { StatusCode = (int)HttpStatusCode.Forbidden, Content = "你沒有訪問該資源的許可權" };
                }

            }
        }
PermissionFilter

同時,啟用許可權過濾器配置

 

 

部分程式碼略過,詳細的請檢視篇尾的原始碼連結

利用第二節的認證授權得到的token,我們用postman測試下:

 

過濾器切面成功工作

 

 

 還記得第一節說的SubjectId麼?這裡利用這個openid,去內部系統去匹配相關使用者資訊,相關業務就不深入了,有興趣的朋友可以下載示例完善下

 

 至此,整個許可權認證、授權、自定義許可權介紹完。

WebApi詳細程式碼在Github的https://github.com/ChuanGoing/Start.git  的Domain分支可以找到,AuthServer詳細程式碼在https://github.com/ChuanGoing/Demo/tree/master/ChuanGoing.AuthorizationServer中。

 

&n