本文介紹 ASP.NET Core 對 Blazor Hybrid 應用中的安全設定和管理及 ASP.NET Core Identity 的支援。
Blazor Hybrid 應用中的身份驗證由本機平臺庫處理,因為後者提供了瀏覽器沙盒無法給予的經過增強的安全保證。 本機應用的身份驗證使用特定於作業系統的機制或通過聯合協定,如 OpenID Connect (OIDC)。 按照針對應用選擇的標識提供者指南進行操作,然後使用本文中的指南進一步整合標識與 Blazor。
整合身份驗證必須為 Razor 元件和服務實現以下目標:
Microsoft.AspNetCore.Components.Authorization
包中的抽象,例如 AuthorizeView。Masa Blazor
的模板,如果已經安裝則忽略dotnet new install Masa.Template::1.0.0-rc.2
建立專案Photino
專案模板
新增Microsoft.AspNetCore.Components.Authorization
NuGet包到專案檔案中
<PackageReference Include="Microsoft.AspNetCore.Components.Authorization" Version="7.0.5" />
建立CustomAuthenticationStateProvider
然後繼承AuthenticationStateProvider
CustomAuthenticationStateProvider.cs
程式碼檔案
using Microsoft.AspNetCore.Components.Authorization;
using System.Security.Claims;
namespace MasaBlazorApp1;
public class CustomAuthenticationStateProvider : AuthenticationStateProvider
{
private ClaimsPrincipal User { get; set; }
public override Task<AuthenticationState> GetAuthenticationStateAsync()
{
if (User != null)
{
return Task.FromResult(new AuthenticationState(User));
}
var identity = new ClaimsIdentity();
User = new ClaimsPrincipal(identity);
return Task.FromResult(new AuthenticationState(User));
}
public void AuthenticateUser(string emailAddress)
{
var identity = new ClaimsIdentity(new[]
{
new Claim(ClaimTypes.Name, emailAddress),
}, "Custom Authentication");
User = new ClaimsPrincipal(identity);
NotifyAuthenticationStateChanged(
Task.FromResult(new AuthenticationState(User)));
}
}
在繼承AuthenticationStateProvider
方法會要重寫GetAuthenticationStateAsync
方法,用於給授權元件獲取授權資訊,
在這個程式碼當中的User
是用於儲存持久化我們的使用者資訊的如果需要對於授權修改,我們只需要修改這個自定義的處理程式即可。然後我們在CustomAuthenticationStateProvider
中還自定義了AuthenticateUser
這個是根據實際需求去實現,在目前這個程式碼中我們實現了個簡單的Name
在最後有一個NotifyAuthenticationStateChanged
的呼叫,NotifyAuthenticationStateChanged
是幹啥的?NotifyAuthenticationStateChanged也是AuthenticationStateProvider
提供的方法,核心功能是用於通知我們的授權狀態被修改。
開啟Program.cs
然後注入我們的CustomAuthenticationStateProvider
服務,在新增注入CustomAuthenticationStateProvider
之前我們實現新增注入了AddAuthorizationCore
,這個需要注意。
using MasaBlazorApp1;
using Microsoft.AspNetCore.Components.Authorization;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.DependencyInjection.Extensions;
using Photino.Blazor;
internal class Program
{
[STAThread]
private static void Main(string[] args)
{
var builder = PhotinoBlazorAppBuilder.CreateDefault(args);
builder.RootComponents.Add<App>("#app");
builder.Services.AddMasaBlazor();
builder.Services.AddAuthorizationCore();
builder.Services.TryAddSingleton<AuthenticationStateProvider, CustomAuthenticationStateProvider>();
var app = builder.Build();
app.MainWindow
.SetTitle("Photino Blazor Sample");
AppDomain.CurrentDomain.UnhandledException += (sender, error) =>
{
};
app.Run();
}
}
然後繼續開啟我們的App.razor
新增授權相關元件,修改之前在_Imports.razor
新增以下參照
@using Microsoft.AspNetCore.Components.Authorization
新增未授權時顯示的元件Shared/Login.razor
,元件提供了一個輸入框和一個按鈕,輸入框輸入使用者名稱,按鈕則使用我們自定義的CustomAuthenticationStateProvider
中提供的AuthenticateUser
方法,用於更新授權資訊從而重新整理到授權的元件當中。
@inject AuthenticationStateProvider AuthenticationStateProvider
<MTextField @bind-Value="_userName" />
<MButton @onclick="SignIn">登入</MButton>
@code {
private string _userName = "賬號";
private void SignIn()
{
((CustomAuthenticationStateProvider)AuthenticationStateProvider)
.AuthenticateUser(_userName);
}
}
App.razor
檔案
@namespace MasaBlazorApp1
<CascadingAuthenticationState>
<Router AppAssembly="@typeof(App).Assembly">
<Found Context="routeData">
<AuthorizeView>
<Authorized>
<RouteView RouteData="@routeData" DefaultLayout="@typeof(MainLayout)">
</RouteView>
</Authorized>
<NotAuthorized>
<Login/>
</NotAuthorized>
</AuthorizeView>
<FocusOnNavigate RouteData="@routeData" Selector="h1"/>
</Found>
<NotFound>
<LayoutView Layout="@typeof(MainLayout)">
<p role="alert">Sorry, there's nothing at this address.</p>
</LayoutView>
</NotFound>
</Router>
</CascadingAuthenticationState>
App.razor
檔案在Router
中新增了AuthorizeView
元件,用於顯示授權顯示的元件和未授權顯示的元件。當有授權的情況下我們將使用預設的MainLayout
佈局,如果是未授權我們將實現Login
,需要注意的是我們在元件最外層新增了CascadingAuthenticationState
這個是核心元件,
CascadingAuthenticationState.cs
的反編譯以後的程式碼,在元件當中注入了AuthenticationStateProvider
然後在進入OnInitialized
事件的時候對於AuthenticationStateProvider
提供的AuthenticationStateChanged
事件進行了監聽,這個也就對應到了上面提到的NotifyAuthenticationStateChanged
,當呼叫到NotifyAuthenticationStateChanged
的時候會觸發到AuthenticationStateChanged
的事件,然後會觸發元件的OnAuthenticationStateChanged
方法,進行授權狀態更新,這個也就是核心的元件。
#line hidden
using System;
using System.Collections.Generic;
using System.Linq;
using System.Threading.Tasks;
using Microsoft.AspNetCore.Components;
public partial class CascadingAuthenticationState : global::Microsoft.AspNetCore.Components.ComponentBase, IDisposable
{
#pragma warning disable 1998
protected override void BuildRenderTree(global::Microsoft.AspNetCore.Components.Rendering.RenderTreeBuilder __builder)
{
__builder.OpenComponent<global::Microsoft.AspNetCore.Components.CascadingValue<System.Threading.Tasks.Task<AuthenticationState>>>(0);
__builder.AddAttribute(1, "Value", global::Microsoft.AspNetCore.Components.CompilerServices.RuntimeHelpers.TypeCheck<System.Threading.Tasks.Task<AuthenticationState>>(
#nullable restore
#line 4 "D:\a\_work\1\s\src\Components\Authorization\src\CascadingAuthenticationState.razor"
_currentAuthenticationStateTask
#line default
#line hidden
#nullable disable
));
__builder.AddAttribute(2, "ChildContent", (global::Microsoft.AspNetCore.Components.RenderFragment)(
#nullable restore
#line 4 "D:\a\_work\1\s\src\Components\Authorization\src\CascadingAuthenticationState.razor"
ChildContent
#line default
#line hidden
#nullable disable
));
__builder.CloseComponent();
}
#pragma warning restore 1998
#nullable restore
#line 6 "D:\a\_work\1\s\src\Components\Authorization\src\CascadingAuthenticationState.razor"
private Task<AuthenticationState>? _currentAuthenticationStateTask;
/// <summary>
/// The content to which the authentication state should be provided.
/// </summary>
[Parameter]
public RenderFragment? ChildContent { get; set; }
protected override void OnInitialized()
{
AuthenticationStateProvider.AuthenticationStateChanged += OnAuthenticationStateChanged;
_currentAuthenticationStateTask = AuthenticationStateProvider
.GetAuthenticationStateAsync();
}
private void OnAuthenticationStateChanged(Task<AuthenticationState> newAuthStateTask)
{
_ = InvokeAsync(() =>
{
_currentAuthenticationStateTask = newAuthStateTask;
StateHasChanged();
});
}
void IDisposable.Dispose()
{
AuthenticationStateProvider.AuthenticationStateChanged -= OnAuthenticationStateChanged;
}
#line default
#line hidden
#nullable disable
[global::Microsoft.AspNetCore.Components.InjectAttribute] private AuthenticationStateProvider AuthenticationStateProvider { get; set; }
}
}
啟動專案檢視具體效果
預設進入登入介面,由於我們並沒有授權資訊,所以進入這個介面,然後我們隨便輸入一些內容點選登入。
當我們點選了登入按鈕以後我們就進入到了MainLayout
當中並且進入了首頁。
對於授權Blazor由於模式都有所差異。
授權檔案:ASP.NET Core Blazor Hybrid 身份驗證和授權
來著token的分享
Blazor交流群:452761192