使用 Azure Active Directory 保护 ASP.NET Core Blazor WebAssembly 托管应用Secure an ASP.NET Core Blazor WebAssembly hosted app with Azure Active Directory

本文内容

作者: 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 入门

本文介绍如何创建使用Azure Active Directory (AAD)进行身份验证的Blazor WebAssembly 托管应用

在 AAD B2C 中注册应用并创建解决方案Register apps in AAD B2C and create solution

创建租户Create a tenant

按照快速入门:设置租户中的指导在 AAD 中创建租户。

注册服务器 API 应用Register a server API app

请按照快速入门:向 Microsoft 标识平台注册应用程序和后续 Azure AAD 主题中的指导,在 Azure 门户的 " Azure Active Directory > 应用注册" 区域中为服务器 API 应用注册 AAD 应用:

  • 选择“新注册”。
  • 提供应用的名称(例如 Blazor 服务器 AAD)。
  • 选择受支持的帐户类型。对于此体验,你可以选择仅在此组织目录中的帐户(单租户)。
  • 在这种情况下,服务器 API 应用不需要重定向 uri ,因此请将下拉集设置为 " Web ",并不要输入 "重定向 uri"。
  • 禁用 "权限 > 向 openid 和 offline_access 权限授予管理员权限" 复选框。
  • 选择“注册”。
    在 " API 权限" 中,删除Microsoft Graph > 用户 "。读取权限,因为应用不需要登录或 uer 配置文件访问。

