NET8 Blazor Web App 進階授權檢查與問題(BUG)
NET8, @attribute [Authorize], CustomAuthenticationStateProvider, AuthorizeView
引言
NET8 實作 Blazor Web App 過程發現一個授權檢查的bug。有進行加值授權檢驗的 @page
在【刷新頁面】時會出現 404
。
發生情境
用<F5>
或刷新指令【刷新頁面】都會出現 404 錯誤。 若由登入畫面開始“正常使用”不會出現這個問題。難怪偶有斷斷續續回報此問題但一直找不出原因。
若用 @attribute [Authorize]
基本檢驗 IsAuthenticated
則正常。
但若有在 CustomAuthenticationStateProvider
加值授權,這些加值的授權在【刷新頁面】時不會被加入, 導致不管是 Policy-base 或 Role-base 的加值授權檢查都會失敗! => 內部未知錯誤處過程 => 404。
@attribute [Authorize("AuthPage")] // Policy-base
@attribute [Authorize(Roles = "EXT-ROLE1,EXT-ROLE2")] // Role-base
解法方案
用 AuthorizeView
執行加值授權檢查就正常了。因為 AuthorizeView 的授權資料一定會經過 AuthenticationStateProvider 加值。
解法一:直接使用 AuthorizeView
AuthorizeView
以 Policy-base 為例。
直接使用 AuthorizeView 包裝 page。缺點就是需再包裝一層。 如下:
@page "/demo013"
@attribute [Page("DEMO013", "展示功能13")]
<AuthorizeView Policy="AuthPage" Resource=@this>
@* ...page content... *@
</AuthorizeView>
@code {
...略...
}
解法二:客製 AuthorizePage 元件
客製 AuthorizePage 元件執行加值授權檢查。以 AuthorizeView 為基底實作。
@page "/demo013"
@attribute [Page("DEMO013", "展示功能13")]
@* 取代 @attribute [Authorize("AuthPage")]。因為 F5 刷新會 => 404! *@
<AuthorizePage Page=@this />
@* ...page content... *@
@code {
...略...
}
AuthorizePage 客製元件
@using AsvtTPL.Components.Account.Shared
@inject NavigationManager navSvc
@*
* 取代 @attribute [Authorize("AuthPage")]。
* 因為 F5 刷新會 => 404!
* 注意:必需搭配 AOP.PageAttribute 宣示。例:
* ```
* @attribute [Page("DEMO013", "展示功能13")]
* ```
*@
<AuthorizeView Policy="AuthPage" Resource=@pageType>
@* <Authorized>
<h1 style="color:green;">允許使用(for debug)</h1>
</Authorized> *@
<Authorizing>
@* 授權檢查中 *@
<MudProgressLinear Color=Color.Error Indeterminate />
</Authorizing>
<NotAuthorized>
@* 403 授權不足 禁止使用此功能 *@
<RedirectToErrorStatus StatusCode=403 />
</NotAuthorized>
</AuthorizeView>
@code {
[CascadingParameter] Task<AuthenticationState> AuthStateTask { get; set; } = default!;
/// <summary>
/// 必需一開始就指定
/// </summary>
[Parameter, EditorRequired] public ComponentBase Page { get; set; } = default!;
Type pageType = default!;
protected override void OnInitialized()
{
pageType = Page.GetType();
}
protected override async Task OnParametersSetAsync()
{
//# RedirectToLogin when not IsAuthenticated.
var authState = await AuthStateTask;
if (!(authState.User.Identity?.IsAuthenticated ?? false))
navSvc.NavigateTo($"/login?returnUrl={Uri.EscapeDataString(navSvc.Uri)}", forceLoad: true);
/// 或 => 401
}
}
RedirectToErrorStatus 驗證失敗就轉址 403
@inject NavigationManager nvaSvc
@code {
[Parameter] public int StatusCode { get; set; }
protected override void OnInitialized()
{
nvaSvc.NavigateTo($"/ErrorStatus/{StatusCode}", forceLoad: false);
}
}
相關 Policy-base 檢驗程式碼
using Microsoft.AspNetCore.Authorization;
using System.Reflection;
namespace YourProject.Services;
// 宣告 Authorization Requirement
// ※ 不必實作內容,只做為需求的宣示。
class AuthPageRequirement : IAuthorizationRequirement { }
// 實作 handler --- Authorization Requirement 檢驗程序
internal class AuthPageHandler(ILogger<AuthPageHandler> _logger)
: 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", "展示功能13")]
PageAttribute? pageAttr = pageType.GetCustomAttribute<PageAttribute>();
if (pageAttr == null) return Task.CompletedTask;
// 功能代碼
string funcCode = pageAttr.FuncId;
//# 開始驗證
// 是否登入者有授權的功能清單。
if (!context.User.IsInRole(funcCode)) return Task.CompletedTask;
// OK
context.Succeed(requirement); // 滿足此項授權需求
_logger.LogInformation($"User [{context.User.Identity!.Name}] has checked AuthPageRequirement successfully.");
return Task.CompletedTask;
}
}
別忘了在 Program.cs 註冊
var builder = WebApplication.CreateBuilder(args);
// Add services to the container.
...略...
builder.Services.AddAuthentication(...);
// 註冊 客製 Policy 與滿足它所要的需求 Authorization Requirement
builder.Services.AddAuthorization(options =>
{
options.AddPolicy("AuthPage", policy => policy.Requirements.Add(new AuthPageRequirement()));
});
// 註冊 客製化授權需求 Authorization Requirement。
// ※ 將自動觸發對應的檢驗程序
builder.Services.AddSingleton<IAuthorizationHandler, AuthPageHandler>();
...略...
var app = builder.Build();
// Configure the HTTP request pipeline.
...略...
參考文章
(EOF)
Last updated