asp.net core 3.x 受權中的概念

前言

預計是經過三篇來將清楚asp.net core 3.x中的受權:一、基本概念介紹;二、asp.net core 3.x中受權的默認流程;三、擴展。html

在徹底沒有概念的狀況下不管是看官方文檔仍是源碼都暈乎乎的,但願本文能幫到你。不過我也是看源碼結合官方文檔看的,可能有些地方理解不對,因此只做爲參考。web

要求對asp.net core的基礎有全部瞭解:Host、依賴注入、日誌、配置、選項模式、終結點路由、身份驗證等。仍是推薦A大博客數據庫

 

概述

概括來講受權就是:某人  針對某個資源  能夠作什麼操做,如:張三   針對銷售訂單    能夠查看、審覈、取消等操做api

  • 某人:這個好理解,只要登陸系統的用戶咱們就曉得他是誰;額外的他屬於某個角色、屬於某個部門、甚至咱們能夠規定年齡段在18-30歲的能幹什麼,30-50歲的能幹啥,這些都屬於所屬角色、所屬部門、所屬年齡段都是用戶的一個屬性,會做爲權限判斷的一個依據
  • 資源:能夠任何形式的資源,好比銷售訂單、商品、等等;也能夠有更復雜的規則,好比金額大於10000以上的,必須通過老總審覈這種要求;另外好比一個頁面也能夠看作是資源,好比是否容許誰能夠訪問某個頁面。對資源的限定也將做爲權限判斷的一部分
  • 操做:好比上面說的查看、審覈、新增、修改..巴拉巴拉...固然操做也做爲權限判斷的一部分。

除了上面這3個概念外加一個權限判斷邏輯,就組成了受權系統。下面逐一介紹asp.net core 3.x中受權系統涉及到的相關概念微信

注:
功能權限:這是咱們一般說的某人(包含所屬角色,所屬部門等)能夠訪問某個菜單下的某個按鈕
數據權限:上面說的訂單金額大於10000的必需要經理角色才能夠審覈
這個說來也沒啥區別,一個按鈕一般是對應到mvc中的某個action,因此仍是能夠當作是操做;金額大於10000,這個也只是對資源的一種選定cookie

還有一種狀況,說一個按鈕的點擊對應到一個action,那麼這個按鈕究竟是看作「操做」呢,仍是把這個Action當作是一個頁面地址,做爲資源呢?這個就看怎麼設計了,mvc默認是當作資源。mvc

 

用戶標識ClaimsPrincipal

現實生活中,一我的有多種證件,好比身份證、登機牌、就診卡等,你去到不一樣的機構須要出示不一樣的證件,而每張證件上又有不一樣的信息,好比身份驗證上有身份證號、姓名、性別、出示日期等等...  登機牌上有 航班號、座位號之類的。app

在asp.net core中,ClaimsPrincipal就表明上面說的這我的,它可能存在多張證件,證件用ClaimsIdentity表示,固然得有一張證件做爲主要證件(如身份證);一張證件又包含多條信息,能夠用相似字典的形式IDictionary<string,string>來存儲證件的信息,可是字典不夠面向對象,因此單獨爲證件上的一條信息定義了一個類Claim,拿身份證上的出生日期來講,ClaimType="出生日期",Value=「1995-2-4」框架

上面咱們一直拿一我的擁有多張證件來舉例,其實並不許確,由於對系統來講並不關心是誰登陸,多是一個用戶、也多是一個第三方應用。因此將ClaimsPrincipal理解爲一個登陸到系統的主體更合理。asp.net

