Policy-based authorization in .NET6
關於Authorization
有二種授權檢驗的方式:Role based 或 Policy based。 Role based 就是用角色決定,俱體上設定 Role 屬性在目標上,這會綁定固較沒有彈性難以客製化。可適於小型系統。 Policy based 是所謂抽像的〝政策〞,在俱體上就是跑程式決定。較有彈性然實作上有點小麻煩。
Policy based 關鍵知識
為『授權標的』指定『Policy』。
為該『Policy』註冊須滿足的授權需求『
IAuthorizationRequirement
』。滿足這些授權需求『
IAuthorizationRequirement
』就滿足『Policy』。
將 Policy based 關聯簡化表示如下
Target ---> Policy ---> Requirement ---> Handler
為授權標的指定 Policy
[ApiController]
[Route("api/[controller]/[action]")]
[Authorize("AuthFunc")] //<--- 指定 Policy based authorization
[AuthFunc("DEMO04")] //<--- 添加客製屬性:表明標的的FuncCode。
public class YourController : ControllerBase //<--- 授權標的
{
...省略...
}
為 Policy 註冊須滿足的授權需求(Authorization Requirement)
... 省略 ...
builder.Services.AddAuthentication(/*** 省略 ***/);
// 註冊 客製 Policy 與滿足它所要的需求 Authorization Requirement
// 一個 Policy 可以滿足多個需求才成立,此例只有一個:AuthFunc。
builder.Services.AddAuthorization(options => {
options.AddPolicy("AuthFunc", policy => policy.Requirements.Add(new AuthFuncRequirement()));
});
// 註冊 客製化授權需求 Authorization Requirement。
// ※ 將自動觸發對應的檢驗程序
builder.Services.AddSingleton<IAuthorizationHandler, AuthFuncHandler>();
// 註冊 客製化服務 --- 認證與授權
builder.Services.AddSingleton<AccountService>();
var app = builder.Build();
... 省略 ...
app.UseAuthentication();
app.UseAuthorization();
... 省略 ...
滿足授權需求(Authorization Requirement)就滿足 Policy
using Microsoft.AspNetCore.Authorization;
using Microsoft.AspNetCore.Mvc.Controllers;
using System.Diagnostics;
using System.Reflection;
using System.Security.Claims;
using YourProject.Controllers;
using YourProject.Services;
namespace YourProject.Models;
// 用來添加客製屬性:表明標的的FuncCode。用於授權檢驗需求。
// ※ 不必實作內容,只做為需求參數的取得來源。
[AttributeUsage(AttributeTargets.Class)]
public class AuthFuncAttribute : Attribute
{
public AuthFuncAttribute(string funcCode) { }
}
// 宣告 Authorization Requirement
// ※ 不必實作內容,只做為需求的宣示。
class AuthFuncRequirement : IAuthorizationRequirement { }
// 實作 handler --- Authorization Requirement 檢驗程序
internal class AuthFuncHandler : AuthorizationHandler<AuthFuncRequirement>
{
readonly ILogger<AuthFuncHandler> _logger;
readonly AccountService _account;
// 將注入資源
public AuthFuncHandler(ILogger<AuthFuncHandler> logger, AccountService account)
{
_logger = logger;
_account = account;
}
// 檢驗程序實作
protected override Task HandleRequirementAsync(AuthorizationHandlerContext context, AuthFuncRequirement requirement)
{
//# 未登入離開。
if (!context.User.Identity?.IsAuthenticated ?? false)
return Task.CompletedTask;
//# 取得需要資源,若非預期資源離開。
var http = context.Resource as DefaultHttpContext;
if (http == null) return Task.CompletedTask;
var endpoint = http.GetEndpoint();
if (endpoint == null) return Task.CompletedTask;
var descriptor = endpoint.Metadata.GetMetadata<ControllerActionDescriptor>();
if (descriptor == null) return Task.CompletedTask;
//# 取得客製資源
// 功能代碼:預設為Controller名稱。
string? funcCode = descriptor.ControllerName;
// 但若用 AuthFuncAttribute 指定[功能代碼]則為優先。
CustomAttributeData? authFunc = descriptor.ControllerTypeInfo.CustomAttributes.FirstOrDefault(c => c.AttributeType == typeof(AuthFuncAttribute));
if (authFunc != null && authFunc.ConstructorArguments.Count > 0)
funcCode = authFunc.ConstructorArguments[0].Value as string;
//# 開始驗證
// 是否登入者有授權的功能清單。
if(!InAuthFuncList(context.User, funcCode)) return Task.CompletedTask;
// OK
context.Succeed(requirement); // 滿足此項授權需求
return Task.CompletedTask;
}
/// <summary>
/// help funciton: 有否在登入者的授權功能清單中
/// </summary>
bool InAuthFuncList(ClaimsPrincipal user, string? funcCode)
{
if (funcCode == null) return false;
// 檢查是否有該授權功能
AuthUser? auth = _account.GetSessionUser(user.Identity);
if (auth == null) return false;
if(!auth.AuthFuncList().Contains(funcCode)) return false;
// Success
return true;
}
}
完整程式碼
參考文章
Last updated