Blazor HyBrid 授權講解

2023-05-31 18:03:31

Blazor HyBrid 授權講解

本文介紹 ASP.NET Core 對 Blazor Hybrid 應用中的安全設定和管理及 ASP.NET Core Identity 的支援。

Blazor Hybrid 應用中的身份驗證由本機平臺庫處理,因為後者提供了瀏覽器沙盒無法給予的經過增強的安全保證。 本機應用的身份驗證使用特定於作業系統的機制或通過聯合協定,如 OpenID Connect (OIDC)。 按照針對應用選擇的標識提供者指南進行操作,然後使用本文中的指南進一步整合標識與 Blazor。

整合身份驗證必須為 Razor 元件和服務實現以下目標:

準備工作

  • 安裝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" />
    

自定義AuthenticationStateProvider處理程式

建立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