使用标识服务器保护 ASP.NET Core Blazor WebAssembly 托管应用Secure an ASP.NET Core Blazor WebAssembly hosted app with Identity Server

本文内容

作者: Javier Calvarro 使用Luke Latham

重要

Blazor WebAssembly 为预览版状态

ASP.NET Core 3.0 支持 Blazor Server。Blazor WebAssembly 在 ASP.NET Core 3.1 中为预览版。

备注

本文中的指导适用于 ASP.NET Core Blazor WebAssembly 模板 3.2 版或更高版本。要在未使用 Visual Studio 版本 16.6 预览版 2 或更高版本时获取最新的 Blazor WebAssembly 模板(版本 3.2.0-preview3.20168.3),请参阅 ASP.NET Core Blazor 入门

若要在 Visual Studio 中创建新的 Blazor 托管应用,使用IdentityServer对用户和 API 调用进行身份验证:

  • 使用 Visual Studio 创建新的 Blazor WebAssembly应用。有关详细信息,请参阅 ASP.NET Core Blazor 入门
  • 在 "新建 Blazor 应用" 对话框中,在 "身份验证" 部分中选择 "更改"。
  • 选择后跟 "确定"单个用户帐户
  • 选中 "高级" 部分中的 " ASP.NET Core 托管" 复选框。
  • 选择“创建”按钮。
    若要在命令外壳中创建应用,请执行以下命令:
  1. dotnet new blazorwasm -au Individual -ho

若要指定输出位置(如果它不存在,则创建一个项目文件夹),请在命令中包含 output 选项,其中包含一个路径(例如 -o BlazorSample)。文件夹名称还会成为项目名称的一部分。

服务器应用配置Server app configuration

以下各节介绍了在包括身份验证支持时对项目的添加。

Startup 类Startup class

Startup 类具有以下附加项:

  • Startup.ConfigureServices 中:

    • 具有默认 UI 的标识:
  1. services.AddDbContext<ApplicationDbContext>(options =>
  2. options.UseSqlite(Configuration.GetConnectionString("DefaultConnection")));
  3. services.AddDefaultIdentity<ApplicationUser>()
  4. .AddDefaultUI(UIFramework.Bootstrap4)
  5. .AddEntityFrameworkStores<ApplicationDbContext>();
  • 使用附加的 AddApiAuthorization 帮助器方法,可在 IdentityServer 上设置某些默认的 ASP.NET Core 约定:
  1. services.AddIdentityServer()
  2. .AddApiAuthorization<ApplicationUser, ApplicationDbContext>();
  • 使用附加的 AddIdentityServerJwt 帮助器方法进行身份验证,该方法将应用程序配置为验证 IdentityServer 生成的 JWT 令牌:
  1. services.AddAuthentication()
  2. .AddIdentityServerJwt();
  • Startup.Configure 中:

    • 负责验证请求凭据并在请求上下文上设置用户的身份验证中间件:
  1. app.UseAuthentication();
  • 公开 Open ID Connect (OIDC)终结点的 IdentityServer 中间件:
  1. app.UseIdentityServer();

AddApiAuthorizationAddApiAuthorization

AddApiAuthorization helper 方法为 ASP.NET Core 情况配置IdentityServerIdentityServer 是一个功能强大且可扩展的框架,用于处理应用安全问题。在最常见的情况下,IdentityServer 会造成不必要的复杂性。因此,我们考虑到了一个很好的起点。身份验证需要更改后,IdentityServer 的全部功能仍可用于自定义身份验证,以满足应用程序的要求。

AddIdentityServerJwtAddIdentityServerJwt

AddIdentityServerJwt helper 方法为应用配置策略方案,作为默认身份验证处理程序。此策略配置为允许标识处理路由到标识 URL 空间中任何子路径的所有请求 /IdentityJwtBearerHandler 处理所有其他请求。此外,此方法:

  • 使用 {APPLICATION NAME}API的默认范围向 IdentityServer 注册 {APPLICATION NAME}API API 资源。
  • 将 JWT 持有者令牌中间件配置为验证 IdentityServer 为应用程序颁发的令牌。

WeatherForecastControllerWeatherForecastController

WeatherForecastController控制器/WeatherForecastController)中, [Authorize]属性应用于类。属性指示用户必须根据默认策略进行授权才能访问资源。默认授权策略配置为使用默认身份验证方案,该方案由 AddIdentityServerJwt 设置为之前提到的策略方案。Helper 方法将 JwtBearerHandler 配置为应用程序请求的默认处理程序。

ApplicationDbContextApplicationDbContext

ApplicationDbContextData/ApplicationDbContext)中,同一 DbContext 用于标识,但它扩展 ApiAuthorizationDbContext<TUser> 以包括 IdentityServer 的架构。ApiAuthorizationDbContext<TUser> 派生自 IdentityDbContext