在中公开 API

  • 选择“添加范围”。
  • 选择“保存并继续”。
  • 提供作用域名称(例如 API.Access)。
  • 提供管理员同意显示名称(例如 Access API)。
  • 提供管理员同意说明(例如 Allows the app to access server app API endpoints.)。
  • 确认 "状态" 设置为 "已启用"。
  • 选择 "添加作用域"。
    记录以下信息:
  • 服务器 API 应用应用程序 ID (客户端 ID)(例如 11111111-1111-1111-1111-111111111111
  • 目录 ID (租户 ID)(例如 222222222-2222-2222-2222-222222222222
  • AAD 租户域(例如 contoso.onmicrosoft.com
  • 默认作用域(例如 API.Access

注册客户端应用Register a client app

请按照快速入门:向 Microsoft 标识平台注册应用程序和后续 Azure AAD 主题中的指导,在 Azure 门户的Azure Active Directory > 应用注册区域中为客户端应用程序注册 AAD 应用程序:

  • 选择“新注册”。
  • 提供应用的名称(例如, Blazor 客户端 AAD)。
  • 选择受支持的帐户类型。对于此体验,你可以选择仅在此组织目录中的帐户(单租户)。
  • 将 "重定向 uri " 下拉集保持设置为 " Web",并提供 https://localhost:5001/authentication/login-callback的重定向 uri。
  • 禁用 "权限 > 向 openid 和 offline_access 权限授予管理员权限" 复选框。
  • 选择“注册”。
    在 "身份验证" > 平台配置 > Web

  • 确认存在 https://localhost:5001/authentication/login-callback重定向 URI

  • 对于 "隐式授予",选中 "访问令牌" 和 " ID 令牌" 对应的复选框。
  • 此体验可接受应用的其余默认值。
  • 选择“保存”按钮。
    在 " API 权限

  • 确认应用程序已Microsoft Graph > 用户。读取权限。

  • 选择 "添加权限",然后选择 "我的 api"
  • 从 "名称" 列中选择 "服务器 API 应用" (例如 Blazor 服务器 AAD)。
  • 打开API列表。
  • 启用对 API 的访问(例如 API.Access)。
  • 选择“添加权限”。
  • 选择 "为 {租户名称} 授予管理内容" 按钮。请选择“是”以确认。
    记录客户端应用应用程序 Id (客户端 id)(例如 33333333-3333-3333-3333-333333333333)。

创建应用Create the app

将以下命令中的占位符替换为前面记录的信息,然后在命令行界面中执行命令:

  1. dotnet new blazorwasm -au SingleOrg --api-client-id "{SERVER API APP CLIENT ID}" --app-id-uri "{SERVER API APP CLIENT ID}" --client-id "{CLIENT APP CLIENT ID}" --default-scope "{DEFAULT SCOPE}" --domain "{DOMAIN}" -ho --tenant-id "{TENANT ID}"

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

备注

请参阅身份验证服务支持部分,了解对默认访问令牌范围的重要配置更改。从模板创建客户端应用后,必须手动更改 Blazor WebAssembly 模板提供的值。

服务器应用配置Server app configuration

本部分适用于解决方案的服务器应用。

身份验证包Authentication package

Microsoft.AspNetCore.Authentication.AzureAD.UI提供对 ASP.NET Core Web Api 的身份验证和授权调用的支持:

  1. <PackageReference Include="Microsoft.AspNetCore.Authentication.AzureAD.UI"
  2. Version="3.1.0" />

身份验证服务支持Authentication service support

AddAuthentication 方法在应用中设置身份验证服务,并将 JWT 持有者处理程序配置为默认身份验证方法。AddAzureADBearer 方法在验证 Azure Active Directory 发出的令牌所需的 JWT 持有者处理程序中设置特定参数:

  1. services.AddAuthentication(AzureADDefaults.BearerAuthenticationScheme)
  2. .AddAzureADBearer(options => Configuration.Bind("AzureAd", options));

UseAuthenticationUseAuthorization 确保:

  • 应用尝试分析和验证传入请求的令牌。
  • 任何试图访问受保护资源的请求均不正确。
  1. app.UseAuthentication();
  2. app.UseAuthorization();

应用设置App settings

Appsettings文件包含用于配置用于验证访问令牌的 JWT 持有者处理程序的选项。

  1. {
  2. "AzureAd": {
  3. "Instance": "https://login.microsoftonline.com/",
  4. "Domain": "{DOMAIN}",
  5. "TenantId": "{TENANT ID}",
  6. "ClientId": "{API CLIENT ID}",
  7. }
  8. }

WeatherForecast 控制器WeatherForecast controller

WeatherForecast 控制器(控制器/WeatherForecastController)公开受保护的 API,并将 [Authorize] 特性应用到控制器。务必要了解:

  • 此 API 控制器中的 [Authorize] 属性是保护此 API 不受未经授权的访问的唯一操作。
  • Blazor WebAssembly 应用程序中使用的 [Authorize] 属性仅作为对应用程序的提示,用户应授权该应用程序正常工作。
  1. [Authorize]
  2. [ApiController]
  3. [Route("[controller]")]
  4. public class WeatherForecastController : ControllerBase
  5. {
  6. [HttpGet]
  7. public IEnumerable<WeatherForecast> Get()
  8. {
  9. ...
  10. }
  11. }

客户端应用配置Client app configuration

本部分适用于解决方案的客户端应用。

身份验证包Authentication package

创建应用以使用工作或学校帐户(SingleOrg)时,应用会自动接收Microsoft 身份验证库Microsoft.Authentication.WebAssembly.Msal)的包引用。包提供一组基元,可帮助应用对用户进行身份验证,并获取令牌以调用受保护的 Api。

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

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

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

Microsoft.Authentication.WebAssembly.Msal 包可向应用程序中添加 Microsoft.AspNetCore.Components.WebAssembly.Authentication 包。

身份验证服务支持Authentication service support

使用 Microsoft.Authentication.WebAssembly.Msal 包提供的 AddMsalAuthentication 扩展方法在服务容器中注册对用户进行身份验证。此方法设置应用程序与标识提供程序(IP)交互所需的所有服务。

Program.cs:

生成客户端应用时,默认的访问令牌范围为 api://{SERVER API APP CLIENT ID}/{DEFAULT SCOPE}格式。删除范围值的 api:// 部分。此问题将在将来的预览版本中得到解决。

  1. builder.Services.AddMsalAuthentication(options =>
  2. {
  3. var authentication = options.ProviderOptions.Authentication;
  4. authentication.Authority = "https://login.microsoftonline.com/{TENANT ID}";
  5. authentication.ClientId = "{CLIENT ID}";
  6. options.ProviderOptions.DefaultAccessTokenScopes.Add(
  7. "{SERVER API APP CLIENT ID}/{DEFAULT SCOPE}");
  8. });

备注

默认访问令牌范围必须是 {SERVER API APP CLIENT ID}/{DEFAULT SCOPE} 格式(例如,11111111-1111-1111-1111-111111111111/API.Access)。如果向范围设置提供方案或方案和主机(如 Azure 门户中所示),则当客户端应用收到来自服务器 API 应用401 未经授权响应时,将引发未处理的异常。

AddMsalAuthentication 方法接受回调,以配置对应用进行身份验证所需的参数。注册应用时,可以从 Azure 门户 AAD 配置获取配置应用所需的值。

默认访问令牌范围表示访问令牌作用域的列表:

  • 默认情况下,在登录请求中包括。
  • 用于在身份验证后立即设置访问令牌。

对于每个 Azure Active Directory 规则,所有作用域都必须属于同一应用。可以根据需要为其他 API 应用添加其他作用域:

  1. builder.Services.AddMsalAuthentication(options =>
  2. {
  3. ...
  4. options.ProviderOptions.DefaultAccessTokenScopes.Add(
  5. "{SERVER API APP CLIENT ID}/{SCOPE}");
  6. });

索引页面Index page

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

  1. <script src="_content/Microsoft.Authentication.WebAssembly.Msal/
  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)中呈现并管理以下行为:

  • 对于经过身份验证的用户:
    • 显示当前用户名。
    • 提供用于注销应用的按钮。
  • 对于匿名用户,提供登录选项。
  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. Hello, @context.User.Identity.Name!
  8. <button class="nav-link btn btn-link" @onclick="BeginSignOut">
  9. Log out
  10. </button>
  11. </Authorized>
  12. <NotAuthorized>
  13. <a href="authentication/login">Log in</a>
  14. </NotAuthorized>
  15. </AuthorizeView>
  16. @code {
  17. private async Task BeginSignOut(MouseEventArgs args)
  18. {
  19. await SignOutManager.SetSignOutState();
  20. Navigation.NavigateTo("authentication/logout");
  21. }
  22. }

身份验证组件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)。

其他资源Additional resources