使用标识服务器保护 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 托管" 复选框。
- 选择“创建”按钮。
若要在命令外壳中创建应用,请执行以下命令:
dotnet new blazorwasm -au Individual -ho
若要指定输出位置(如果它不存在,则创建一个项目文件夹),请在命令中包含 output 选项,其中包含一个路径(例如 -o BlazorSample
)。文件夹名称还会成为项目名称的一部分。
服务器应用配置Server app configuration
以下各节介绍了在包括身份验证支持时对项目的添加。
Startup 类Startup class
Startup
类具有以下附加项:
在
Startup.ConfigureServices
中:- 具有默认 UI 的标识:
services.AddDbContext<ApplicationDbContext>(options =>
options.UseSqlite(Configuration.GetConnectionString("DefaultConnection")));
services.AddDefaultIdentity<ApplicationUser>()
.AddDefaultUI(UIFramework.Bootstrap4)
.AddEntityFrameworkStores<ApplicationDbContext>();
- 使用附加的 AddApiAuthorization 帮助器方法,可在 IdentityServer 上设置某些默认的 ASP.NET Core 约定:
services.AddIdentityServer()
.AddApiAuthorization<ApplicationUser, ApplicationDbContext>();
- 使用附加的 AddIdentityServerJwt 帮助器方法进行身份验证,该方法将应用程序配置为验证 IdentityServer 生成的 JWT 令牌:
services.AddAuthentication()
.AddIdentityServerJwt();
在
Startup.Configure
中:- 负责验证请求凭据并在请求上下文上设置用户的身份验证中间件:
app.UseAuthentication();
- 公开 Open ID Connect (OIDC)终结点的 IdentityServer 中间件:
app.UseIdentityServer();
AddApiAuthorizationAddApiAuthorization
AddApiAuthorization helper 方法为 ASP.NET Core 情况配置IdentityServer 。IdentityServer 是一个功能强大且可扩展的框架,用于处理应用安全问题。在最常见的情况下,IdentityServer 会造成不必要的复杂性。因此,我们考虑到了一个很好的起点。身份验证需要更改后,IdentityServer 的全部功能仍可用于自定义身份验证,以满足应用程序的要求。
AddIdentityServerJwtAddIdentityServerJwt
AddIdentityServerJwt helper 方法为应用配置策略方案,作为默认身份验证处理程序。此策略配置为允许标识处理路由到标识 URL 空间中任何子路径的所有请求 /Identity
。JwtBearerHandler 处理所有其他请求。此外,此方法:
- 使用
{APPLICATION NAME}API
的默认范围向 IdentityServer 注册{APPLICATION NAME}API
API 资源。 - 将 JWT 持有者令牌中间件配置为验证 IdentityServer 为应用程序颁发的令牌。
WeatherForecastControllerWeatherForecastController
在 WeatherForecastController
(控制器/WeatherForecastController)中, [Authorize]
属性应用于类。属性指示用户必须根据默认策略进行授权才能访问资源。默认授权策略配置为使用默认身份验证方案,该方案由 AddIdentityServerJwt 设置为之前提到的策略方案。Helper 方法将 JwtBearerHandler 配置为应用程序请求的默认处理程序。
ApplicationDbContextApplicationDbContext
在 ApplicationDbContext
(Data/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
参数。配置文件指示正在配置的应用类型。此配置文件可在内部使用,以促进简化服务器配置过程的约定。
"IdentityServer": {
"Clients": {
"BlazorApplicationWithAuthentication.Client": {
"Profile": "IdentityServerSPA"
}
}
}
开发环境中的应用设置文件(appsettings)。开发)在项目根目录下,IdentityServer
部分介绍了用于对令牌进行签名的密钥。
"IdentityServer": {
"Key": {
"Type": "Development"
}
}
客户端应用配置Client app configuration
身份验证包Authentication package
创建应用以使用单个用户帐户(Individual
)时,应用会自动在应用的项目文件中接收 Microsoft.AspNetCore.Components.WebAssembly.Authentication
包的包引用。包提供一组基元,可帮助应用对用户进行身份验证,并获取令牌以调用受保护的 Api。
如果向应用程序中添加身份验证,请将包手动添加到应用的项目文件中:
<PackageReference
Include="Microsoft.AspNetCore.Components.WebAssembly.Authentication"
Version="{VERSION}" />
将前面的包引用中的 {VERSION}
替换为 ASP.NET Core Blazor 入门 一文中所示 Microsoft.AspNetCore.Blazor.Templates
包的版本。
API 授权支持API authorization support
通过 Microsoft.AspNetCore.Components.WebAssembly.Authentication
包内提供的扩展方法,将对用户进行身份验证的支持插入到服务容器中。此方法设置应用与现有授权系统交互所需的所有服务。
builder.Services.AddApiAuthorization();
默认情况下,它按约定从 _configuration/{client-id}
加载应用的配置。按照约定,将客户端 ID 设置为应用的程序集名称。可以通过使用选项调用重载,将此 URL 更改为指向不同的终结点。
索引页面Index page
"索引页(wwwroot/index.html)" 页包含一个用于在 JavaScript 中定义 AuthenticationService
的脚本。AuthenticationService
处理 OIDC 协议的低级别详细信息。应用在内部调用在脚本中定义的方法来执行身份验证操作。
<script src="_content/Microsoft.AspNetCore.Components.WebAssembly.Authentication/
AuthenticationService.js"></script>
应用组件App component
App
组件(app.config)类似于在 Blazor Server apps 中找到 App
组件:
CascadingAuthenticationState
组件管理向应用程序的其余部分公开AuthenticationState
。AuthorizeRouteView
组件确保当前用户有权访问给定页面或呈现RedirectToLogin
组件。RedirectToLogin
组件管理将未经授权的用户重定向到登录页。
<CascadingAuthenticationState>
<Router AppAssembly="@typeof(Program).Assembly">
<Found Context="routeData">
<AuthorizeRouteView RouteData="@routeData"
DefaultLayout="@typeof(MainLayout)">
<NotAuthorized>
<RedirectToLogin />
</NotAuthorized>
</AuthorizeRouteView>
</Found>
<NotFound>
<LayoutView Layout="@typeof(MainLayout)">
<p>Sorry, there's nothing at this address.</p>
</LayoutView>
</NotFound>
</Router>
</CascadingAuthenticationState>
RedirectToLogin 组件RedirectToLogin component
RedirectToLogin
组件(Shared/RedirectToLogin):
- 管理将未经授权的用户重定向到登录页。
- 保留用户尝试访问的当前 URL,以便在身份验证成功时可以将其返回到该页。
@inject NavigationManager Navigation
@using Microsoft.AspNetCore.Components.WebAssembly.Authentication
@code {
protected override void OnInitialized()
{
Navigation.NavigateTo($"authentication/login?returnUrl={Navigation.Uri}");
}
}
LoginDisplay 组件LoginDisplay component
LoginDisplay
组件(shared/LoginDisplay)在 MainLayout
组件(shared/MainLayout)中呈现并管理以下行为:
- 对于经过身份验证的用户:
- 显示当前用户名。
- 提供指向 ASP.NET Core 标识中的用户配置文件页的链接。
- 提供用于注销应用的按钮。
- 对于匿名用户:
- 提供注册的选项。
- 提供用于登录的选项。
@using Microsoft.AspNetCore.Components.Authorization
@using Microsoft.AspNetCore.Components.WebAssembly.Authentication
@inject NavigationManager Navigation
@inject SignOutSessionStateManager SignOutManager
<AuthorizeView>
<Authorized>
<a href="authentication/profile">Hello, @context.User.Identity.Name!</a>
<button class="nav-link btn btn-link" @onclick="BeginSignOut">
Log out
</button>
</Authorized>
<NotAuthorized>
<a href="authentication/register">Register</a>
<a href="authentication/login">Log in</a>
</NotAuthorized>
</AuthorizeView>
@code {
private async Task BeginSignOut(MouseEventArgs args)
{
await SignOutManager.SetSignOutState();
Navigation.NavigateTo("authentication/logout");
}
}
身份验证组件Authentication component
Authentication
组件(Pages/Authentication)生成的页面定义处理不同的身份验证阶段所需的路由。
RemoteAuthenticatorView
组件:
- 由
Microsoft.AspNetCore.Components.WebAssembly.Authentication
包提供。 - 管理在每个身份验证阶段执行适当的操作。
@page "/authentication/{action}"
@using Microsoft.AspNetCore.Components.WebAssembly.Authentication
<RemoteAuthenticatorView Action="@Action" />
@code {
[Parameter]
public string Action { get; set; }
}
FetchData 组件FetchData component
FetchData
组件显示了如何:
- 设置访问令牌。
- 使用访问令牌调用服务器应用中受保护的资源 API。
@attribute [Authorize]
指令向 Blazor WebAssembly 授权系统表明,用户必须获得授权才能访问此组件。如果客户端应用程序中存在该属性,则不会阻止在没有正确凭据的情况下调用服务器上的 API。服务器应用程序还必须在适当的终结点上使用 [Authorize]
,才能正确地对其进行保护。
AuthenticationService.RequestAccessToken();
负责请求可添加到请求中的访问令牌,以调用 API。如果该令牌已缓存,或者该服务在没有用户交互的情况下能够预配新的访问令牌,则令牌请求会成功。否则,令牌请求会失败。
为了获得要包含在请求中的实际标记,应用程序必须通过调用 tokenResult.TryGetToken(out var token)
来检查请求是否成功。
如果请求成功,将使用访问令牌填充令牌变量。此标记的 Value
属性公开要包含在 Authorization
请求标头中的文本字符串。
如果请求失败,因为无法在没有用户交互的情况下进行设置,令牌结果将包含重定向 URL。导航到此 URL 后,用户将进入登录页,并在身份验证成功后返回到当前页面。
@page "/fetchdata"
...
@attribute [Authorize]
...
@code {
private WeatherForecast[] forecasts;
protected override async Task OnInitializedAsync()
{
var httpClient = new HttpClient();
httpClient.BaseAddress = new Uri(Navigation.BaseUri);
var tokenResult = await AuthenticationService.RequestAccessToken();
if (tokenResult.TryGetToken(out var token))
{
httpClient.DefaultRequestHeaders.Add("Authorization",
$"Bearer {token.Value}");
forecasts = await httpClient.GetJsonAsync<WeatherForecast[]>(
"WeatherForecast");
}
else
{
Navigation.NavigateTo(tokenResult.RedirectUrl);
}
}
}
有关详细信息,请参阅在执行身份验证操作之前保存应用程序状态。
运行应用Run the app
从服务器项目运行应用。使用 Visual Studio 时,请在解决方案资源管理器中选择服务器项目,并在工具栏中选择 "运行" 按钮,或从 "调试" 菜单启动应用程序。
疑难解答Troubleshoot
由于 ID 令牌和访问令牌可在登录尝试期间保持,因此,每次更新后,请使用浏览器的开发人员控制台清除浏览器 cookie:
- 应用的身份验证代码或配置设置。
- 应用的配置 OIDC 兼容的提供程序(例如 Azure Active Directory)。