在一個系統中可能同時存在多種身份驗證方案,好比咱們系統自己作了用戶管理功能,使用最簡單的cookie身份驗證方案,或者使用第三方登陸,微信、QQ、支付寶帳號登陸,一般一個身份驗證方案能夠產生一張證件(ClaimsIdentity),固然某個身份驗證方案也能夠將得到的Claim添加到一張現有的證件中,這個是靈活的。默認狀況下,用戶登陸時asp.net core會選擇設置好的默認身份驗證方案作身份驗證,本質是建立一個ClaimsPrincipal,並根據當前請求建立一個證件(ClaimsIdentity),而後將此ClaimsIdentity加入到ClaimsPrincipal,最後將這個ClaimsPrincipal設置到HttpContext.User屬性上。身份驗證不是本篇重點,詳細描述參考:《asp.net core 3.x 身份驗證-1涉及到的概念》。咱們目前只要記住一個字符串表明一個身份驗證方案,它能夠從當前請求或第三方去得到一張證件(ClaimsIdentity)  

當用戶登陸後,咱們已經能夠從HttpContext.User拿到當前用戶,裏面就包含一張或多張證件,後續的權限判斷一般就依賴裏面的信息,好比所屬角色、所屬部門,除了證件的信息咱們也能夠經過用戶id去數據庫中查詢獲得更多用戶信息做爲權限判斷的依據。

 

資源

資源的概念很寬泛,上面說的銷售訂單、客戶檔案、屬於資源,咱們能夠控制某個用戶是否能查看、新增、審覈訂單。或者說一個頁面也是一種資源,咱們但願控制某用戶是否能訪問某個頁面。在asp.net core中直接以object類型來表示資源,由於asp.net core做爲一個框架,它不知道未來使用此框架的開發者究竟是對什麼類型的資源作權限限制。

在咱們平常開發中常常在Action上應用AuthorizeAttribute標籤來進行受權控制,其實這裏就是將這個Action當作資源。因爲目前asp.net core 3.x中默認使用終結點路由,因此如今在asp.net core 3.x中的默認受權流程中當前Endpoint就是資源

記住權限判斷中不必定須要資源的參與,好比只要用戶登陸,就容許使用系統中全部功能。此時整個系統就是資源,容許全部操做。

 

核心概念圖

 

  

受權依據IAuthorizationRequirement

試想這樣一種權限需求:要求屬於角色"經理"或"系統管理員"的用戶能夠訪問系統任何功能。當咱們作權限判斷時咱們能夠從HttpContext.User獲得當前用戶,從其證件列表中總能找到當前用戶的所屬角色,那麼這裏須要進行比較的兩個角色"經理"、"系統管理員"從哪裏得到呢?
再好比:要求只要當前用戶的證件中包含一個"特別通行證"的Calim,就容許他訪問系統的任何功能。同上面的狀況同樣,在判斷權限時咱們能夠知道當前登陸用戶的Calim列表,那須要進行比對的"特別通行證"這個字符串從哪來呢?

asp.net core將這種權限判斷時須要用來比對的數據定義爲IAuthorizationRequirement,我這裏叫作"受權依據",在一次權限判斷中可能會存在多個判斷,因此可能須要多個受權依據,文件後面會講如何定製受權依據

其實某種意義上說「當前用戶(及其包含的Calim列表)」也能夠看作是一種依據,由於它也是在受權判斷過程當中須要訪問的數據,可是這個咱們是直接經過HttpContext.User來獲取的,不須要咱們來定義。

當咱們針對某個頁面或Action進行受權時能夠直接從當前路由數據中獲取Action名,在asp.net core 3.x中甚至更方便,能夠在請求管道的早期就能得到當前請求的終結點。因此針對Action的訪問也不須要定義成受權依據中

因此受權依據算是一種靜態數據,爲了更好的理解,下面列出asp.net core中已提供的幾種受權依據

  • ClaimsAuthorizationRequirement
