ASP.NET Core Blazor Hybrid 身份验证和授权
注意
此版本不是本文的最新版本。 对于当前版本,请参阅此文的 .NET 8 版本。
警告
此版本的 ASP.NET Core 不再受支持。 有关详细信息,请参阅 .NET 和 .NET Core 支持策略。 对于当前版本,请参阅此文的 .NET 8 版本。
本文介绍 ASP.NET Core 对 Blazor Hybrid 应用中的安全配置和管理及 ASP.NET Core Identity 的支持。
Blazor Hybrid 应用中的身份验证由本机平台库处理,因为后者提供了浏览器沙盒无法给予的经过增强的安全保证。 本机应用的身份验证使用特定于操作系统的机制或通过联合协议,如 OpenID Connect (OIDC)。 按照针对应用选择的 identity 提供程序指南进行操作,然后使用本文中的指南进一步将 identity 与 Blazor 集成。
集成身份验证必须为 Razor 组件和服务实现以下目标:
- 使用
Microsoft.AspNetCore.Components.Authorization
包中的抽象,例如 AuthorizeView。 - 对身份验证上下文中的更改做出回应。
- 访问由应用根据 identity 提供程序预配的凭据,例如用于执行授权 API 调用的访问令牌。
将身份验证添加到 .NET MAUI 后,WPF 或 Windows 窗体应用和用户能够成功登录和注销,将身份验证与 Blazor 集成,以使经过身份验证的用户用使用 Razor 组件和服务。 执行以下步骤:
引用
Microsoft.AspNetCore.Components.Authorization
包。注意
有关将包添加到 .NET 应用的指南,请参阅包使用工作流(NuGet 文档)中“安装和管理包”下的文章。 在 NuGet.org 中确认正确的包版本。
实现自定义 AuthenticationStateProvider,这是 Razor 组件用来访问有关经过身份验证的用户的信息并在身份验证状态发生更改时接收更新的一种抽象。
在依赖项注入容器中注册自定义身份验证状态提供程序。
.NET MAUI 应用使用 Xamarin.Essentials:Web 验证器: WebAuthenticator
类允许应用启动基于浏览器的身份验证流,这些流侦听注册到应用的特定 URL 的回调。
要获得更多指南,请查看下面的资源:
WPF 应用使用 Microsoft identity 平台与 Microsoft Entra (ME-ID) 和 AAD B2C 集成。 有关指导和示例,请参阅以下资源:
Windows 窗体应用使用 Microsoft identity 平台与 Microsoft Entra (ME-ID) 和 AAD B2C 集成。 有关更多信息,请参阅 Microsoft 身份验证库 (MSAL) 概述。
在不使用用户更改更新的情况下创建自定义 AuthenticationStateProvider
如果应用在应用启动后立即对用户进行身份验证,并且经过身份验证的用户在整个应用生存期内保持不变,则无需发送用户更改通知,并且该应用仅提供有关经过身份验证的用户的信息。 在此方案中,用户在打开应用时登录到应用,并在用户注销后再次显示登录屏幕。下面的 ExternalAuthStateProvider
是此身份验证方案的自定义 AuthenticationStateProvider 的示例实现。
注意
以下自定义 AuthenticationStateProvider 未声明命名空间,以便使代码示例适用于任何 Blazor Hybrid 应用。 但是,最佳做法是在生产应用中实现示例时提供应用的命名空间。
ExternalAuthStateProvider.cs
:
using System.Security.Claims;
using System.Threading.Tasks;
using Microsoft.AspNetCore.Components.Authorization;
public class ExternalAuthStateProvider : AuthenticationStateProvider
{
private readonly Task<AuthenticationState> authenticationState;
public ExternalAuthStateProvider(AuthenticatedUser user) =>
authenticationState = Task.FromResult(new AuthenticationState(user.Principal));
public override Task<AuthenticationState> GetAuthenticationStateAsync() =>
authenticationState;
}
public class AuthenticatedUser
{
public ClaimsPrincipal Principal { get; set; } = new();
}
以下步骤介绍了如何:
- 添加所需的命名空间。
- 将授权服务和 Blazor 抽象添加到服务集合。
- 生成服务集合。
- 解析
AuthenticatedUser
服务以设置经过身份验证的用户的声明主体。 有关详细信息,请参阅 identity 提供程序的文档。 - 返回生成的主机。
在 MauiProgram.cs
的 MauiProgram.CreateMauiApp
方法中,为 Microsoft.AspNetCore.Components.Authorization 和 System.Security.Claims 添加命名空间:
using Microsoft.AspNetCore.Components.Authorization;
using System.Security.Claims;
删除以下返回生成的 Microsoft.Maui.Hosting.MauiApp 的代码行:
- return builder.Build();
用下面的代码替换前面的代码行。 添加 OpenID/MSAL 代码以对用户进行身份验证。 有关详细信息,请参阅 identity 提供程序的文档。
builder.Services.AddAuthorizationCore();
builder.Services.TryAddScoped<AuthenticationStateProvider, ExternalAuthStateProvider>();
builder.Services.AddSingleton<AuthenticatedUser>();
var host = builder.Build();
var authenticatedUser = host.Services.GetRequiredService<AuthenticatedUser>();
/*
Provide OpenID/MSAL code to authenticate the user. See your identity provider's
documentation for details.
The user is represented by a new ClaimsPrincipal based on a new ClaimsIdentity.
*/
var user = new ClaimsPrincipal(new ClaimsIdentity());
authenticatedUser.Principal = user;
return host;
以下步骤介绍了如何:
- 添加所需的命名空间。
- 将授权服务和 Blazor 抽象添加到服务集合。
- 生成服务集合,并将生成的服务集合作为资源添加到应用的
ResourceDictionary
中。 - 解析
AuthenticatedUser
服务以设置经过身份验证的用户的声明主体。 有关详细信息,请参阅 identity 提供程序的文档。 - 返回生成的主机。
在 MainWindow
的构造函数 (MainWindow.xaml.cs
) 中,为 Microsoft.AspNetCore.Components.Authorization 和 System.Security.Claims 添加命名空间:
using Microsoft.AspNetCore.Components.Authorization;
using System.Security.Claims;
删除以下用于将生成的服务集合作为资源添加到应用的 ResourceDictionary
的代码行:
- Resources.Add("services", serviceCollection.BuildServiceProvider());
用下面的代码替换前面的代码行。 添加 OpenID/MSAL 代码以对用户进行身份验证。 有关详细信息,请参阅 identity 提供程序的文档。
serviceCollection.AddAuthorizationCore();
serviceCollection.TryAddScoped<AuthenticationStateProvider, ExternalAuthStateProvider>();
serviceCollection.AddSingleton<AuthenticatedUser>();
var services = serviceCollection.BuildServiceProvider();
Resources.Add("services", services);
var authenticatedUser = services.GetRequiredService<AuthenticatedUser>();
/*
Provide OpenID/MSAL code to authenticate the user. See your identity provider's
documentation for details.
The user is represented by a new ClaimsPrincipal based on a new ClaimsIdentity.
*/
var user = new ClaimsPrincipal(new ClaimsIdentity());
authenticatedUser.Principal = user;
以下步骤介绍了如何:
- 添加所需的命名空间。
- 将授权服务和 Blazor 抽象添加到服务集合。
- 生成服务集合,并将生成的服务集合添加到应用的服务提供商。
- 解析
AuthenticatedUser
服务以设置经过身份验证的用户的声明主体。 有关详细信息,请参阅 identity 提供程序的文档。
在 Form1
的构造函数 (Form1.cs
) 中,为 Microsoft.AspNetCore.Components.Authorization 和 System.Security.Claims 添加命名空间:
using Microsoft.AspNetCore.Components.Authorization;
using System.Security.Claims;
删除以下用于将生成的服务集合设置为应用的服务提供商的代码行:
- blazorWebView1.Services = services.BuildServiceProvider();
用下面的代码替换前面的代码行。 添加 OpenID/MSAL 代码以对用户进行身份验证。 有关详细信息,请参阅 identity 提供程序的文档。
services.AddAuthorizationCore();
services.TryAddScoped<AuthenticationStateProvider, ExternalAuthStateProvider>();
services.AddSingleton<AuthenticatedUser>();
var serviceCollection = services.BuildServiceProvider();
blazorWebView1.Services = serviceCollection;
var authenticatedUser = serviceCollection.GetRequiredService<AuthenticatedUser>();
/*
Provide OpenID/MSAL code to authenticate the user. See your identity provider's
documentation for details.
The user is represented by a new ClaimsPrincipal based on a new ClaimsIdentity.
*/
var user = new ClaimsPrincipal(new ClaimsIdentity());
authenticatedUser.Principal = user;
在使用用户更改更新的情况下创建自定义 AuthenticationStateProvider
若要在 Blazor 应用运行时更新用户,请使用以下任一方法在 AuthenticationStateProvider 实现中调用 NotifyAuthenticationStateChanged:
从 BlazorWebView
外发出身份验证更新信号(选项 1)
自定义 AuthenticationStateProvider 可以使用全局服务来发出身份验证更新的信号。 我们建议此服务提供一个 AuthenticationStateProvider 可以订阅的事件,该事件会调用 NotifyAuthenticationStateChanged。
注意
以下自定义 AuthenticationStateProvider 未声明命名空间,以便使代码示例适用于任何 Blazor Hybrid 应用。 但是,最佳做法是在生产应用中实现示例时提供应用的命名空间。
ExternalAuthStateProvider.cs
:
using System;
using System.Security.Claims;
using System.Threading.Tasks;
using Microsoft.AspNetCore.Components.Authorization;
public class ExternalAuthStateProvider : AuthenticationStateProvider
{
private AuthenticationState currentUser;
public ExternalAuthStateProvider(ExternalAuthService service)
{
currentUser = new AuthenticationState(service.CurrentUser);
service.UserChanged += (newUser) =>
{
currentUser = new AuthenticationState(newUser);
NotifyAuthenticationStateChanged(Task.FromResult(currentUser));
};
}
public override Task<AuthenticationState> GetAuthenticationStateAsync() =>
Task.FromResult(currentUser);
}
public class ExternalAuthService
{
public event Action<ClaimsPrincipal>? UserChanged;
private ClaimsPrincipal? currentUser;
public ClaimsPrincipal CurrentUser
{
get { return currentUser ?? new(); }
set
{
currentUser = value;
if (UserChanged is not null)
{
UserChanged(currentUser);
}
}
}
}
在 MauiProgram.cs
的 MauiProgram.CreateMauiApp
方法中,为 Microsoft.AspNetCore.Components.Authorization 添加命名空间:
using Microsoft.AspNetCore.Components.Authorization;
将授权服务和 Blazor 抽象添加到服务集合:
builder.Services.AddAuthorizationCore();
builder.Services.TryAddScoped<AuthenticationStateProvider, ExternalAuthStateProvider>();
builder.Services.AddSingleton<ExternalAuthService>();
在 MainWindow
的构造函数 (MainWindow.xaml.cs
) 中,为 Microsoft.AspNetCore.Components.Authorization 添加命名空间:
using Microsoft.AspNetCore.Components.Authorization;
将授权服务和 Blazor 抽象添加到服务集合:
serviceCollection.AddAuthorizationCore();
serviceCollection.TryAddScoped<AuthenticationStateProvider, ExternalAuthStateProvider>();
serviceCollection.AddSingleton<ExternalAuthService>();
在 Form1
的构造函数 (Form1.cs
) 中,为 Microsoft.AspNetCore.Components.Authorization 添加命名空间:
using Microsoft.AspNetCore.Components.Authorization;
将授权服务和 Blazor 抽象添加到服务集合:
services.AddAuthorizationCore();
services.TryAddScoped<AuthenticationStateProvider, ExternalAuthStateProvider>();
services.AddSingleton<ExternalAuthService>();
无论应用在何处对用户进行身份验证,请解析 ExternalAuthService
服务:
var authService = host.Services.GetRequiredService<ExternalAuthService>();
执行自定义 OpenID/MSAL 代码以对用户进行身份验证。 有关详细信息,请参阅 identity 提供程序的文档。 经过身份验证的用户(以下示例中的 authenticatedUser
)是基于新 ClaimsIdentity 的新 ClaimsPrincipal。
将当前用户设置为经过身份验证的用户:
authService.CurrentUser = authenticatedUser;
上述方法的替代方法是基于 System.Threading.Thread.CurrentPrincipal 设置用户的主体,而不是通过服务进行设置,从而避免使用依赖项注入容器:
public class CurrentThreadUserAuthenticationStateProvider : AuthenticationStateProvider
{
public override Task<AuthenticationState> GetAuthenticationStateAsync() =>
Task.FromResult(
new AuthenticationState(Thread.CurrentPrincipal as ClaimsPrincipal ??
new ClaimsPrincipal(new ClaimsIdentity())));
}
使用替代方法,仅向服务集合中添加授权服务 (AddAuthorizationCore) 和 CurrentThreadUserAuthenticationStateProvider
(.TryAddScoped<AuthenticationStateProvider, CurrentThreadUserAuthenticationStateProvider>()
)。
在 BlazorWebView
中处理身份验证(选项 2)
自定义 AuthenticationStateProvider 可以包含用于触发登录和注销及更新用户的其他方法。
注意
以下自定义 AuthenticationStateProvider 未声明命名空间,以便使代码示例适用于任何 Blazor Hybrid 应用。 但是,最佳做法是在生产应用中实现示例时提供应用的命名空间。
ExternalAuthStateProvider.cs
:
using System.Security.Claims;
using System.Threading.Tasks;
using Microsoft.AspNetCore.Components.Authorization;
public class ExternalAuthStateProvider : AuthenticationStateProvider
{
private ClaimsPrincipal currentUser = new ClaimsPrincipal(new ClaimsIdentity());
public override Task<AuthenticationState> GetAuthenticationStateAsync() =>
Task.FromResult(new AuthenticationState(currentUser));
public Task LogInAsync()
{
var loginTask = LogInAsyncCore();
NotifyAuthenticationStateChanged(loginTask);
return loginTask;
async Task<AuthenticationState> LogInAsyncCore()
{
var user = await LoginWithExternalProviderAsync();
currentUser = user;
return new AuthenticationState(currentUser);
}
}
private Task<ClaimsPrincipal> LoginWithExternalProviderAsync()
{
/*
Provide OpenID/MSAL code to authenticate the user. See your identity
provider's documentation for details.
Return a new ClaimsPrincipal based on a new ClaimsIdentity.
*/
var authenticatedUser = new ClaimsPrincipal(new ClaimsIdentity());
return Task.FromResult(authenticatedUser);
}
public void Logout()
{
currentUser = new ClaimsPrincipal(new ClaimsIdentity());
NotifyAuthenticationStateChanged(
Task.FromResult(new AuthenticationState(currentUser)));
}
}
在上面的示例中:
- 调用
LogInAsyncCore
触发登录过程。 - 调用 NotifyAuthenticationStateChanged 通知更新正在进行中,这允许应用在登录或注销过程中提供临时 UI。
- 返回
loginTask
会返回任务,以便触发登录的组件可以等待并在任务完成后做出回应。 LoginWithExternalProviderAsync
方法由开发人员实现,以使用 identity 提供程序的 SDK 登录用户。 有关更多信息,请参阅 identity 提供程序的文档。 经过身份验证的用户 (authenticatedUser
) 是基于新 ClaimsIdentity 的新 ClaimsPrincipal。
在 MauiProgram.cs
的 MauiProgram.CreateMauiApp
方法中,将授权服务和 Blazor 抽象添加到服务集合:
builder.Services.AddAuthorizationCore();
builder.Services.TryAddScoped<AuthenticationStateProvider, ExternalAuthStateProvider>();
在 MainWindow
的构造函数 (MainWindow.xaml.cs
) 中,将授权服务和 Blazor 抽象添加到服务集合:
serviceCollection.AddAuthorizationCore();
serviceCollection.TryAddScoped<AuthenticationStateProvider, ExternalAuthStateProvider>();
在 Form1
的构造函数 (Form1.cs
) 中,将授权服务和 Blazor 抽象添加到服务集合:
services.AddAuthorizationCore();
services.TryAddScoped<AuthenticationStateProvider, ExternalAuthStateProvider>();
以下 LoginComponent
组件演示了如何实现用户登录。 在典型应用中,仅当用户未登录到应用时,LoginComponent
组件才会显示在父组件中。
Shared/LoginComponent.razor
:
@inject AuthenticationStateProvider AuthenticationStateProvider
<button @onclick="Login">Log in</button>
@code
{
public async Task Login()
{
await ((ExternalAuthStateProvider)AuthenticationStateProvider)
.LogInAsync();
}
}
以下 LogoutComponent
组件演示了如何实现用户注销。 在典型应用中,仅当用户登录到应用时,LogoutComponent
组件才会显示在父组件中。
Shared/LogoutComponent.razor
:
@inject AuthenticationStateProvider AuthenticationStateProvider
<button @onclick="Logout">Log out</button>
@code
{
public async Task Logout()
{
await ((ExternalAuthStateProvider)AuthenticationStateProvider)
.Logout();
}
}
访问其他身份验证信息
Blazor 未定义处理其他凭据的抽象,例如用于对 Web API 发送 HTTP 请求的访问令牌。 我们建议遵循 identity 提供者的指导,使用 identity 提供者的 SDK 提供的基元管理用户的凭证。
identity 提供者 SDK 通常会对存储在设备中的用户凭据使用令牌存储。 如果将 SDK 的令牌存储基元添加到服务容器,请在应用中使用 SDK 的基元。
框架 Blazor 既不知晓用户的身份验证凭据,也不会以任何方式与凭据进行交互,因此应用的代码可随意遵循你认为最方便的任何方法。 但是,在应用中执行身份验证代码时,请遵循下一部分其他身份验证安全注意事项中的一般安全指南要求。
其他身份验证安全注意事项
身份验证过程在 Blazor 的外部,我们建议开发人员访问 identity 提供者指南以了解其他安全指导意见。
进行身份验证时:
- 避免在 Web View 的上下文中进行身份验证。 例如,避免使用 JavaScript OAuth 库来执行身份验证流。 在单页应用中,身份验证令牌不是隐藏在 JavaScript 中的,可被恶意用户轻松发现并用于恶意目的。 本机应用不会遭受此风险,因为本机应用只能在浏览器上下文之外获取令牌,这意味着第三方恶意脚本无法窃取令牌并危及应用安全。
- 避免自行执行身份验证工作流。 在大多数情况下,平台库将使用系统的浏览器(而不是使用容易遭到劫持的自定义 Web View)安全地处理身份验证工作流。
- 避免使用平台的 Web View 控件来执行身份验证。 相反,请尽可能使用系统的浏览器。
- 避免将令牌传递到文档上下文 (JavaScript)。 在某些情况下,文档中的 JavaScript 库需要执行对外部服务的授权调用。 无需通过 JS 互操作向 JavaScript 提供令牌:
- 向库和 Web View 内部提供生成的临时令牌。
- 截获代码中的传出网络请求。
- 将临时令牌替换为实际令牌,并确认请求的目标有效。
其他资源
Blazor Hybrid 应用中的身份验证由本机平台库处理,因为后者提供了浏览器沙盒无法给予的经过增强的安全保证。 本机应用的身份验证使用特定于操作系统的机制或通过联合协议,如 OpenID Connect (OIDC)。 按照针对应用选择的 identity 提供程序指南进行操作,然后使用本文中的指南进一步将 identity 与 Blazor 集成。
集成身份验证必须为 Razor 组件和服务实现以下目标:
- 使用
Microsoft.AspNetCore.Components.Authorization
包中的抽象,例如 AuthorizeView。 - 对身份验证上下文中的更改做出回应。
- 访问由应用根据 identity 提供程序预配的凭据,例如用于执行授权 API 调用的访问令牌。
将身份验证添加到 .NET MAUI 后,WPF 或 Windows 窗体应用和用户能够成功登录和注销,将身份验证与 Blazor 集成,以使经过身份验证的用户用使用 Razor 组件和服务。 执行以下步骤:
引用
Microsoft.AspNetCore.Components.Authorization
包。注意
有关将包添加到 .NET 应用的指南,请参阅包使用工作流(NuGet 文档)中“安装和管理包”下的文章。 在 NuGet.org 中确认正确的包版本。
实现自定义 AuthenticationStateProvider,这是 Razor 组件用来访问有关经过身份验证的用户的信息并在身份验证状态发生更改时接收更新的一种抽象。
在依赖项注入容器中注册自定义身份验证状态提供程序。
.NET MAUI 应用使用 Xamarin.Essentials:Web 验证器: WebAuthenticator
类允许应用启动基于浏览器的身份验证流,这些流侦听注册到应用的特定 URL 的回调。
要获得更多指南,请查看下面的资源:
WPF 应用使用 Microsoft identity 平台与 Microsoft Entra (ME-ID) 和 AAD B2C 集成。 有关指导和示例,请参阅以下资源:
Windows 窗体应用使用 Microsoft identity 平台与 Microsoft Entra (ME-ID) 和 AAD B2C 集成。 有关更多信息,请参阅 Microsoft 身份验证库 (MSAL) 概述。
在不使用用户更改更新的情况下创建自定义 AuthenticationStateProvider
如果应用在应用启动后立即对用户进行身份验证,并且经过身份验证的用户在整个应用生存期内保持不变,则无需发送用户更改通知,并且该应用仅提供有关经过身份验证的用户的信息。 在此方案中,用户在打开应用时登录到应用,并在用户注销后再次显示登录屏幕。下面的 ExternalAuthStateProvider
是此身份验证方案的自定义 AuthenticationStateProvider 的示例实现。
注意
以下自定义 AuthenticationStateProvider 未声明命名空间,以便使代码示例适用于任何 Blazor Hybrid 应用。 但是,最佳做法是在生产应用中实现示例时提供应用的命名空间。
ExternalAuthStateProvider.cs
:
using System.Security.Claims;
using System.Threading.Tasks;
using Microsoft.AspNetCore.Components.Authorization;
public class ExternalAuthStateProvider : AuthenticationStateProvider
{
private readonly Task<AuthenticationState> authenticationState;
public ExternalAuthStateProvider(AuthenticatedUser user) =>
authenticationState = Task.FromResult(new AuthenticationState(user.Principal));
public override Task<AuthenticationState> GetAuthenticationStateAsync() =>
authenticationState;
}
public class AuthenticatedUser
{
public ClaimsPrincipal Principal { get; set; } = new();
}
以下步骤介绍了如何:
- 添加所需的命名空间。
- 将授权服务和 Blazor 抽象添加到服务集合。
- 生成服务集合。
- 解析
AuthenticatedUser
服务以设置经过身份验证的用户的声明主体。 有关详细信息,请参阅 identity 提供程序的文档。 - 返回生成的主机。
在 MauiProgram.cs
的 MauiProgram.CreateMauiApp
方法中,为 Microsoft.AspNetCore.Components.Authorization 和 System.Security.Claims 添加命名空间:
using Microsoft.AspNetCore.Components.Authorization;
using System.Security.Claims;
删除以下返回生成的 Microsoft.Maui.Hosting.MauiApp 的代码行:
- return builder.Build();
用下面的代码替换前面的代码行。 添加 OpenID/MSAL 代码以对用户进行身份验证。 有关详细信息,请参阅 identity 提供程序的文档。
builder.Services.AddAuthorizationCore();
builder.Services.AddScoped<AuthenticationStateProvider, ExternalAuthStateProvider>();
builder.Services.AddSingleton<AuthenticatedUser>();
var host = builder.Build();
var authenticatedUser = host.Services.GetRequiredService<AuthenticatedUser>();
/*
Provide OpenID/MSAL code to authenticate the user. See your identity provider's
documentation for details.
The user is represented by a new ClaimsPrincipal based on a new ClaimsIdentity.
*/
var user = new ClaimsPrincipal(new ClaimsIdentity());
authenticatedUser.Principal = user;
return host;
以下步骤介绍了如何:
- 添加所需的命名空间。
- 将授权服务和 Blazor 抽象添加到服务集合。
- 生成服务集合,并将生成的服务集合作为资源添加到应用的
ResourceDictionary
中。 - 解析
AuthenticatedUser
服务以设置经过身份验证的用户的声明主体。 有关详细信息,请参阅 identity 提供程序的文档。 - 返回生成的主机。
在 MainWindow
的构造函数 (MainWindow.xaml.cs
) 中,为 Microsoft.AspNetCore.Components.Authorization 和 System.Security.Claims 添加命名空间:
using Microsoft.AspNetCore.Components.Authorization;
using System.Security.Claims;
删除以下用于将生成的服务集合作为资源添加到应用的 ResourceDictionary
的代码行:
- Resources.Add("services", serviceCollection.BuildServiceProvider());
用下面的代码替换前面的代码行。 添加 OpenID/MSAL 代码以对用户进行身份验证。 有关详细信息,请参阅 identity 提供程序的文档。
serviceCollection.AddAuthorizationCore();
serviceCollection.AddScoped<AuthenticationStateProvider, ExternalAuthStateProvider>();
serviceCollection.AddSingleton<AuthenticatedUser>();
var services = serviceCollection.BuildServiceProvider();
Resources.Add("services", services);
var authenticatedUser = services.GetRequiredService<AuthenticatedUser>();
/*
Provide OpenID/MSAL code to authenticate the user. See your identity provider's
documentation for details.
The user is represented by a new ClaimsPrincipal based on a new ClaimsIdentity.
*/
var user = new ClaimsPrincipal(new ClaimsIdentity());
authenticatedUser.Principal = user;
以下步骤介绍了如何:
- 添加所需的命名空间。
- 将授权服务和 Blazor 抽象添加到服务集合。
- 生成服务集合,并将生成的服务集合添加到应用的服务提供商。
- 解析
AuthenticatedUser
服务以设置经过身份验证的用户的声明主体。 有关详细信息,请参阅 identity 提供程序的文档。
在 Form1
的构造函数 (Form1.cs
) 中,为 Microsoft.AspNetCore.Components.Authorization 和 System.Security.Claims 添加命名空间:
using Microsoft.AspNetCore.Components.Authorization;
using System.Security.Claims;
删除以下用于将生成的服务集合设置为应用的服务提供商的代码行:
- blazorWebView1.Services = services.BuildServiceProvider();
用下面的代码替换前面的代码行。 添加 OpenID/MSAL 代码以对用户进行身份验证。 有关详细信息,请参阅 identity 提供程序的文档。
services.AddAuthorizationCore();
services.AddScoped<AuthenticationStateProvider, ExternalAuthStateProvider>();
services.AddSingleton<AuthenticatedUser>();
var serviceCollection = services.BuildServiceProvider();
blazorWebView1.Services = serviceCollection;
var authenticatedUser = serviceCollection.GetRequiredService<AuthenticatedUser>();
/*
Provide OpenID/MSAL code to authenticate the user. See your identity provider's
documentation for details.
The user is represented by a new ClaimsPrincipal based on a new ClaimsIdentity.
*/
var user = new ClaimsPrincipal(new ClaimsIdentity());
authenticatedUser.Principal = user;
在使用用户更改更新的情况下创建自定义 AuthenticationStateProvider
若要在 Blazor 应用运行时更新用户,请使用以下任一方法在 AuthenticationStateProvider 实现中调用 NotifyAuthenticationStateChanged:
从 BlazorWebView
外发出身份验证更新信号(选项 1)
自定义 AuthenticationStateProvider 可以使用全局服务来发出身份验证更新的信号。 我们建议此服务提供一个 AuthenticationStateProvider 可以订阅的事件,该事件会调用 NotifyAuthenticationStateChanged。
注意
以下自定义 AuthenticationStateProvider 未声明命名空间,以便使代码示例适用于任何 Blazor Hybrid 应用。 但是,最佳做法是在生产应用中实现示例时提供应用的命名空间。
ExternalAuthStateProvider.cs
:
using System;
using System.Security.Claims;
using System.Threading.Tasks;
using Microsoft.AspNetCore.Components.Authorization;
public class ExternalAuthStateProvider : AuthenticationStateProvider
{
private AuthenticationState currentUser;
public ExternalAuthStateProvider(ExternalAuthService service)
{
currentUser = new AuthenticationState(service.CurrentUser);
service.UserChanged += (newUser) =>
{
currentUser = new AuthenticationState(newUser);
NotifyAuthenticationStateChanged(Task.FromResult(currentUser));
};
}
public override Task<AuthenticationState> GetAuthenticationStateAsync() =>
Task.FromResult(currentUser);
}
public class ExternalAuthService
{
public event Action<ClaimsPrincipal>? UserChanged;
private ClaimsPrincipal? currentUser;
public ClaimsPrincipal CurrentUser
{
get { return currentUser ?? new(); }
set
{
currentUser = value;
if (UserChanged is not null)
{
UserChanged(currentUser);
}
}
}
}
在 MauiProgram.cs
的 MauiProgram.CreateMauiApp
方法中,为 Microsoft.AspNetCore.Components.Authorization 添加命名空间:
using Microsoft.AspNetCore.Components.Authorization;
将授权服务和 Blazor 抽象添加到服务集合:
builder.Services.AddAuthorizationCore();
builder.Services.AddScoped<AuthenticationStateProvider, ExternalAuthStateProvider>();
builder.Services.AddSingleton<ExternalAuthService>();
在 MainWindow
的构造函数 (MainWindow.xaml.cs
) 中,为 Microsoft.AspNetCore.Components.Authorization 添加命名空间:
using Microsoft.AspNetCore.Components.Authorization;
将授权服务和 Blazor 抽象添加到服务集合:
serviceCollection.AddAuthorizationCore();
serviceCollection.AddScoped<AuthenticationStateProvider, ExternalAuthStateProvider>();
serviceCollection.AddSingleton<ExternalAuthService>();
在 Form1
的构造函数 (Form1.cs
) 中,为 Microsoft.AspNetCore.Components.Authorization 添加命名空间:
using Microsoft.AspNetCore.Components.Authorization;
将授权服务和 Blazor 抽象添加到服务集合:
services.AddAuthorizationCore();
services.AddScoped<AuthenticationStateProvider, ExternalAuthStateProvider>();
services.AddSingleton<ExternalAuthService>();
无论应用在何处对用户进行身份验证,请解析 ExternalAuthService
服务:
var authService = host.Services.GetRequiredService<ExternalAuthService>();
执行自定义 OpenID/MSAL 代码以对用户进行身份验证。 有关详细信息,请参阅 identity 提供程序的文档。 经过身份验证的用户(以下示例中的 authenticatedUser
)是基于新 ClaimsIdentity 的新 ClaimsPrincipal。
将当前用户设置为经过身份验证的用户:
authService.CurrentUser = authenticatedUser;
上述方法的替代方法是基于 System.Threading.Thread.CurrentPrincipal 设置用户的主体,而不是通过服务进行设置,从而避免使用依赖项注入容器:
public class CurrentThreadUserAuthenticationStateProvider : AuthenticationStateProvider
{
public override Task<AuthenticationState> GetAuthenticationStateAsync() =>
Task.FromResult(
new AuthenticationState(Thread.CurrentPrincipal as ClaimsPrincipal ??
new ClaimsPrincipal(new ClaimsIdentity())));
}
使用替代方法,仅向服务集合中添加授权服务 (AddAuthorizationCore) 和 CurrentThreadUserAuthenticationStateProvider
(.AddScoped<AuthenticationStateProvider, CurrentThreadUserAuthenticationStateProvider>()
)。
在 BlazorWebView
中处理身份验证(选项 2)
自定义 AuthenticationStateProvider 可以包含用于触发登录和注销及更新用户的其他方法。
注意
以下自定义 AuthenticationStateProvider 未声明命名空间,以便使代码示例适用于任何 Blazor Hybrid 应用。 但是,最佳做法是在生产应用中实现示例时提供应用的命名空间。
ExternalAuthStateProvider.cs
:
using System.Security.Claims;
using System.Threading.Tasks;
using Microsoft.AspNetCore.Components.Authorization;
public class ExternalAuthStateProvider : AuthenticationStateProvider
{
private ClaimsPrincipal currentUser = new ClaimsPrincipal(new ClaimsIdentity());
public override Task<AuthenticationState> GetAuthenticationStateAsync() =>
Task.FromResult(new AuthenticationState(currentUser));
public Task LogInAsync()
{
var loginTask = LogInAsyncCore();
NotifyAuthenticationStateChanged(loginTask);
return loginTask;
async Task<AuthenticationState> LogInAsyncCore()
{
var user = await LoginWithExternalProviderAsync();
currentUser = user;
return new AuthenticationState(currentUser);
}
}
private Task<ClaimsPrincipal> LoginWithExternalProviderAsync()
{
/*
Provide OpenID/MSAL code to authenticate the user. See your identity
provider's documentation for details.
Return a new ClaimsPrincipal based on a new ClaimsIdentity.
*/
var authenticatedUser = new ClaimsPrincipal(new ClaimsIdentity());
return Task.FromResult(authenticatedUser);
}
public void Logout()
{
currentUser = new ClaimsPrincipal(new ClaimsIdentity());
NotifyAuthenticationStateChanged(
Task.FromResult(new AuthenticationState(currentUser)));
}
}
在上面的示例中:
- 调用
LogInAsyncCore
触发登录过程。 - 调用 NotifyAuthenticationStateChanged 通知更新正在进行中,这允许应用在登录或注销过程中提供临时 UI。
- 返回
loginTask
会返回任务,以便触发登录的组件可以等待并在任务完成后做出回应。 LoginWithExternalProviderAsync
方法由开发人员实现,以使用 identity 提供程序的 SDK 登录用户。 有关更多信息,请参阅 identity 提供程序的文档。 经过身份验证的用户 (authenticatedUser
) 是基于新 ClaimsIdentity 的新 ClaimsPrincipal。
在 MauiProgram.cs
的 MauiProgram.CreateMauiApp
方法中,将授权服务和 Blazor 抽象添加到服务集合:
builder.Services.AddAuthorizationCore();
builder.Services.AddScoped<AuthenticationStateProvider, ExternalAuthStateProvider>();
在 MainWindow
的构造函数 (MainWindow.xaml.cs
) 中,将授权服务和 Blazor 抽象添加到服务集合:
serviceCollection.AddAuthorizationCore();
serviceCollection.AddScoped<AuthenticationStateProvider, ExternalAuthStateProvider>();
在 Form1
的构造函数 (Form1.cs
) 中,将授权服务和 Blazor 抽象添加到服务集合:
services.AddAuthorizationCore();
services.AddScoped<AuthenticationStateProvider, ExternalAuthStateProvider>();
以下 LoginComponent
组件演示了如何实现用户登录。 在典型应用中,仅当用户未登录到应用时,LoginComponent
组件才会显示在父组件中。
Shared/LoginComponent.razor
:
@inject AuthenticationStateProvider AuthenticationStateProvider
<button @onclick="Login">Log in</button>
@code
{
public async Task Login()
{
await ((ExternalAuthStateProvider)AuthenticationStateProvider)
.LogInAsync();
}
}
以下 LogoutComponent
组件演示了如何实现用户注销。 在典型应用中,仅当用户登录到应用时,LogoutComponent
组件才会显示在父组件中。
Shared/LogoutComponent.razor
:
@inject AuthenticationStateProvider AuthenticationStateProvider
<button @onclick="Logout">Log out</button>
@code
{
public async Task Logout()
{
await ((ExternalAuthStateProvider)AuthenticationStateProvider)
.Logout();
}
}
访问其他身份验证信息
Blazor 未定义处理其他凭据的抽象,例如用于对 Web API 发送 HTTP 请求的访问令牌。 我们建议遵循 identity 提供者的指导,使用 identity 提供者的 SDK 提供的基元管理用户的凭证。
identity 提供者 SDK 通常会对存储在设备中的用户凭据使用令牌存储。 如果将 SDK 的令牌存储基元添加到服务容器,请在应用中使用 SDK 的基元。
框架 Blazor 既不知晓用户的身份验证凭据,也不会以任何方式与凭据进行交互,因此应用的代码可随意遵循你认为最方便的任何方法。 但是,在应用中执行身份验证代码时,请遵循下一部分其他身份验证安全注意事项中的一般安全指南要求。
其他身份验证安全注意事项
身份验证过程在 Blazor 的外部,我们建议开发人员访问 identity 提供者指南以了解其他安全指导意见。
进行身份验证时:
- 避免在 Web View 的上下文中进行身份验证。 例如,避免使用 JavaScript OAuth 库来执行身份验证流。 在单页应用中,身份验证令牌不是隐藏在 JavaScript 中的,可被恶意用户轻松发现并用于恶意目的。 本机应用不会遭受此风险,因为本机应用只能在浏览器上下文之外获取令牌,这意味着第三方恶意脚本无法窃取令牌并危及应用安全。
- 避免自行执行身份验证工作流。 在大多数情况下,平台库将使用系统的浏览器(而不是使用容易遭到劫持的自定义 Web View)安全地处理身份验证工作流。
- 避免使用平台的 Web View 控件来执行身份验证。 相反,请尽可能使用系统的浏览器。
- 避免将令牌传递到文档上下文 (JavaScript)。 在某些情况下,文档中的 JavaScript 库需要执行对外部服务的授权调用。 无需通过 JS 互操作向 JavaScript 提供令牌:
- 向库和 Web View 内部提供生成的临时令牌。
- 截获代码中的传出网络请求。
- 将临时令牌替换为实际令牌,并确认请求的目标有效。