若要完全控制数据库架构,请从某个可用的标识 DbContext 类继承,并通过在 OnModelCreating 方法中调用 builder.ConfigurePersistedGrantContext(_operationalStoreOptions.Value),将上下文配置为包含标识架构。

OidcConfigurationControllerOidcConfigurationController

OidcConfigurationController (controller /OidcConfigurationController)中,客户端终结点配置为提供 OIDC 参数。

应用设置文件App settings files

在项目根目录下的应用设置文件(appsettings)中,IdentityServer 部分介绍了已配置的客户端的列表。在下面的示例中,有一个客户端。客户端名称对应于应用名称,并按约定映射到 OAuth ClientId 参数。配置文件指示正在配置的应用类型。此配置文件可在内部使用,以促进简化服务器配置过程的约定。

  1. "IdentityServer": {
  2. "Clients": {
  3. "BlazorApplicationWithAuthentication.Client": {
  4. "Profile": "IdentityServerSPA"
  5. }
  6. }
  7. }

开发环境中的应用设置文件(appsettings)。开发)在项目根目录下,IdentityServer 部分介绍了用于对令牌进行签名的密钥。

  1. "IdentityServer": {
  2. "Key": {
  3. "Type": "Development"
  4. }
  5. }

客户端应用配置Client app configuration

身份验证包Authentication package

创建应用以使用单个用户帐户(Individual)时,应用会自动在应用的项目文件中接收 Microsoft.AspNetCore.Components.WebAssembly.Authentication 包的包引用。包提供一组基元,可帮助应用对用户进行身份验证,并获取令牌以调用受保护的 Api。

如果向应用程序中添加身份验证,请将包手动添加到应用的项目文件中:

  1. <PackageReference
  2. Include="Microsoft.AspNetCore.Components.WebAssembly.Authentication"
  3. Version="{VERSION}" />

将前面的包引用中的 {VERSION} 替换为 ASP.NET Core Blazor 入门 一文中所示 Microsoft.AspNetCore.Blazor.Templates 包的版本。

API 授权支持API authorization support

通过 Microsoft.AspNetCore.Components.WebAssembly.Authentication 包内提供的扩展方法,将对用户进行身份验证的支持插入到服务容器中。此方法设置应用与现有授权系统交互所需的所有服务。

  1. builder.Services.AddApiAuthorization();

默认情况下,它按约定从 _configuration/{client-id}加载应用的配置。按照约定,将客户端 ID 设置为应用的程序集名称。可以通过使用选项调用重载,将此 URL 更改为指向不同的终结点。

索引页面Index page

"索引页(wwwroot/index.html)" 页包含一个用于在 JavaScript 中定义 AuthenticationService 的脚本。AuthenticationService 处理 OIDC 协议的低级别详细信息。应用在内部调用在脚本中定义的方法来执行身份验证操作。

  1. <script src="_content/Microsoft.AspNetCore.Components.WebAssembly.Authentication/
  2. AuthenticationService.js"></script>

应用组件App component

App 组件(app.config)类似于在 Blazor Server apps 中找到 App 组件:

  • CascadingAuthenticationState 组件管理向应用程序的其余部分公开 AuthenticationState
  • AuthorizeRouteView 组件确保当前用户有权访问给定页面或呈现 RedirectToLogin 组件。
  • RedirectToLogin 组件管理将未经授权的用户重定向到登录页。
  1. <CascadingAuthenticationState>
  2. <Router AppAssembly="@typeof(Program).Assembly">
  3. <Found Context="routeData">
  4. <AuthorizeRouteView RouteData="@routeData"
  5. DefaultLayout="@typeof(MainLayout)">
  6. <NotAuthorized>
  7. <RedirectToLogin />
  8. </NotAuthorized>
  9. </AuthorizeRouteView>
  10. </Found>
  11. <NotFound>
  12. <LayoutView Layout="@typeof(MainLayout)">
  13. <p>Sorry, there's nothing at this address.</p>
  14. </LayoutView>
  15. </NotFound>
  16. </Router>
  17. </CascadingAuthenticationState>

RedirectToLogin 组件RedirectToLogin component

RedirectToLogin 组件(Shared/RedirectToLogin):

  • 管理将未经授权的用户重定向到登录页。
  • 保留用户尝试访问的当前 URL,以便在身份验证成功时可以将其返回到该页。
  1. @inject NavigationManager Navigation
  2. @using Microsoft.AspNetCore.Components.WebAssembly.Authentication
  3. @code {
  4. protected override void OnInitialized()
  5. {
  6. Navigation.NavigateTo($"authentication/login?returnUrl={Navigation.Uri}");
  7. }
  8. }

LoginDisplay 组件LoginDisplay component