public string ClaimType { get; }
public IEnumerable<string> AllowedValues { get; }

     未來在權限判斷是會判斷當前用戶的Claim列表中是否包含一個類型爲ClaimType的Claim,若AllowedValues有數據,則進一步判斷是否完整包含AllowedValues中定義的值

  • DenyAnonymousAuthorizationRequirement:權限判斷髮現存在這個依據,則直接拒絕匿名用戶訪問
  • RolesAuthorizationRequirement:這就是最多見的基於角色的受權時會使用的,它定義了 public IEnumerable<string> AllowedRoles { get; } ,未來作權限判斷時會看當前用戶是否屬於這裏容許的角色中的一種
  • OperationAuthorizationRequirement:這個也比較經常使用,在作功能受權時比較經常使用。它定義了 public string Name { get; set; } ,Name表明當前操做名,好比「Order.Add」就是新增訂單,未來權限判斷是能夠根據當前用戶Id、所屬角色和"Order.Add"到數據庫去作對比
  • AssertionRequirement:這個就更強大了,它定義了  public Func<AuthorizationHandlerContext, Task<bool>> Handler { get; } ,未來權限判斷時發現是這個類型,直接調用這個委託來進行權限判斷,因此靈活性很是大 

 

受權策略AuthorizationPolicy

策略同時做爲身份驗證方案受權依據的容器,它包含本次受權須要的數據。

請求抵達時asp.net core會找到默認身份驗證方案進行身份驗證(根據請求獲取用戶ClaimsPrincipal),但有時候咱們但願由本身來指定本次受權使用哪些身份驗證驗證方案,而不是使用默認的,這樣未來身份驗證過程當中會調用設置的這幾個身份驗證方案去得到多張證件,此時HttpContext.User中就包含多張證件。因此受權策略裏包含多種身份驗證方案。

一次受權可能須要多種判斷,好比同時判斷所屬角色、而且是否包含哪一種類型的Calim而且.....,某些判斷可能須要對比「受權依據」,因此一個受權策略包含多個受權依據。

另外咱們能夠將多個受權策略合併成一個對嗎?全部的身份驗證方案合併,全部的「受權依據」合併

未來受權檢查時將根據身份驗證方案獲取當前用戶的多個證件(裏面包含不少Cliam能夠用做權限判斷),而後逐個判斷受權依據,若都知足則認爲受權檢查成功。

如果針對某個資源的受權,受權方法大概是這樣定義的xxxx.Authorize(策略,訂單),這裏不必定直接傳入整個訂單,可能只傳入訂單金額,這個根據業務須要。如果簡單的狀況只判斷頁面訪問權限,則xxx.Authorize(策略),由於當前頁面能夠直接經過當前請求獲取。

在asp.net core 3.x中啓動階段咱們能夠定義一個受權策略列表,這個當作是全局受權策略,一直存在於應用中。
在應用運行時,每次進行受權時會動態建立一個受權策略,這個策略是最終進行本次受權檢查用的,它可能會引用某一個或多個全局策略,所謂的引用就是合併其「身份驗證方案」列表和「受權依據列表」,固然其自身的「身份驗證方案」列表和「受權依據列表」也是能夠定製的,待會在AuthorizeAttribute部分再詳細說

 

策略構造器AuthorizationPolicyBuilder

主要用來幫助建立一個受權策略(.net中典型的Builder模式),使用步驟是:

  1. new一個AuthorizationPolicyBuilder
  2. 調用各類方法對策略進行配置
  3. 最後調用Build方法生成最終的受權策略。

下面用僞代碼感覺下

var builder = new AuthorizationPolicyBuilder();
builder.RequireRole("Manager","Admin");
//builder....繼續配置
var authorizationPolicy = builder.Build();

RequireRole將爲最終會生成的策略中的「受權依據」列表加入一個RolesAuthorizationRequirement("Manager","Admin")。其它相似的api就不介紹了。

 

受權處理器AuthorizationHandler

上面說的當前用戶、受權依據、以及受權時傳遞進來的資源都是能夠當作是靜態的數據,做爲受權判斷的依據,真正受權的邏輯就是用IAuthorizationHandler來表示的,先看看它的定義

public interface IAuthorizationHandler
{
    Task HandleAsync(AuthorizationHandlerContext context);
}

 

AuthorizationHandlerContext

中包含當前用戶、受權依據列表和參與受權判斷的資源,前者是根據受權策略中的多個身份驗證方案通過身份驗證後獲得的;後者就是受權策略中的受權依據列表。在方法內部處理成功或失敗的結果是直接存儲到context對象上的。

