ASP.NET Core 中基於策略的授權
軟體應用程式的授權層可確保當前使用者能夠訪問指定資源、執行給定操作或對指定資源執行給定操作。在 ASP.NET Core
中,授權層的設定方式有兩種。可以使用角色,也可以使用策略。前一種方法(即基於角色的授權)一直在舊版 ASP.NET
平臺中沿用,而基於策略的授權則是 ASP.NET Core
中新增的方法。
##Authorize 屬性
從早期開始,ASP.NET
應用程式中使用的一直都是角色。從技術角度來講,角色是純字串。不過,它的值被安全層視為元資訊(檢查 IPrincipal
物件中是否有值),並供應用程式使用,用於將一組許可權對映到經過身份驗證的給定使用者。在 ASP.NET
中,登入使用者由 IPrincipal
物件進行標識。在 ASP.NET Core
中,實際類是 ClaimsPrincipal
。此類可公開一系列標識,每個標識均由 IIdentity
物件(具體而言,就是 ClaimsIdentity
物件)進行表示。也就是說,任何登入使用者都會隨附一個宣告列表,這其實就是使用者的狀態宣告。使用者名稱和角色是 ASP.NET Core
應用程式使用者的兩個常見宣告。不過,角色是否顯示取決於後備標識儲存區。例如,如果使用社交身份驗證,永遠都不會看到角色。
授權比身份驗證更進一步。身份驗證就是發現使用者標識,而授權則是定義使用者呼叫應用程式終結點的要求。使用者角色通常儲存在資料庫中,並在使用者憑據經過驗證後進行檢索。此時,角色資訊以某種方式附加到使用者帳戶。IIdentity
介面的特徵之一是,必須實現 IsInRole
方法。為此,ClaimsIdentity
類檢查身份驗證程式生成的一系列宣告中是否有角色宣告。總之,當使用者嘗試呼叫安全的控制器方法時,角色應可供檢查。如果不可以,使用者呼叫任何安全的方法時則會遭拒。
Authorize
屬性通過宣告的方式保護控制器或其部分方法:
[Authorize]
public class CustomerController : Controller
{
...
}
如果未指定引數,此屬性僅檢查使用者是否經過身份驗證。不過,此屬性支援 Roles
等其他屬性。Roles
屬性指明將對具有任一所列角色的使用者授予訪問許可權。如果需要多個角色,可以多次應用 Authorize
屬性,也可以編寫自己的篩選器。
[Authorize(Roles="admin, system"]
public class BackofficeController : Controller
{
...
}
Authorize
屬性還可以視需要通過 ActiveAuthenticationSchemes
屬性,接受一個或多個身份驗證方案。
[Authorize(Roles="admin, system", ActiveAuthenticationSchemes="Cookie"]
public class BackofficeController : Controller
{
...
}
ActiveAuthenticationSchemes
屬性是逗號分隔字串,用於列出授權層將在當前上下文中信任的身份驗證中介軟體元件。也就是說,它宣告僅當使用者通過 Cookie
方案進行身份驗證並具有任一所列角色時,才允許訪問 BackofficeController
類。如前所述,傳遞到 ActiveAuthenticationSchemes
屬性的字串值必須與應用程式啟動時註冊的身份驗證中介軟體一致。
請注意,在 ASP.NET 2.0
中,身份驗證中介軟體被替換為包含多個處理程式的服務。因此,身份驗證方案是選擇處理程式的標籤。若要詳細瞭解 ASP.NET Core
中的身份驗證,建議參閱我在 2017 年 9 月發表的專欄文章“ASP.NET Core
中的 Cookie
、宣告和身份驗證”(msdn.com/magazine/mt842501)。
授權篩選器
系統提供的授權篩選器使用 Authorize
屬性提供的資訊。此篩選器先於其他任何 ASP.NET Core
篩選器執行,因為它負責檢查使用者能否執行請求的操作。如果使用者未經授權,篩選器會簡化管道,並取消請求。
可以建立自定義授權篩選器,但大部分情況下無需這樣做。實際上,最好配置預設篩選器依賴的現有授權層。
##角色、許可權和否決
藉助角色,可以根據使用者能夠執行或不能執行的操作,對應用程式使用者輕鬆進行分組。不過,這種方法不是非常容易表達;至少,還不足以滿足大部分新式應用程式的需求。例如,假設為相對簡單的授權體系結構,可以服務於網站的普通使用者,以及獲得授權可訪問後端辦公系統軟體並更新內容的 Power User
。基於角色的授權層可以圍繞兩個角色(即使用者和管理員)進行構建,這些角色定義了每組可以訪問的控制器和方法。
涉及否決方面的細微區別時,就會遇到問題,因為這些區別描述了具有給定角色的使用者能夠執行或不能執行的操作。例如,可能有使用者喜歡訪問後端辦公系統。但在這些使用者中,有的獲得授權只能編輯客戶資料,有的獲得授權只能處理內容,還有的獲得授權既能編輯客戶資料,也能處理內容(見圖 1)。
圖 1:角色層次結構
角色實質上是一種平面概念。如何平展圖 1 中所示的簡單層次結構?可以建立四個不同的角色(即 User
、Admin
、CustomerAdmin
和 ContentsAdmin
),但只要否決數量變多,所需的角色數量就會大大增加。即使像這樣的簡單練習,也表明角色可能並不是處理授權的最有效方法(優先考慮向後相容性的簡單方案和例項除外)。對於其他所有情況,要求則不同。下面開始介紹基於策略的授權。
##策略到底是什麼?
在 ASP.NET Core
中,基於策略的授權框架旨在分離授權與應用程式邏輯。簡而言之,策略是以一系列要求的形式設計的實體,這些要求本身就是當前使用者必須滿足的條件。
最簡單的策略是,對使用者進行身份驗證,同時還須滿足使用者與給定角色相關聯這一常見要求。另一常見要求是,使用者必須有特定宣告或包含某值的特定宣告。從最一般的意義上來講,要求就是斷言了嘗試訪問正確方法的使用者標識。策略物件是使用以下程式碼進行建立:
var policy = new AuthorizationPolicyBuilder()
.AddAuthenticationSchemes("Cookie, Bearer")
.RequireAuthenticatedUser()
.RequireRole("Admin")
.RequireClaim("editor", "contents") .RequireClaim("level", "senior")
.Build();
生成器物件使用各種擴充套件方法收集要求,再生成策略例項。可以看到,要求約束了身份驗證狀態和方案、角色以及通過身份驗證 Cookie
或持有者令牌讀取的任何宣告組合。
如果預定義的所有擴充套件方法都不適用於定義要求,始終可以採取最後手段,即通過自己的斷言定義新要求。命令如下:
var policy = new AuthorizationPolicyBuilder()
.AddAuthenticationSchemes("Cookie, Bearer")
.RequireAuthenticatedUser()
.RequireRole("Admin")
.RequireAssertion(ctx =>
{
return ctx.User.HasClaim("editor", "contents") ||
ctx.User.HasClaim("level", "senior");
})
.Build();
RequireAssertion
方法需要使用 lambda
,以接收 HttpContext
物件,並返回布林值。因此,斷言就是條件語句。請注意,如果多次連線 RequireRole
,使用者必須履行所有角色。若要改為表達 OR
條件,可能需要將斷言用作最後手段。在此示例中,策略實際上允許角色為內容編輯者或高階使用者的使用者。
##註冊策略
光定義策略還不夠,還必須向授權中介軟體註冊策略。為此,請在 Startup
類的 ConfigureServices
方法中,將授權中介軟體新增為服務,如下所示:
services.AddAuthorization(options=>
{
options.AddPolicy("ContentsEditor", policy =>
{
policy.AddAuthenticationSchemes("Cookie, Bearer");
policy.RequireAuthenticatedUser();
policy.RequireRole("Admin");
policy.RequireClaim("editor", "contents");
});
}
新增到中介軟體的每個策略都有一個名稱,用於在 Controller
類的 Authorize
屬性中引用策略:
[Authorize(Policy = "ContentsEditor")]
public IActionResult Save(Article article)
{
// ...
}
使用 Authorize
屬性,可以宣告的方式設定策略,但也可以通過操作方法以程式設計方式呼叫策略,如圖 2 所示。
圖 2:以程式設計方式檢查策略
public class AdminController : Controller
{
private IAuthorizationService _authorization;
public AdminController(IAuthorizationService authorizationService)
{
_authorization = authorizationService;
}
public async Task<IActionResult> Save(Article article)
{ var allowed = await _authorization.AuthorizeAsync( User, "ContentsEditor"));
if (!allowed)
return new ForbiddenResult();
// Proceed with the method implementation
...
}
}
如果無法以程式設計方式檢查許可權,建議返回 ForbiddenResult
物件。另一種選擇是,返回 ChallengeResult
物件。在 ASP.NET Core 1.x
中,返回質詢會指示授權中介軟體返回 401
狀態程式碼,或將使用者重定向到登入頁,具體視配置而定。不過,ASP.NET Core 2.0
中不會發生重定向;即使在 ASP.NET Core 1.x
中,如果使用者已登入,質詢最終也會指示返回 ForbiddenResult
物件。最後看來,最好的方法是在無法檢查許可權時返回 ForbiddenResult
物件。
請注意,甚至可以在 Razor
檢視中以程式設計方式檢查策略,如下面的程式碼所示:
@{
var authorized = await Authorization.AuthorizeAsync(
User, "ContentsEditor"))}
@if (!authorized)
{
<div class="alert alert-error">
You’re not authorized to access this page.
</div>
}
不過,為了讓此程式碼能夠正常執行,必須先注入對授權服務的依賴,如下所示:
@inject IAuthorizationService Authorization
在檢視中使用授權服務,有助於隱藏當前使用者在給定上下文中不得接觸到的 UI
元素。但請注意,光在檢視中隱藏選項還不夠。始終還需要在控制器中強制執行策略。
##自定義要求
常備要求基本上涵蓋了宣告、身份驗證,並提供了常規用途機制,用於根據斷言進行自定義,但也可以建立自定義要求。策略要求由以下兩種元素組成:僅保留資料的要求類,以及對使用者驗證資料的授權處理程式。建立自定義要求,還可以進一步表達特定策略。例如,假設要將內容編輯者策略擴充套件為,增添使用者至少必須有三年經驗的要求。具體程式碼如下:
public class ExperienceRequirement : IAuthorizationRequirement
{
public int Years { get; private set; }
public ExperienceRequirement(int minimumYears)
{
Years = minimumYears;
}
}
要求至少必須有一個授權處理程式。處理程式的型別為 AuthorizationHandler<T>
,其中 T
是要求型別。圖 3 展示了 ExperienceRequirement
型別的示例處理程式。
圖 3:示例授權處理程式
public class ExperienceHandler :
AuthorizationHandler<ExperienceRequirement>
{
protected override Task HandleRequirementAsync(
AuthorizationHandlerContext context,
ExperienceRequirement requirement)
{
// Save User object to access claims
var user = context.User; if (!user.HasClaim(c => c.Type == "EditorSince")) return Task.CompletedTask;
var since = user.FindFirst("EditorSince").Value.ToInt();
if (since >= requirement.Years)
context.Succeed(requirement);
return Task.CompletedTask;
}
}
示例授權處理程式讀取與使用者關聯的宣告,並檢查自定義 EditorSince
宣告。如果找不到,處理程式便無法返回成功。只有在找到宣告且包含的整數值不低於指定年數時,才能返回成功。
自定義宣告應為一條資訊,以某種方式與儲存到身份驗證 Cookie
中的使用者相關聯(例如,“使用者”表中的列)。不過,一旦保留對使用者的引用,便始終可以從宣告中找到使用者名稱,並對任何資料庫或外部服務執行查詢,以獲取經驗年數,從而在處理程式中使用此資訊。(我承認,如果 EditorSince
值保留 DateTime
,並計算使用者擔任編輯者是否已有一定年數,此示例會更真實一點。)
授權處理程式呼叫方法 Succeed
,同時傳遞當前要求,以通知此要求已成功得到驗證。如果沒有傳遞要求,處理程式無需執行任何操作,可以直接返回內容。不過,如果處理程式要確定是否不符合要求(無論其他處理程式是否已成功驗證同一要求),將會對授權上下文物件呼叫方法 Fail
。
下面展示瞭如何將自定義要求新增到策略(請注意,由於這是自定義要求,因此沒有擴充套件方法,而必須繼續處理策略物件的整個 Requirements
集合):
services.AddAuthorization(options =>
{
options.AddPolicy("AtLeast3Years",
policy => policy
.Requirements
.Add(new ExperienceRequirement(3)));
});
此外,還需要在 IAuthorizationHandler
型別的範圍內向 DI
系統註冊新的處理程式:
services.AddSingleton<IAuthorizationHandler, ExperienceHandler>();
如前所述,要求可包含多個處理程式。如果為授權層的同一要求向 DI
系統註冊多個處理程式,有一個成功就足夠了。
##訪問當前 HttpContext
在實現授權處理程式的過程中,可能需要檢查請求屬性或路由資料,如下所示:
if (context.Resource is AuthorizationFilterContext mvc)
{
var url = mvc.HttpContext.Request.GetDisplayUrl(); ...
}
在 ASP.NET Core
中,AuthorizationHandlerContext
物件向 FilterContext
物件公開 Resource
屬性集。上下文物件因所涉及的框架而異。例如,MVC
和 SignalR
傳送自己的特定物件。是否發生轉換視需要訪問的內容而定。例如,使用者資訊始終可用,所以無需為此進行轉換;但若要獲取 MVC
專屬詳細資訊(如路由資訊),則需要進行轉換。
##總結
在 ASP.NET Core
中,授權分為兩種。一種是基於角色的傳統授權,它的工作原理與在經典 ASP.NET MVC
中的工作原理相同,但仍存在相當平面化的結構限制,不適合表達複雜的授權邏輯。基於策略的身份驗證是一種新方法,可提供更豐富、更易表達的模型。這是因為,策略包含一系列基於宣告的要求,以及基於可從 HTTP
上下文或外部源注入的其他任何資訊的自定義邏輯。這些要求各自與一個或多個處理程式相關聯,這些處理程式負責要求的實際計算。
相關文章
- ASP.NET Core策略授權和 ABP 授權ASP.NET
- 理解ASP.NET Core - 授權(Authorization)ASP.NET
- 快速理解ASP.NET Core的認證與授權ASP.NET
- ASP.NET Core Web API下基於Keycloak的多租戶使用者授權的實現ASP.NETWebAPI
- 【ASP.NET Core】按使用者角色授權ASP.NET
- 【ASP.NET Core學習】使用JWT認證授權ASP.NETJWT
- Asp.Net Core 企業微信靜默授權ASP.NET
- ASP.NET Core之身份認證和授權JWTASP.NETJWT
- 【ASP.NET Core】按使用者等級授權ASP.NET
- ASP.NET Core 6.0 新增 JWT 認證和授權ASP.NETJWT
- Laravel授權策略Laravel
- 學習ASP.NET Core(05)-使用Swagger與Jwt授權ASP.NETSwaggerJWT
- 從零搭建一個IdentityServer——聊聊Asp.net core中的身份驗證與授權IDEServerASP.NET
- ASP.NET Core之身份驗證和授權Cookie&SessionASP.NETCookieSession
- 【ASP.NET Core】用配置檔案來設定授權角色ASP.NET
- ASP.NET Core 實戰:基於 Jwt Token 的許可權控制全揭露ASP.NETJWT
- .NET Core中的鑑權授權正確方式(.NET5)
- asp.net core3.1 實戰開發(授權,鑑權封裝詳解)ASP.NET封裝
- 使用者授權,策略的使用
- .Net Core之JWT授權JWT
- ASP.NET Core 中基於工廠的中介軟體啟用ASP.NET
- ASP.NET Core 基於JWT的認證(一)ASP.NETJWT
- ASP.NET Core 基於JWT的認證(二)ASP.NETJWT
- golang 基於 jwt 實現的登入授權GolangJWT
- .Net Core官方的 JWT 授權驗證JWT
- 基於.NetCore3.1系列 —— 認證授權方案之授權揭祕 (下篇)NetCore
- Asp.net core IdentityServer4與傳統基於角色的許可權系統的整合ASP.NETIDEServer
- Linkerd Service Mesh 授權策略(Server & ServerAuthorization)Server
- 理解ASP.NET Core - 基於Cookie的身份認證(Authentication)ASP.NETCookie
- 理解ASP.NET Core - 基於JwtBearer的身份認證(Authentication)ASP.NETJWT
- 用於安全授權的DevSecOpsdev
- 一款基於.NET Core的認證授權解決方案-葫蘆藤1.0開源啦
- 【認證與授權】2、基於session的認證方式Session
- asp.net core 3.1多種身份驗證方案,cookie和jwt混合認證授權ASP.NETCookieJWT
- IDEA基於支付寶小程式之授權篇Idea
- 造輪子之自定義授權策略
- Keycloak中授權的實現
- ASP.NET Core 高階(二)【基於工廠的中介軟體】ASP.NET