LoginDisplay 组件(shared/LoginDisplay)在 MainLayout 组件(shared/MainLayout)中呈现并管理以下行为:

  • 对于经过身份验证的用户:
    • 显示当前用户名。
    • 提供指向 ASP.NET Core 标识中的用户配置文件页的链接。
    • 提供用于注销应用的按钮。
  • 对于匿名用户:
    • 提供注册的选项。
    • 提供用于登录的选项。
  1. @using Microsoft.AspNetCore.Components.Authorization
  2. @using Microsoft.AspNetCore.Components.WebAssembly.Authentication
  3. @inject NavigationManager Navigation
  4. @inject SignOutSessionStateManager SignOutManager
  5. <AuthorizeView>
  6. <Authorized>
  7. <a href="authentication/profile">Hello, @context.User.Identity.Name!</a>
  8. <button class="nav-link btn btn-link" @onclick="BeginSignOut">
  9. Log out
  10. </button>
  11. </Authorized>
  12. <NotAuthorized>
  13. <a href="authentication/register">Register</a>
  14. <a href="authentication/login">Log in</a>
  15. </NotAuthorized>
  16. </AuthorizeView>
  17. @code {
  18. private async Task BeginSignOut(MouseEventArgs args)
  19. {
  20. await SignOutManager.SetSignOutState();
  21. Navigation.NavigateTo("authentication/logout");
  22. }
  23. }

身份验证组件Authentication component

Authentication 组件(Pages/Authentication)生成的页面定义处理不同的身份验证阶段所需的路由。

RemoteAuthenticatorView 组件:

  • Microsoft.AspNetCore.Components.WebAssembly.Authentication 包提供。
  • 管理在每个身份验证阶段执行适当的操作。
  1. @page "/authentication/{action}"
  2. @using Microsoft.AspNetCore.Components.WebAssembly.Authentication
  3. <RemoteAuthenticatorView Action="@Action" />
  4. @code {
  5. [Parameter]
  6. public string Action { get; set; }
  7. }

FetchData 组件FetchData component

FetchData 组件显示了如何:

  • 设置访问令牌。
  • 使用访问令牌调用服务器应用中受保护的资源 API。

@attribute [Authorize] 指令向 Blazor WebAssembly 授权系统表明,用户必须获得授权才能访问此组件。如果客户端应用程序中存在该属性,则不会阻止在没有正确凭据的情况下调用服务器上的 API。服务器应用程序还必须在适当的终结点上使用 [Authorize],才能正确地对其进行保护。

AuthenticationService.RequestAccessToken(); 负责请求可添加到请求中的访问令牌,以调用 API。如果该令牌已缓存,或者该服务在没有用户交互的情况下能够预配新的访问令牌,则令牌请求会成功。否则,令牌请求会失败。

为了获得要包含在请求中的实际标记,应用程序必须通过调用 tokenResult.TryGetToken(out var token)来检查请求是否成功。

如果请求成功,将使用访问令牌填充令牌变量。此标记的 Value 属性公开要包含在 Authorization 请求标头中的文本字符串。

如果请求失败,因为无法在没有用户交互的情况下进行设置,令牌结果将包含重定向 URL。导航到此 URL 后,用户将进入登录页,并在身份验证成功后返回到当前页面。

  1. @page "/fetchdata"
  2. ...
  3. @attribute [Authorize]
  4. ...
  5. @code {
  6. private WeatherForecast[] forecasts;
  7. protected override async Task OnInitializedAsync()
  8. {
  9. var httpClient = new HttpClient();
  10. httpClient.BaseAddress = new Uri(Navigation.BaseUri);
  11. var tokenResult = await AuthenticationService.RequestAccessToken();
  12. if (tokenResult.TryGetToken(out var token))
  13. {
  14. httpClient.DefaultRequestHeaders.Add("Authorization",
  15. $"Bearer {token.Value}");
  16. forecasts = await httpClient.GetJsonAsync<WeatherForecast[]>(
  17. "WeatherForecast");
  18. }
  19. else
  20. {
  21. Navigation.NavigateTo(tokenResult.RedirectUrl);
  22. }
  23. }
  24. }

有关详细信息,请参阅在执行身份验证操作之前保存应用程序状态

运行应用Run the app

从服务器项目运行应用。使用 Visual Studio 时,请在解决方案资源管理器中选择服务器项目,并在工具栏中选择 "运行" 按钮,或从 "调试" 菜单启动应用程序。

疑难解答Troubleshoot

由于 ID 令牌和访问令牌可在登录尝试期间保持,因此,每次更新后,请使用浏览器的开发人员控制台清除浏览器 cookie:

  • 应用的身份验证代码或配置设置。
  • 应用的配置 OIDC 兼容的提供程序(例如 Azure Active Directory)。