一個應用中能夠存在多個AuthorizationHandler,在執行受權檢查時逐個調用它們進行檢查,若都成功則本次受權成功。

 

針對特定受權依據類型  的  受權處理器AuthorizationHandler<TRequirement>

上面聊過受權依據是有多種類型的,未來還可能自定義,一般受權依據不一樣,受權的判斷邏輯也不一樣。

  • 好比RolesAuthorizationRequirement這個受權依據,它裏面包含角色列表,受權判斷邏輯應該是判斷當前用戶是否屬於這裏面的角色;
  • 再好比OperationAuthorizationRequirement它裏面定義了操做的名稱,因此受權判斷邏輯應該是拿當前用戶以及它所屬角色和這個操做(好比是「新增」)拿到數據庫去作對比

因此這樣看來一種「受權依據」類型應該對應一種「受權處理器」,因此微軟定義了public abstract class AuthorizationHandler<TRequirement> : IAuthorizationHandler ,這個TRequirement就表明這個受權處理器類型是針對哪一種類型的「受權依據的」 

一個受權策略AuthorizationPolicy是包含多個「受權依據」的,這其中可能有幾個「受權依據」的類型是同樣的,只是裏面存儲的值不一樣,以OperationAuthorizationRequirement爲例,一個受權策略裏可能包含以下受權依據列表:

new OperationAuthorizationRequirement{ Name="新增" }
new OperationAuthorizationRequirement{ Name="審覈" }
new RolesAuthorizationRequirement("Manager","Admin");
//其它。。。

因此一個受權處理器AuthorizationHandler雖然只關聯一種類型「受權依據」,可是一個受權處理器實例能夠處理多個相同類型的「受權依據」

在受權過程當中,每一個AuthorizationHandler<TRequirement>會找到本身能處理的「受權依據」,逐個進行檢查

 

針對特定受權依據類型、特定類型的資源  的  受權處理器AuthorizationHandler<TRequirement, TResource>

定義是這樣的 public abstract class AuthorizationHandler<TRequirement, TResource> : IAuthorizationHandler 
跟AuthorizationHandler<TRequirement>定義及處理邏輯惟一的區別是多了個TResource,在受權過程當中是能夠對給定資源進行判斷的,資源在AuthorizationHandlerContext.Resource,這個是object類型,爲了方便子類降重重寫,因此由這裏的父類將AuthorizationHandlerContext.Resource轉換爲TResource

乾脆貼下源碼吧

 1 public abstract class AuthorizationHandler<TRequirement> : IAuthorizationHandler
 2         where TRequirement : IAuthorizationRequirement
 3 {
 4     public virtual async Task HandleAsync(AuthorizationHandlerContext context)
 5     {
 6         foreach (var req in context.Requirements.OfType<TRequirement>())
 7         {
 8             await HandleRequirementAsync(context, req);
 9         }
10     }
11         
12     protected abstract Task HandleRequirementAsync(AuthorizationHandlerContext context, TRequirement requirement);
13 }
14     
15 public abstract class AuthorizationHandler<TRequirement, TResource> : IAuthorizationHandler
16     where TRequirement : IAuthorizationRequirement
17 {
18     public virtual async Task HandleAsync(AuthorizationHandlerContext context)
19     {
20         if (context.Resource is TResource)
21         {
22             foreach (var req in context.Requirements.OfType<TRequirement>())
23             {
24                 await HandleRequirementAsync(context, req, (TResource)context.Resource);
25             }
26         }
27     }
28         
29     protected abstract Task HandleRequirementAsync(AuthorizationHandlerContext context, TRequirement requirement, TResource resource);
30 }
View Code

 

合併AuthorizationHandler & AuthorizationRequirement

