Resource-based authorization in Blazor
關聯文章
NET8 Blazor Web App 進階授權檢查與問題(BUG)參考文章
授權檢查方法
已知授權檢查 Authorize 方法有 Role-based, Policy-based 最近新發現 Resource-based 授權檢查。
Role-based 直接與角色綁定。Policy-based 有小程度的動態,比如:18歲檢查這種以登入用戶個資來判斷。Resource-based 可以處理更動態的情境。
Resource-based authorization 關鍵知識
因為 Role-based 與 Policy-based 都無法滿足才使用 Resource-based 授權檢查使用命令式授權。
可注入 IAuthorizationService 介面服務,使用 AuthorizeAsync 函式來觸發 Policy 或 Requirements 驗證。是的它的核心與 policy-based 都是一樣的,不過多了名為 Resource 的參數。這個 Resrouce 參數可以在執行期動態指定,role-based 與 policy-based 都無法在執行期動態指定 Resource。
案例:AuthorizePage in Blazor
Blazor Page Componnet
@page "/DEMO006"
@attribute [Page("DEMO006", "展示功能頁面")]
@* 取代 @attribute [Authorize("AuthPage")]。因為 F5 刷新會 => 404! *@
<AuthorizePage Page=@this />
<MudContainer>
...content...
</MudContainer>
@code {
...略...
}
AuthorizePage.razor
@using AsvtTPL.Components.Account.Shared
@inject NavigationManager navSvc
@inject IAuthorizationService authSvc
@*
* ref→[Resource-based authorization in ASP.NET Core](https://learn.microsoft.com/en-us/aspnet/core/security/authorization/resourcebased?view=aspnetcore-8.0)
*
* 取代 @attribute [Authorize("AuthPage")]。
* 因為 F5 刷新會 => 404!
* 注意:必需搭配 Vista.Biz.AOP.PageAttribute 宣示。例:
* ```
* @attribute [Page("DEMO013", "步步申請樣板")]
* ```
*@
@* 授權檢查中 *@
@if (authing)
{
<MudProgressLinear Color=Color.Error Indeterminate />
}
@code {
[CascadingParameter] Task<AuthenticationState> AuthStateTask { get; set; } = default!;
/// <summary>
/// 必需一開始就指定。資源可在動態時期指定。
/// </summary>
[Parameter, EditorRequired] public ComponentBase Page { get; set; } = default!;
//## State
bool authing = true;
protected override async Task OnParametersSetAsync()
{
Type pageType = Page.GetType();
//# RedirectToLogin when not IsAuthenticated
var authState = await AuthStateTask;
if (!(authState.User.Identity?.IsAuthenticated ?? false))
{
/// 401 未登入
navSvc.NavigateTo($"/login?returnUrl={Uri.EscapeDataString(navSvc.Uri)}", forceLoad: true);
return;
}
//※ 用 AuthorizeAsync 觸發 AuthPageRequirement 檢驗,帶入資源:pageType。
var result = await authSvc.AuthorizeAsync(authState.User, pageType, new AuthPageRequirement());
if (!result.Succeeded)
{
/// 403 授權不足 禁止使用此功能
navSvc.NavigateTo($"/ErrorStatus/{403}", forceLoad: false);
return;
}
// Sucess
authing = false;
}
}
AuthPageRequirement.cs
using Microsoft.AspNetCore.Authorization;
using System.Reflection;
using Vista.Biz.AOP;
using Vista.Models;
namespace Vista.Services;
// 宣告 Authorization Requirement
// ※ 不必實作內容,只做為需求的宣示。
class AuthPageRequirement : IAuthorizationRequirement { }
// 實作 handler --- Authorization Requirement 檢驗程序
internal class AuthPageHandler(
ILogger<AuthPageHandler> _logger,
AccountService _accSvc)
: AuthorizationHandler<AuthPageRequirement>
{
// 檢驗程序實作
protected override Task HandleRequirementAsync(AuthorizationHandlerContext context, AuthPageRequirement requirement)
{
//# 未登入離開。
if (!context.User.Identity?.IsAuthenticated ?? false)
return Task.CompletedTask;
//# 取得需要資源,若非預期資源離開。
Type? pageType = context.Resource as Type;
if (pageType == null) return Task.CompletedTask;
//# 取得客製資源
// AuthPage 屬性 := @attribute[Page("DEMO013", "步步申請樣板")]
PageAttribute? pageAttr = pageType.GetCustomAttribute<PageAttribute>();
if (pageAttr == null) return Task.CompletedTask;
// 功能代碼
string funcCode = pageAttr.FuncId;
//# 開始驗證
// 是否登入者有授權的功能清單。
//if (!context.User.IsInRole(funcCode)) return Task.CompletedTask;
var authUser = _accSvc.GetAuthDataFromPool(context.User.Identity!.Name!);
if (authUser is null) return Task.CompletedTask;
if (!authUser.AuthFuncList().Contains(funcCode)) return Task.CompletedTask;
// OK
context.Succeed(requirement); // 滿足此項授權需求
_logger.LogInformation($"User [{context.User.Identity!.Name}] has checked AuthPageRequirement successfully.");
return Task.CompletedTask;
}
}
(EOF)
Last updated