Blazor允许你利用.NET平台来构建你的WebAssembly(也称为WASM)应用程序。由于有了Auth0,你还可以通过添加对认证和授权的支持来轻松地保护它们,正如本文所显示的。让我们开始吧!
构建一个Blazor WebAssembly应用程序
在前一篇文章中,你通过使用服务器托管模式建立了一个Blazor应用程序。那是一个简单的测验应用程序,它显示了一个有多个答案的问题列表,并根据你提供的正确答案给你打分。
现在你要通过使用WebAssembly托管模型来实现同样的应用程序。要了解更多关于Blazor托管模型的信息,请查看那篇提到的文章中的具体章节。
正如那篇文章所解释的,WebAssembly托管模式使你的应用程序被编译成WebAssembly并在浏览器中运行。然而,根据你项目的结构,你有两个选择来创建你的应用程序。
- 你可能只有客户端应用程序,它将调用一个现有的Web API
- 你可以同时拥有客户端应用程序和Web API应用程序。在这种情况下,Web API应用程序也会向浏览器提供Blazor WebAssembly应用程序。这个选项被称为ASP.NET Core托管。
对于这个项目,你将选择第二个选项。事实上,你将有一个客户端应用程序,它将负责显示用户界面和管理用户互动,而Web API应用程序将为客户端提供测验。
要建立这个应用程序,你需要在你的机器上安装.NET 6.0 SDK或以上版本。
你通过输入以下命令创建一个新的Blazor WebAssembly项目。
dotnet new blazorwasm -o QuizManagerClientHosted --hosted
注意:如果你想只创建客户端的应用程序,你必须省略前面命令中的
--hosted标志。
如果你看一下QuizManagerClientHosted 文件夹,你会发现如下所示的文件夹结构。
QuizManagerClientHosted
│ .gitignore
│ QuizManagerClientHosted.sln
├── Client
├── Server
└── Shared
这些文件夹中的每一个都包含一个.NET项目。虽然Client 和Server 文件夹是直接的,但你可能想知道Shared 文件夹包含什么。它包含一个类库项目,其代码由客户端和服务器端应用程序共享。在你要重新实现的应用程序的情况下,它将包含数据模型。
因此,移动到Shared 文件夹中,删除 WeatherForecasts.cs文件。在Shared 文件夹中创建一个新文件,名为 QuizItem.cs的新文件,内容如下。
// Shared/QuizItem.cs
namespace QuizManagerClientHosted.Shared;
public class QuizItem
{
public string Question { get; set; }
public List<string> Choices { get; set; }
public int AnswerIndex { get; set; }
public int Score { get; set; }
public QuizItem()
{
Choices = new List<string>();
}
}
这个类为测验的每个项目实现了模型。它提供了一个问题,一个可能的答案列表,正确答案的零基索引,以及用户给出正确答案时分配的分数。
创建服务器
移动到 Server/Controllers文件夹,并删除该 WeatherForecastController.cs文件。然后,在同一文件夹中添加一个新文件,名为 QuizController.cs的新文件,并在其中放入以下代码。
// Server/Controllers/QuizController.cs
using QuizManagerClientHosted.Shared;
using Microsoft.AspNetCore.Mvc;
namespace QuizManagerClientHosted.Server.Controllers;
[ApiController]
[Route("[controller]")]
public class QuizController : ControllerBase
{
private static readonly List<QuizItem> Quiz = new List<QuizItem> {
new QuizItem
{
Question = "Which of the following is the name of a Leonardo da Vinci's masterpiece?",
Choices = new List<string> {"Sunflowers", "Mona Lisa", "The Kiss"},
AnswerIndex = 1,
Score = 3
},
new QuizItem
{
Question = "Which of the following novels was written by Miguel de Cervantes?",
Choices = new List<string> {"The Ingenious Gentleman Don Quixote of La Mancia", "The Life of Gargantua and of Pantagruel", "One Hundred Years of Solitude"},
AnswerIndex = 0,
Score = 5
}
};
[HttpGet]
public List<QuizItem> Get()
{
return Quiz;
}
}
正如你所看到的,这是你在Blazor服务器应用程序中创建的QuizService 类的Web API版本。你注意到用几个QuizItem 实例初始化了Quiz 静态变量,并定义了返回该变量的 Get()定义了返回该变量的动作。
关于如何在ASP.NET Core中创建Web API的更多信息,请参阅本教程。
创建客户端
为了创建Blazor客户端应用程序,请移至 Client/Pages文件夹,并删除 Counter.razor和 FetchData.razor文件。然后,在该文件夹中添加一个名为 QuizViewer.razor的文件,内容如下。
// Client/Pages/QuizViewer.cs
@page "/quizViewer"
@using QuizManagerClientHosted.Shared
@inject HttpClient Http
<h1>Take your quiz!</h1>
<p>Your current score is @currentScore</p>
@if (quiz == null)
{
<p><em>Loading...</em></p>
}
else
{
int quizIndex = 0;
@foreach (var quizItem in quiz)
{
<section>
<h3>@quizItem.Question</h3>
<div class="form-check">
@{
int choiceIndex = 0;
quizScores.Add(0);
}
@foreach (var choice in quizItem.Choices)
{
int currentQuizIndex = quizIndex;
<input class="form-check-input"
type="radio"
name="@quizIndex"
value="@choiceIndex"
@onchange="@((eventArgs) => UpdateScore(Convert.ToInt32(eventArgs.Value), currentQuizIndex))" />@choice
<br>
choiceIndex++;
}
</div>
</section>
quizIndex++;
}
}
@code {
List<QuizItem> quiz;
List<int> quizScores = new List<int>();
int currentScore = 0;
protected override async Task OnInitializedAsync()
{
quiz = await Http.GetFromJsonAsync<List<QuizItem>>("Quiz");
}
void UpdateScore(int chosenAnswerIndex, int quizIndex)
{
var quizItem = quiz[quizIndex];
if (chosenAnswerIndex == quizItem.AnswerIndex)
{
quizScores[quizIndex] = quizItem.Score;
}
else
{
quizScores[quizIndex] = 0;
}
currentScore = quizScores.Sum();
}
}
@page 指令将这个Razor组件定义为一个页面,它是一个UI元素,可以通过Blazor的路由系统中的一个地址(/quizViewer在本例中)直接到达的UI元素。然后,你有@using 指令,它提供了对上面创建的共享数据模型的访问(QuizItem.cs).@inject 指令要求依赖性注入系统获取HttpClient 类的实例。
在这些初始化之后,你会发现定义了用户界面的标记。正如你所看到的,这部分是HTML和C#代码的混合体,其目的是建立问题列表,并以单选按钮表示各自可能的答案。
该组件的最后一个块被包围在@code 指令中。这是你放置组件的逻辑的地方。在QuizViewer 组件的例子中,你有 OnInitializedAsync()和 UpdateScore()方法。第一个方法在组件初始化时被调用,它基本上是通过调用你之前创建的Web API的Quiz 端点来获得测验数据。第二个方法 UpdateScore()方法在用户点击其中一个建议的答案时被调用,它根据用户选择的答案更新分配的分数列表。在同一个方法中,当前分数的值被计算出来并分配给currentScore 。这个变量的值显示在问题列表的上方,你可以在标记中看到。
为了完成你的应用程序,将 NavMenu.razor文件夹中的 Client/Shared文件夹中的内容,替换为以下代码。
// Shared/NavMenu.razor
<div class="top-row ps-3 navbar navbar-dark">
<div class="container-fluid">
<a class="navbar-brand" href="">QuizManager</a>
<button title="Navigation menu" class="navbar-toggler" @onclick="ToggleNavMenu">
<span class="navbar-toggler-icon"></span>
</button>
</div>
</div>
<div class="@NavMenuCssClass" @onclick="ToggleNavMenu">
<nav class="flex-column">
<div class="nav-item px-3">
<NavLink class="nav-link" href="" Match="NavLinkMatch.All">
<span class="oi oi-home" aria-hidden="true"></span> Home
</NavLink>
</div>
<div class="nav-item px-3">
<NavLink class="nav-link" href="quizViewer">
<span class="oi oi-list-rich" aria-hidden="true"></span> Quiz
</NavLink>
</div>
</nav>
</div>
@code {
private bool collapseNavMenu = true;
private string? NavMenuCssClass => collapseNavMenu ? "collapse" : null;
private void ToggleNavMenu()
{
collapseNavMenu = !collapseNavMenu;
}
}
你可能已经注意到,这段代码重新定义了导航菜单,删除了指向Counter 和FetchData 组件的默认项目,并包括了一个到达QuizViewer 组件的项目。
运行你的Blazor WebAssembly应用程序
你的Blazor WebAssembly应用程序已经完成,可以运行了。
⚠️如果你使用的是Mac,请阅读!⚠️
在撰写本文时,Mac用户在通过.NET CLI运行ASP.NET Core应用程序时受到一个问题的影响。在运行.NET CLI时,你可能会得到以下对话窗口。
这是由于macOS上.NET CLI的一个已知问题。目前的解决方法是,你打开 QuizManagerClientHosted.Server.csproj文件并添加 <UseAppHost>false</UseAppHost>元素,如下图所示。
<!-- QuizManagerClientHosted.Server.csproj -->
<Project Sdk="Microsoft.NET.Sdk.Web">
<PropertyGroup>
<TargetFramework>net6.0</TargetFramework>
<Nullable>enable</Nullable>
<ImplicitUsings>enable</ImplicitUsings>
<!--👇 new element --->
<UseAppHost>false</UseAppHost>
</PropertyGroup>
<!-- ...existing elements... -->
</Project>
如果你使用Visual Studio,你不会受到这个问题的影响。
在项目的根文件夹中,键入以下命令。
dotnet run --project Server
看一下你的终端窗口,以获得你的应用程序正在监听的地址。它的形式是 https://localhost:<YOUR_PORT_NUMBER>.在我的例子中,我得到的地址是 https://localhost:7291,我将在整个文章中提到它。
从.NET 6.0开始,任何通过模板创建的ASP.NET项目都会为HTTP分配一个5000到5300之间的随机端口,为HTTPS分配7000到7300之间的随机端口。更多信息请参见本文档。
将浏览器指向你的应用程序的地址,你应该看到以下页面。
点击导航栏上的测验项目,你应该可以进行一个简单的测验,如下面的截图所示。
即使这个应用程序的外观和感觉与Blazor服务器的实现基本相同,但应用程序的架构却大不相同。在这种情况下,你的客户端被编译成WebAssembly并在浏览器中运行,而服务器端则在内置的Web服务器中运行。此外,在这种架构下,客户端和服务器用经典的HTTP请求进行交互。你可以通过使用浏览器的开发工具分析网络流量来检查这一点。
用Auth0注册Blazor WASM应用程序
现在你有了WebAssembly版本的Quiz Manager应用程序,学习如何保护它。你将使用Auth0,因为它提供了一种简单的方法来整合认证和授权,而不必处理底层技术的复杂性。要使用Auth0,你需要提供一些信息,并配置你的应用程序,使双方相互沟通。如果你还没有Auth0账户,你可以现在就注册一个免费账户。
免费试用最强大的认证平台。开始使用→
访问Auth0仪表板后,移到应用程序部分,并按照以下步骤操作。
- 单击 "创建应用程序"按钮。
- 为您的应用程序提供一个友好的名称(例如,Quiz Blazor WASM客户端),并选择单页Web应用程序作为应用程序类型。
- 最后,单击 "创建"按钮。
在你注册应用程序后,移动到设置选项卡,注意你的Auth0域和你的客户ID。然后,把这个值分配给 https://localhost:<YOUR_PORT_NUMBER>/authentication/login-callback到允许的回调URLs字段,以及 https://localhost:<YOUR_PORT_NUMBER>到允许注销的URLs字段。将 <YOUR_PORT_NUMBER>占位符替换为分配给你的应用程序的实际端口号。在我的例子中,这些值是 https://localhost:7291/authentication/login-callback和 https://localhost:7291.
第一个值告诉Auth0在用户验证后要回调哪个URL。第二个值告诉Auth0,用户注销后应该被重定向到哪个URL。
最后,单击 "保存更改"按钮来应用它们。
添加对认证的支持
现在,你需要配置你的Blazor项目,应用一些变化,使其了解Auth0。
配置您的Blazor应用程序
因此,移动到 Client/wwwroot文件夹,并创建一个 appsettings.json文件,内容如下。
{
"Auth0": {
"Authority": "https://<YOUR_AUTH0_DOMAIN>",
"ClientId": "<YOUR_CLIENT_ID>"
}
}
替换占位符 <YOUR_AUTH0_DOMAIN>和 <YOUR_CLIENT_ID>用从Auth0仪表板上获取的相应数值替换。
添加对认证的支持
现在,通过在Client 文件夹中运行以下命令,将认证包添加到Blazor客户端项目中。
dotnet add package Microsoft.AspNetCore.Components.WebAssembly.Authentication
添加软件包后,仍在Client 文件夹中,编辑 Program.cs文件,修改其内容如下。
// Client/Program.cs
using Microsoft.AspNetCore.Components.Web;
using Microsoft.AspNetCore.Components.WebAssembly.Hosting;
using QuizManagerClientHosted.Client;
var builder = WebAssemblyHostBuilder.CreateDefault(args);
builder.RootComponents.Add<App>("#app");
builder.RootComponents.Add<HeadOutlet>("head::after");
builder.Services.AddScoped(sp => new HttpClient { BaseAddress = new Uri(builder.HostEnvironment.BaseAddress) });
// 👇 new code
builder.Services.AddOidcAuthentication(options =>
{
builder.Configuration.Bind("Auth0", options.ProviderOptions);
options.ProviderOptions.ResponseType = "code";
});
// 👆 new code
await builder.Build().RunAsync();
你添加了对 AddOidcAuthentication()的特定选项。特别是,你指定了使用Auth0 部分的参数。 appsettings.json配置文件中的参数。另外,你还指定了你要使用的认证和授权流程的类型;在这种特定情况下,推荐使用授权代码流程。
要在你的应用程序中完成认证支持的实施,请打开 index.html文件夹下的 Client/wwwroot文件夹下的文件,并将引用添加到 AuthenticationService.js脚本,如下图所示。
<!-- Client/wwwroot/index.html -->
<!DOCTYPE html>
<html>
<!-- existing markup -->
<body>
<!-- existing markup -->
<script src="_content/Microsoft.AspNetCore.Components.WebAssembly.Authentication/AuthenticationService.js"></script>
<!--👆 new addition -->
<script src="appsettings.jsons_framework/blazor.webassembly.js"></script>
</body>
</html>
这个脚本负责在WebAssembly客户端执行认证操作。
调整你的Blazor应用程序的用户界面
在这一点上,你为你的Blazor应用准备了支持认证的基础设施。现在你需要对用户界面做一些改变。
第一步是启用对授权Razor组件的支持。所以,打开 _Imports.razor文件,并在Client 文件夹中添加一个引用到 Microsoft.AspNetCore.Components.Authorization和 Microsoft.AspNetCore.Authorization命名空间的引用。该文件的内容将如下所示。
@* Client/_Imports.razor *@
@using System.Net.Http
@using System.Net.Http.Json
@using Microsoft.AspNetCore.Components.Authorization //👈 new addition
@using Microsoft.AspNetCore.Authorization //👈 new addition
@using Microsoft.AspNetCore.Components.Forms
@using Microsoft.AspNetCore.Components.Routing
@using Microsoft.AspNetCore.Components.Web
@using Microsoft.AspNetCore.Components.Web.Virtualization
@using Microsoft.AspNetCore.Components.WebAssembly.Http
@using Microsoft.JSInterop
@using QuizManagerClientHosted.Client
@using QuizManagerClientHosted.Client.Shared
然后,打开同一文件夹中的 App.razor文件,并将其内容替换为以下内容。
<!-- Client/App.razor -->
<CascadingAuthenticationState>
<Router AppAssembly="@typeof(App).Assembly">
<Found Context="routeData">
<AuthorizeRouteView RouteData="@routeData" DefaultLayout="@typeof(MainLayout)">
<Authorizing>
<p>Determining session state, please wait...</p>
</Authorizing>
<NotAuthorized>
<h1>Sorry</h1>
<p>You're not authorized to reach this page. You need to log in.</p>
</NotAuthorized>
</AuthorizeRouteView>
<FocusOnNavigate RouteData="@routeData" Selector="h1" />
</Found>
<NotFound>
<PageTitle>Not found</PageTitle>
<LayoutView Layout="@typeof(MainLayout)">
<p role="alert">Sorry, there's nothing at this address.</p>
</LayoutView>
</NotFound>
</Router>
</CascadingAuthenticationState>
你使用AuthorizeRouteView Blazor组件,根据用户的认证状态定制内容。CascadingAuthenticationState 组件将把当前的认证状态传播给内部组件,以便它们能够一致地进行工作。
下一步是创建一个新的Razor组件,允许用户登录并在认证时看到他们的名字。因此,创建一个新的文件,名为 AccessControl.razor的新文件,内容如下 Client/Shared文件夹中创建一个新文件,内容如下。
@* Client/Shared/AccessControl.razor *@
@using Microsoft.AspNetCore.Components.Authorization
@using Microsoft.AspNetCore.Components.WebAssembly.Authentication
@inject NavigationManager Navigation
@inject SignOutSessionStateManager SignOutManager
<AuthorizeView>
<Authorized>
Hello, @context.User.Identity.Name!
<a href="#" @onclick="BeginSignOut">Log out</a>
</Authorized>
<NotAuthorized>
<a href="authentication/login">Log in</a>
</NotAuthorized>
</AuthorizeView>
@code{
private async Task BeginSignOut(MouseEventArgs args)
{
await SignOutManager.SetSignOutState();
Navigation.NavigateTo("authentication/logout");
}
}
该组件使用AuthorizeView 组件,根据用户的认证状态显示不同的内容。基本上,当用户没有被认证时,它会显示登录链接。当用户被认证时,它显示用户的名字和注销链接。
注意用户点击注销链接时被重定向到的URL (authentication/logout).你将在稍后了解该URL的情况。
现在,打开 MainLayout.razor``Shared 文件,并在 "*关于 "*链接之前添加AccessControl 组件。最后的代码应该如下所示。
@* Client/Shared/MainLayout.razor *@
@inherits LayoutComponentBase
<div class="page">
<div class="sidebar">
<NavMenu />
</div>
<main>
<div class="top-row px-4">
<AccessControl /> //👈 new code
<a href="https://docs.microsoft.com/aspnet/" target="_blank">About</a>
</div>
<article class="content px-4">
@Body
</article>
</main>
</div>
当你在Auth0注册你的Blazor应用时,你指定了几个URL作为登录回调和注销的允许URL。为了管理这些URL,你需要实现一个页面,负责处理不同的认证阶段。为了这个目的,请在Auth0中创建一个新的 Authentication.razor文件夹中的 Client/Pages文件夹中创建一个新文件,代码如下。
@* Client/Pages/Authentication.razor *@
@page "/authentication/{action}"
@using Microsoft.AspNetCore.Components.WebAssembly.Authentication
@using Microsoft.Extensions.Configuration
@inject NavigationManager Navigation
@inject IConfiguration Configuration
<RemoteAuthenticatorView Action="@Action">
<LogOut>
@{
var authority = (string)Configuration["Auth0:Authority"];
var clientId = (string)Configuration["Auth0:ClientId"];
Navigation.NavigateTo($"{authority}/v2/logout?client_id={clientId}");
}
</LogOut>
</RemoteAuthenticatorView>
@code{
[Parameter] public string Action { get; set; }
}
正如你所看到的,这个组件实现了一个包含RemoteAuthenticatorView 组件的页面。这个组件管理用户的认证状态,并在Auth0端与授权服务器进行交互。虽然登录的交互不需要任何特定的代码,但你需要管理注销的事务。事实上,根据设计,Blazor在客户端清除了你的认证状态,但并没有断开你与Auth0的连接。要在Auth0端关闭您的会话,您需要明确地调用注销端点,如上面的代码所示。
免责声明:在撰写本文时,由于一个明显的Blazor问题,注销功能似乎并不稳定。请查看Blazor项目资源库中的这个问题,以了解更多信息。
最后,你需要将Authorize 属性添加到 QuizViewer.razor来保护它免受未经授权的访问。打开 QuizViewer.razor文件夹中的Pages ,并添加属性,如下图所示。
@* Client/Pages/QuizViewer.razor *@
@page "/quizViewer"
@attribute [Authorize] //👈 new addition
@using QuizManagerClientHosted.Shared
// ... exisiting code ...
请注意,页面上的
Authorize属性的存在并不能阻止客户端调用服务器上的API。你也需要在服务器端保护API。
在这一点上,如果你的Blazor应用程序仍在运行,你可以停止它,并重新启动它,以测试认证整合。一旦应用程序运行,通过点击Quiz菜单项,您应该看到以下屏幕。
注意右上角的 "登录"。点击它,就会显示Auth0通用登录页面,并进行认证过程。认证后,你就可以访问QuizViewer页面了。
用Auth0保证API的安全
QuizViewer页面上显示的数据是从服务器项目中实现的API加载的。 /quiz在服务器项目中实现的API。这个API没有被保护,所以任何客户端都可以访问它。事实上,Blazor的WASM客户端能够毫无问题地访问它。然而,在生产就绪的情况下,你需要保护API以防止未经授权的访问。虽然API的安全实现不在本教程的范围内,但你需要对服务器项目中的API进行一些修改,以确保其安全。
如果你想了解更多关于保护.NET中的Web API的信息,请查看这篇文章。
注册API
就像你对Blazor WASM应用程序所做的那样,你需要向Auth0注册API。因此,请将您的浏览器指向Auth0仪表板,移至API部分,并按照以下步骤操作。
- 单击 "创建API"按钮。
- 为你的API提供一个友好的名称(例如,Quiz API)和一个独特的标识符(也称为受众),以URL格式(例如,quizapi.com)。
- 把签名算法留给RS256,然后点击创建按钮。
这样,Auth0就知道了你的网络API,并将允许你控制访问。
保护API
在Server 文件夹下的服务器项目中,打开 appsettings.json并修改其内容如下。
{
"Logging": {
"LogLevel": {
"Default": "Information",
"Microsoft.AspNetCore": "Warning"
}
},
"AllowedHosts": "*",
"Auth0": {
"Domain": "<YOUR_AUTH0_DOMAIN>",
"Audience": "<YOUR_API_IDENTIFIER>"
}
}
将 <YOUR_AUTH0_DOMAIN>占位符替换为你在Blazor WASM客户端中使用的Auth0域值。同时,将 <YOUR_API_IDENTIFIER>占位符替换为您在Auth0仪表板中为您的API定义的唯一标识符:它应该是 https://quizapi.com,如果你保留了建议的值。
还是在Server 文件夹中,运行以下命令来安装处理授权过程的库。
dotnet add package Microsoft.AspNetCore.Authentication.JwtBearer
然后,打开 Program.cs文件并应用如下所示的更改。
// Server/Startup.cs
// ... exisiting code ...
using Microsoft.AspNetCore.Authentication.JwtBearer;
//👆 new code
var builder = WebApplication.CreateBuilder(args);
// Add services to the container.
//👇 new code
builder.Services.AddAuthentication(JwtBearerDefaults.AuthenticationScheme)
.AddJwtBearer(JwtBearerDefaults.AuthenticationScheme, c =>
{
c.Authority = $"https://{builder.Configuration["Auth0:Domain"]}";
c.TokenValidationParameters = new Microsoft.IdentityModel.Tokens.TokenValidationParameters
{
ValidAudience = builder.Configuration["Auth0:Audience"],
ValidIssuer = builder.Configuration["Auth0:Domain"]
};
});
//👆 new code
builder.Services.AddControllersWithViews();
builder.Services.AddRazorPages();
var app = builder.Build();
// ... exisiting code ...
app.UseRouting();
//👇 new code
app.UseAuthentication();
app.UseAuthorization();
//👆 new code
app.MapRazorPages();
// ... exisiting code ...
你添加了对 Microsoft.AspNetCore.Authentication.JwtBearer命名空间的引用,并添加了配置服务器通过Auth0处理授权过程的语句。最后,你配置了中间件来处理认证和授权。
现在,打开 QuizController.cs文件夹中的 Server/Controllers文件夹中的文件,并应用以下更改。
// Server/Controllers/QuizController.cs
using QuizManagerClientHosted.Shared;
using Microsoft.AspNetCore.Mvc;
using Microsoft.AspNetCore.Authorization; //👈 new addition
namespace QuizManagerClientHosted.Server.Controllers;
[ApiController]
[Route("[controller]")]
[Authorize] //👈 new addition
public class QuizController : ControllerBase
{
// ... existing code ...
}
你添加了对 Microsoft.AspNetCore.Authorization命名空间,并用Authorize 属性装饰了QuizController 类。
记住:如果你想深入了解如何用Auth0保护你的API,请阅读这篇文章。
现在你的API被保护了。要检查一切是否按预期工作,请移动到项目的根部并重新启动它。然后,登录到应用程序,点击 "测验"菜单项。这一次你不应该再显示测验数据了。你的屏幕应该是下面的样子。
如果你看一下你的浏览器的开发工具的网络部分,你会发现,调用到 /quiz端点的调用会得到一个HTTP 401状态代码,如下面的例子。
这证实了服务器阻止了对API的未授权访问。
"了解如何用Blazor WebAssembly调用受保护的API。"
调用受保护的API
为了使您的Blazor WASM应用程序能够访问受保护的API,您需要从Auth0获得一个访问令牌,并在调用API时提供该令牌。你可能会想写一些代码,在你向服务器发出HTTP请求时附加上这个令牌。然而,你可以以一种直接的方式将访问令牌的附件集中到你的API调用中。
创建HTTP客户端
首先,移动到Client 文件夹,用以下命令安装 Microsoft.Extensions.Http包,并使用以下命令。
dotnet add package Microsoft.Extensions.Http
这个包允许你创建命名的HTTP客户端并自定义其行为。在你的案例中,你将创建一个HTTP客户端,在每个HTTP请求中自动附加一个访问令牌。
打开 Program.cs文件夹中的Client ,并添加一个对 Microsoft.AspNetCore.Components.WebAssembly.Authentication如下图所示。
// Client/Program.cs
using Microsoft.AspNetCore.Components.Web;
using Microsoft.AspNetCore.Components.WebAssembly.Hosting;
using QuizManagerClientHosted.Client;
//👇 new addition
using Microsoft.AspNetCore.Components.WebAssembly.Authentication;
// ... existing code ...
在同一个文件中,应用下面代码片断中指出的变化。
// Client/Program.cs
// ... existing code ...
builder.RootComponents.Add<HeadOutlet>("head::after");
//👇 old code
//builder.Services.AddScoped(sp => new HttpClient { BaseAddress = new Uri(builder.HostEnvironment.BaseAddress) });
//👇 new code
builder.Services.AddHttpClient("ServerAPI",
client => client.BaseAddress = new Uri(builder.HostEnvironment.BaseAddress))
.AddHttpMessageHandler<BaseAddressAuthorizationMessageHandler>();
builder.Services.AddScoped(sp => sp.GetRequiredService<IHttpClientFactory>()
.CreateClient("ServerAPI"));
//👆 new code
// ... existing code ...
你用两行代码替换了现有的创建HTTP客户端的那行代码。该 AddHttpClient()方法定义了一个名为HttpClient 的实例(ServerAPI),将当前服务器的地址作为请求资源时使用的基本地址。同时,BaseAddressAuthorizationMessageHandler 类被添加到HttpClient 实例中作为 HTTP 消息处理程序。这个类是由 Microsoft.AspNetCore.Components.WebAssembly.Authentication命名空间提供的,负责将访问令牌附加到任何HTTP请求的应用程序的基本URI。
实际的HttpClient 实例是由 CreateClient()``IHttpClientFactory 方法创建的。
指定API受众
现在,打开 appsettings.json文件夹中的 Client/wwwroot文件夹,添加Audience 元素,如下图所示。
{
"Auth0": {
"Authority": "https://<YOUR_AUTH0_DOMAIN>",
"ClientId": "<YOUR_CLIENT_ID>",
"Audience": "<YOUR_API_IDENTIFIER>"
}
}
将 <YOUR_API_IDENTIFIER>占位符替换为你在Auth0仪表板中为你的API定义的唯一标识符(例如。 https://quizapi.com).
现在,回到 Client/Program.cs文件中,应用下面强调的变化。
// Client/Program.cs
// ... existing code ...
builder.Services.AddOidcAuthentication(options =>
{
builder.Configuration.Bind("Auth0", options.ProviderOptions);
options.ProviderOptions.ResponseType = "code";
//👇 new code
options.ProviderOptions.AdditionalProviderParameters.Add("audience", builder.Configuration["Auth0:Audience"]);
});
await builder.Build().RunAsync();
你添加了一个额外的audience 参数,让Auth0知道你想调用由Audience 设置值确定的API。
进行调用
在这个全局配置之后,你可以调用你的Web API的quiz 端点。所以,打开 QuizViewer.razor``Client\Pages 文件,并对其内容做如下修改。
@* Client/Pages/QuizViewer.razor *@
@page "/quizViewer"
@attribute [Authorize]
@using QuizManagerClientHosted.Shared
//👇 new code
@using Microsoft.AspNetCore.Components.WebAssembly.Authentication
//👆 new code
@inject HttpClient Http
// ... existing code ...
@code {
// ... existing code ...
protected override async Task OnInitializedAsync()
{
//👇 changed code
try
{
quiz = await Http.GetFromJsonAsync<List<QuizItem>>("quiz");
}
catch (AccessTokenNotAvailableException exception)
{
exception.Redirect();
}
//👆 changed code
}
// ... existing code ...
}
你导入了 Microsoft.AspNetCore.Components.WebAssembly.Authentication命名空间。然后,你简单地安排了 OnInitializedAsync()方法,用一个try-catch语句来包装它。
应用这些变化后,重新启动你的应用程序,登录,并尝试移动到测验页面。这一次你应该能够访问你的受保护的API,并显示测验页面。
回顾总结
本教程指导你通过使用Auth0来创建和保护一个Blazor WebAssembly应用程序。你学会了如何建立一个简单的Blazor WebAssembly应用程序和一些Razor组件。你经历了向Auth0注册你的应用程序并使其支持认证的过程。最后,你保护了由你的应用程序的服务器端托管的API,并通过访问令牌调用该API。
本教程中保护的应用程序的完整源代码可以从GitHub仓库下载。