咱們發現一般一個受權依據的類型會有個對應的受權處理器,若是隻定義一個類,實現這兩種接口事情不是更簡單嗎?舉個例子:

 1 public class RolesAuthorizationRequirement : AuthorizationHandler<RolesAuthorizationRequirement>, IAuthorizationRequirement
 2 {
 3     public RolesAuthorizationRequirement(IEnumerable<string> allowedRoles)
 4     {
 5         AllowedRoles = allowedRoles;
 6     }
 7     public IEnumerable<string> AllowedRoles { get; }
 8     protected override Task HandleRequirementAsync(AuthorizationHandlerContext context, RolesAuthorizationRequirement requirement)
 9     {
10         if (context.User != null)
11         {
12             bool found = false;
13             if (requirement.AllowedRoles == null || !requirement.AllowedRoles.Any())
14             {
15                 // Review: What do we want to do here?  No roles requested is auto success?
16             }
17             else
18             {
19                 found = requirement.AllowedRoles.Any(r => context.User.IsInRole(r));
20             }
21             if (found)
22             {
23                 context.Succeed(requirement);
24             }
25         }
26         return Task.CompletedTask;
27     }
28 }
View Code

咱們上面講的微軟定義的那幾個受權依據基本都是這樣定義的

 

什麼時候實施受權檢查?

若是是用的asp.net core 3.x以前的版本,那麼在Action執行前作受權判斷比較合適,經常使用的就是過濾器Filter咯。這個我不是特別肯定,至少在.net framework時代是用的受權過濾器AuthorizeAttribute
請求  >  其它中間件  >   路由中間件  >  身份驗證中間件  >  MVC中間件  >  Controller  >  [受權過濾器]Action

如果asp.net core 3.x以後,因爲目前用的終結點路由,因此在 路由中間件 和 身份驗證中間件  後作權限判斷(使用受權中間件)比較合適,由於 路由中間件執行後咱們能夠從當前請求上下文中獲取當前終結點(它表明一個Action或一個頁面)。身份驗證中間件執行後能夠經過HttpContext.User獲取當前用戶,此時有了訪問的頁面和當前用戶 就能夠作權限判斷了
請求  >  其它中間件  >   路由中間件(這裏就拿到終結點了)  >  身份驗證中間件  >  受權中間件  >  MVC中間件  >  Controller  >  Action

還有一種狀況是在業務代碼內部去執行權限判斷,好比:但願銷售訂單金額大於10000的,必需要經理角色才能夠審覈,此時由於咱們要先獲取訂單才知道它的金額,因此咱們最好在Action執行內部根據路由拿到訂單號,去數據庫查詢訂單金額後,調用某個方法執行權限判斷。

 

受權服務AuthorizationService

因此執行權限判斷的點不一樣,AuthorizationService就是用來封裝受權檢查的,咱們在不一樣的點均可以來調用它執行權限判斷。看看接口定義

public interface IAuthorizationService
{
    Task<AuthorizationResult> AuthorizeAsync(ClaimsPrincipal user, object resource, IEnumerable<IAuthorizationRequirement> requirements);
    Task<AuthorizationResult> AuthorizeAsync(ClaimsPrincipal user, object resource, string policyName);
}

user:要進行判斷的用戶,它裏面可能存在一張或多張證件
resource:多是一個終結點,也多是一個頁面RazorPage,也多是一個訂單(或者是單獨的訂單金額)
requirements:受權依據列表
policyName:一個受權策略名

在受權中間件和在業務邏輯代碼中手動進行受權檢查時都是調用此接口
它內部會去調用AuthorizationHandler來進行權限判斷。

 

定製受權依據AuthorizeAttribute : IAuthorizeData

在asp.net core 3.x中 啓動階段能夠配置一個全局策略列表,它一直存在於系統中,暫時稱爲「靜態策略列表」
在每次執行受權檢查時也須要一個策略,暫時稱爲「運行時受權策略」,受權中間件執行時就會建立此策略,而後調用AuthorizationService根據此策略進行權限判斷,那此策略中的「受權依據」和「身份驗證方案」這倆列表從哪來的呢?就是在Action經過AuthorizeAttribute來定製的,它實現 IAuthorizeData接口

