.NET MAUI Blazor Hybrid - 自定义身份验证实现

[删除(380066935@qq.com或微信通知)]

更好的阅读体验请查看原文:https://www.mitchelsellers.com/blog/article/net-maui-blazor-hybrid-custom-authentication-implementation

最近,我决定深入研究.NET MAUI Blazor Hybrid的世界,看看它与我已经习惯的传统Xamarin.Forms开发相比如何。初始设置的过程非常棒,我发现触手可及的力量令人印象深刻。但是,我很惊讶我找不到满足我需求的可靠身份验证解决方案,所以我想我会在这里分享它。

免責聲明这篇博文是关于预览版软件的,特别是 MAUI 预览版 14,从 2022 年 <> 月开始,一旦 MAUI 完全发布,此信息可能会发生变化,或者下面的实现/过程将无法用于生产版本。

我的目标

我正在测试的移动应用程序的整体架构非常简单。现有的移动应用程序提供登录表单,并在 .NET Core 后端调用 API。API 返回 JWT 以供将来的 API 调用,移动应用程序将其存储在安全存储中。我想在我的 Blazor 混合应用程序中保留此功能;但是,我还希望完全支持在我的 Razor 组件中使用授权来控制用户看到的内容;否则,我会编写一堆自己的流程。

听起来很简单,对吧?嗯,是的,但试图弄清楚“如何”是复杂的部分。

我的解决方案

我能够以最小的努力完成这项工作。但是,我必须将来自多个来源的信息拼凑在一起,因此我将在此处记录我的步骤。

其他 NuGet 包

在完成其余步骤之前,我需要将以下 NuGet 包添加到我的项目中。

  • Microsoft.AspNetCore.Authorization
  • Microsoft.AspnetCore.Components.Authorization
  • Microsoft.AspNetCore.Components.WebAssembly.Authentication

创建了一个重定向到登录.razor 组件

添加此 Razor 组件是为了管理未经身份验证的用户重定向到登录页面,并添加到项目的“/shared”文件夹中。

重定向到登录.razor
@inject NavigationManager navigationManager
        <div class="loader loader-bouncing"><span>Redirecting...</span></div>
        @code{
        
            protected override void OnInitialized()
            {
                navigationManager.NavigateTo("/login");
            }
        }
    

这仅在启动时重定向到 /login 页面。

更新了 Main.razor 以启用身份验证

.NET MAUI Blazor 的默认模板不包含路由器支持授权所需的元素,因此我将我的 main.razor 内容替换为以下内容。

Updated Main.razor
@using Microsoft.AspNetCore.Components.Authorization
        <Router AppAssembly="@GetType().Assembly">
            <Found Context="routeData">
                <AuthorizeRouteView RouteData="@routeData" DefaultLayout="@typeof(MainLayout)">
                    <Authorizing>
                        Authorizing...
                    </Authorizing>
                    <NotAuthorized>
                        <RedirectToLogin />
                    </NotAuthorized>
                </AuthorizeRouteView>
            </Found>
            <NotFound>
                <CascadingAuthenticationState>
                    <LayoutView Layout="@typeof(MainLayout)">
                        <p>Sorry, there's nothing at this address.</p>
                    </LayoutView>
                </CascadingAuthenticationState>
            </NotFound>
        </Router>
    

The purpose of this code is to set up default support for AuthorizedRoutes, and when a user isn't authorized, we direct them to our login page.

Set Pages to Require Authorization

The next step was to add the [Authorize] Attribute to my pages that required authentication which was as simple as adding the attribute, similar to the following.

Updates to Index.razor
@page "/"
        @using Microsoft.AspNetCore.Authorization
        @attribute [Authorize]
    

Creation of a Custom AuthenticationStateProvider

The real magic of the implementation was the creation of a Custom AuthenticationStateProvider, which allows me to create and share an identity for the user to show that they are authenticated. I only need to differentiate between authenticated and unauthenticated, but you can support much more. My custom implementation looked like the following:

CustomAuthenticationStateProvider Implementation
using Microsoft.AspNetCore.Components.Authorization;
        using System.Security.Claims;
        
        
        namespace FlightFiles.Mobile.Maui
        {
            public class CustomAuthenticationStateProvider : AuthenticationStateProvider
            {
                public IdentityAuthenticationStateProvider()
                {
                }
        
                public async Task Login(string token)
                {
                    await Microsoft.Maui.Essentials.SecureStorage.SetAsync("accounttoken", token);
                    NotifyAuthenticationStateChanged(GetAuthenticationStateAsync());
                }
        
                public async Task Logout()
                {
                    SecureStorage.Remove("accounttoken");
                    NotifyAuthenticationStateChanged(GetAuthenticationStateAsync());
                }
        
                public override async Task<AuthenticationState> GetAuthenticationStateAsync()
                {
                    var identity = new ClaimsIdentity();
                    try
                    {
                        var userInfo = await SecureStorage.GetAsync("accounttoken");
                        if (userInfo != null)
                        {
                            var claims = new[] { new Claim(ClaimTypes.Name, "ffUser") };
                            identity = new ClaimsIdentity(claims, "Server authentication");
                        }
                    }
                    catch (HttpRequestException ex)
                    {
                        Console.WriteLine("Request failed:" + ex.ToString());
                    }
        
                    return new AuthenticationState(new ClaimsPrincipal(identity));
                }
            }
        }
    

Usage of this is quite simple where I simply call once a user is authenticated and it stores the JWT in secure storage, which sets the user as authenticated. How you code/implement your /login page is up to you, the key here is that once authenticated, you need to set the value here and then you are set to go. Logout is as simple as removing the items from secure storage.

This is of course, an initial attempted implementation, future implementations can be as complex as needed.

电线服务

在运行应用程序并看到请求被重定向到登录组件之前,最后一步是将所需的项添加到 MauiProgram.cs,类似于以下内容。

毛伊岛计划.cs新增内容
builder.Services.AddAuthorizationCore();
        builder.Services.AddScoped<CustomAuthenticationStateProvider>();
        builder.Services.AddScoped<AuthenticationStateProvider>(s => s.GetRequiredService<CustomAuthenticationStateProvider>());
    

这仅注册身份验证(我们的自定义提供程序),并将 AuthenticationStateProvider 的预期实现设置为我们的自定义实现。

后续步骤

如果完成以下步骤并实现自己的登录页,现在可以通过外部服务进行身份验证,并在 Blazor 混合应用程序中使用该方法。仍然有很多工作要确保这确实是一种可行的方法,但我希望了解必要的构建块是有帮助的。在下面分享任何反馈。

您还可以查看此解决方案的示例实现