若是你對早期版本mvc有一丟丟了解的話,你可能記得有個受權過濾器的概念AuthorizeAttribute,在Action執行前會先去作受權判斷,若成功纔會繼續執行Action,不然就返回403.

在asp.net core 3.x中不是這樣了,AuthorizeAttribute只是用來定製當前受權策略(AuthorizationPolicy)的,並非過濾器,它實現IAuthorizeData接口,此接口定義以下:

public interface IAuthorizeData
{
    string Policy { get; set; }//直接指定此Action未來受權驗證時要使用的受權策略AuthorizationPolicy,此策略會被合併到當前受權策略
    string Roles { get; set; } //它會建立一個基於角色的受權依據RolesAuthorizationRequirement,此依據會被放入當前受權策略
    string AuthenticationSchemes { get; set; }//它用來定義當前受權策略裏要使用哪些身份驗證方案
}

Policy屬性指明從「靜態策略列表」拿到指定策略,而後將其「受權依據」和「身份驗證方案」這倆列表合併到「運行時受權策略」

看個例子:

1 [Authorize(AuthenticationSchemes = "cookie,jwtBearer")]
2 [Authorize(Roles = "manager,admin")]
3 [Authorize(policy:"test")]
4 [Authorize]
5 public IActionResult Privacy()
6 {
7      return View();
8 }

以上定製只是針對使用受權中間件來作權限判斷時,對當前受權策略進行定製。若咱們直接在業務代碼中調用AuthorizationService手動進行權限判斷呢,就截止調用咯。參考上面的描述

 

受權中間件AuthorizationMiddleware

上面咱們介紹了什麼時候實施受權檢查,受權中間件(AuthorizationMiddleware)就是其中最爲經常使用的一個受權檢查點,至關因而一個受權檢查的入口方法,它在進入MVC中間件以前就能夠作受權判斷,因此比以前的在Action上作判斷更早。而且因爲受權檢查是根據終結點的,所以同一套受權代碼能夠應用在mvc/webapi/razorPages...等多種web框架。因爲受權檢查依賴當前訪問的終結點(若不理解終結點,能夠暫時認爲它=Action及其之上應用的各類Attribute) 和 當前登陸用戶,所以  受權中間件  應該在   路由中間件 和 身份驗證中間件  以後註冊

 1 public void Configure(IApplicationBuilder app, IWebHostEnvironment env)
 2 { 3 //略.... 4  app.UseHttpsRedirection(); 5  app.UseStaticFiles(); 6 app.UseRouting(); 7 app.UseAuthentication(); 8 app.UseAuthorization();  9 app.UseEndpoints(endpoints => 10  { 11  endpoints.MapRazorPages(); 12  }); 13 }

它的核心步驟大體以下:

  1. 從當前請求拿到終結點
  2. 經過終結點拿到其關聯的IAuthorizeData集合
  3. 經過IAuthorizeData集合建立一個複合型受權策略
  4. 遍歷策略中的身份驗證方案獲取多張證件,最後合併放入HttpContext.User中
  5. 若Action上應用了IAllowAnonymous,則放棄受權檢查(爲毛不早點作這步?)
  6. 調用IAuthorizationService執行受權檢查
  7. 若授檢查結果爲質詢,則遍歷策略全部的身份驗證方案,進行質詢,若策略裏木有身份驗證方案則使用默認身份驗證方案進行質詢
  8. 若受權評估拒絕就直接調用身份驗證方案進行拒絕

因此重點是能夠在執行mvc中間件以前拿到終結點及其之上定義的AuthorizeAttribute,從其中的數據就能夠構建出本次權限判斷的「受權策略」,有了受權策略就能夠經過AuthorizationService執行受權判斷,內部會使用到受權處理器AuthorizationHandler

 

結束

暫時就BB到這裏,先有個大概印象,下一篇按asp.net core的默認受權流程走走源碼,再結合此篇應該就差很少了...

相關文章
相關標籤/搜索