.Net8 新功能 Identity API endpoints 简介

本文译自:Introducing the Identity API endpoints ---Identity API endpoints简介

在这篇文章中,我将介绍以 API endpoints的形式添加到 .NET 8 的对 ASP.NET Core Identity 的新支持,可用于执行基本的 Identity 操作,例如注册用户。这些endpoints可作为 API 友好的替代方法,替代 ASP.NET Core Identity 已随附一段时间的 默认 UI。我将演示如何使用这些 API 与应用中受保护的 API 进行交互。

由于本文使用的是预览版 7 版本,因此在 .NET 8 最终于 2023 年 11 月发布之前,某些功能可能会更改、修复或删除!

什么是 ASP.NET Core Identity,为什么需要 API?

ASP.NET Core Identity 是 ASP.NET Core 附带的附加层/框架,提供用于管理 ASP.NET Core 应用程序中的用户帐户的各种服务。这包括每个服务的抽象和默认实现。

ASP.NET Core Identity 允许您在应用程序中存储用户帐户、管理用户详细信息、启用双因素身份验证以及将第三方登录与用户关联。从根本上说,它是关于将用户帐户存储在您的应用程序中,并使用这些帐户登录。

在Identity核心系统中需要考虑三个主要抽象:

  • 数据模型 - ASP.NET Core Identity 模型的基本类型。包括 IdentityUser 和 IdentityRole 等类型。
  • 存储 - 用于存储和检索数据模型类型实例的持久性层。包括接口(如 和 IRoleStore<> )以及一大堆细粒度接口(如 IUserPasswordStore<> IUserStore<> 等 IUserAuthenticatorKeyStore<> )。
  • 管理器 - 在存储顶部提供用于管理用户的服务层。有三个主要的抽象:
    • UserManager - 对用户执行相对高级的操作,例如从用户名和电子邮件创建用户、注意对密码进行哈希处理、生成规范化名称、更新安全标记等。
    • RoleManager - 与 类似,执行与角色相关的高级操作 UserManager ,例如在角色中添加或删除声明。
    • SignInManager - 提供高级 API,用于将用户登录和注销、验证双因素代码、检查帐户锁定等。
      ASP.NET Core Identity系结构的一般结构图。
      ASP.NET Core Identity系结构的一般结构图。

管理器提供了相对高级的 API 来与 Identity 进行交互,但您仍然需要实际调用接口才能使用它们。为了帮助完成此阶段,Microsoft 长期以来一直提供默认的 UI 包 Microsoft.AspNetCore.Identity.UI,其中包括 30 多个用于处理 Identity 的 Razor 页面。这包括用于登录和注销的页面、用于更改电子邮件和密码的页面、用于启用和管理拖曳因素身份验证 (2FA) 代码的页面等。
image

默认 UI 添加的代码量之大(因此节省了您的写作时间)是惊人的。您可以使用脚手架工具(如上所示)将此代码转储到您的应用程序中,以便您可以自定义页面,然后您就可以真正了解这里发生了多少事情!尝试自己实现所有这些通常是一件愚蠢的事情。

不幸的是,这让你处于一个尴尬的境地。如果您想使用 ASP.NET Core Identity 在应用中存储用户帐户,您需要采取以下三种主要路径之一:

  • 按原样使用默认的 Razor Pages UI。这意味着您只能使用默认样式,而不是可以在全局级别设置主题的任何内容。对于玩具,甚至是仅限内部的应用程序来说都很好,但对于其他任何东西来说都不是很好。
  • 搭建默认 Razor Pages UI 基架,然后自定义 Razor 模板。这意味着您可以自定义样式,但有很多页面需要更新,并且很难使基架代码保持最新状态。
  • 自己构建整个 UI。要创建很多页面。而且你最好希望你不要引入任何错误,因为登录和帐户页面可能是你应用中最敏感的部分。
    这些选项都不是特别吸引人。此外,如果要使用 Blazor(或 JavaScript)生成移动应用或生成 SPA,则使用 Razor Pages 的标识页与应用的其余部分之间存在技术和心理上的不协调。更不用说没有办法发行不记名令牌以用于移动设备(或 SPA 应用程序,如果您不使用 BFF 模式)。

这就是新的 Identity API endpoints的用武之地。

什么是新的 Identity API endpoints?

在 .NET 8 中,已向 ASP.NET Core 添加了帮助程序,用于将与标识相关的最小 API 终结点添加到应用。这些终结点大致等同于默认 Razor Pages UI 中的页面。这解决了两个主要用例:

  • 现在可以在使用 API 的 SPA 应用中轻松创建自己的 UI。这样可以使应用的标识/帐户界面的样式与应用的其余部分保持一致。
  • 在 ASP.NET Core 应用中调用其他 API 时,可以颁发访问 SPA 应用或移动应用使用的访问令牌。
    第一点的好处比较明显,但后一点值得进一步探讨。

对于默认的 Razor Pages UI,使用用户名和密码登录,应用通过设置身份验证 Cookie 登录。设置该 cookie 是登录过程的结果。
image

但是,移动应用程序中的 Cookie 通常难以管理。改用持有者令牌身份验证和存储访问令牌要容易得多。使用默认的 Razor Pages UI,这实际上是不可能的,但使用Identity API 检索令牌很简单:

POST http://localhost:5117/account/login
Content-Type: application/json

{
  "username": "andrew@example.com",
  "password": "SuperSecret1!"
}

此终结点给出如下所示的 JSON 响应(为简洁起见,我截断了令牌)

{
  "token_type": "Bearer",
  "access_token": "CfDJ8CuDyfVIT-VKm_2z2YS9T0jen4IyKKwsovVDRrrFyC_nU4HRXbiOjkOr64HnnXYT35pCnWdVXaE32Ztu6PgKhwawiTLLy9J5AYFVF3j9S9SEYoibQQyQ5L7pu",
  "expires_in": 3600,
  "refresh_token": "CfDJ8CuDyfVIT-VKm_2z2YS9T0gvL1EYfbVBnppccNrI6WrfRcsOb7LHPTC6VewrV8UUsARjkdFT5CPj_ofGTGWw_gWtsPtfKDjrlLqeYfvmYllWJ8-LssInwA_fn"
}

access_token的生存期相对较短(生存期可通过配置控制),但客户端可以使用返回值中的 refresh_token 获取新access_token,这都是很正常的东西,这些以前不是 ASP.NET Core Identity 内置的,现在是!

设计注意事项和限制

如您所料,使用这些 API 时需要考虑各种限制和事项。首先要考虑的是这些 API 是如何工作的。在幕后,他们实质上使用与默认 Identity UI 完全相同的 API。这意味着,如果您当前有一个 Identity 实现,您应该能够毫不费力地插入这些endpoints。

需要注意的一个重要功能是,Identity endpoints返回的访问令牌不是 JWT。令牌实质上包含与默认 UI 中的标识 Cookie 完全相同的信息。这使得endpoints的实现相对容易,但它也告诉你一些关于这些 API 的用途。

具体而言,Identity endpoints不能取代 IdentityServer 或 OpenIddict 令牌服务器。Identity 终结点生成的访问令牌仅供该应用使用。此外,它们旨在供交互式用户使用,而不是用于机器对机器的通信。

也就是说,您可以在 IdentityServer 实现中使用Identity endpoints。人们经常对 IdentityServer 和 ASP.NET Core Identity 之间的相互作用感到困惑,所以我打算尽快写一篇关于这一点以及 Identity endpoints适合在哪里的文章!

如果要构建简单的后端 + SPA 组合,那么新的Identity endpoints似乎是一个简单的选择。如果您正在构建更实质性的东西,那么您可能需要考虑像 IdentityServer 这样的 OpenID Connect 服务器。

也就是说,请注意,与使用已建立的“用于身份验证的 cookie + 用于应用程序的 OIDC”方法相比,选择将身份 API 端点与持有者令牌一起使用会带来大量限制、潜在的模拟和漏洞。我将在后续文章中详细介绍,但请注意,将 Identity 端点用于业余爱好项目以外的任何事情可能会有风险。

将Identity API 添加到应用程序

在本节中,我将创建一个演示应用程序,添加所有必备包和服务,最后映射标识 API。据我所知,身份 API 目前不包含在任何 dotnet new 模板中。

该 webapi 模板有一个 --auth 选项,但目前仅支持 Azure AD、Azure AD B2C 或 Windows 身份验证。我希望此模板将使用 .NET 8 的标识 API 使用 Individual 进行更新,但我们将拭目以待!

我们将首先创建一个新的 dotnet new webapi .我在这篇文章中的所有内容中都使用了 .NET 8 预览版 7 SDK。

通过运行下面的cli 在文件夹中生成初始模板

dotnet new webapi

要添加标识 API,我们需要做几件事:

  • 添加所需的软件包
  • 添加 EF Core
  • 添加所需的标识 EF Core 模型并生成迁移
  • 添加Identity API 和服务
  • 添加授权服务和中间件
    在以下各节中,我将逐一介绍这些步骤

添加 EF Core 包

首先,我们将 EF Core 添加到我们的应用程序,这需要引入各种包。

有关将 EF Core 添加到现有应用程序的更多详细信息,请参阅我的新书《ASP.NET Core in Action, Third Edition》第 12 章(第 12.2 节)

对于此演示,我将使用 SQLite 作为后备数据库,因为它既漂亮又简单。您可能不应该在 Web 应用程序的生产中使用 SQLite,但它适用于此类测试。
我们将从添加所需的包开始

# The main package for SQLite EF Core support
dotnet add package Microsoft.EntityFrameworkCore.SQLite --prerelease

# Contains shared build-time components for EF Core
dotnet add package Microsoft.EntityFrameworkCore.Design --prerelease

# The ASP.NET Core Identity integration for EF Core
dotnet add package Microsoft.AspNetCore.Identity.EntityFrameworkCore --prerelease

我已使用该 --prerelease 标志来确保我们拉入最新的包(.NET 8 预览版 7 包)。如果尚未安装该工具,则还需要安装该 ef 工具。我使用以下方法更新到该工具的最新(.NET 8 预览版)版本:

dotnet tool update --global dotnet-ef --prerelease

现在,我们拥有所需的所有工具和包,因此让我们将 EF Core 添加到我们的应用。

在应用中配置 EF CoreConfiguring EF Core in the app

若要开始使用 EF Core,我们需要在应用中实现 DbContext 。最 DbContext 简单的实现如下所示(请注意,我们将很快更新此实现以支持 Identity)

public class AppDbContext : DbContext
{
    public AppDbContext(DbContextOptions<AppDbContext> options)
     : base(options)
    {
    }
}

我们可以通过调用 AddDbContext<> 来 AppDbContext 注册我们的应用程序 WebApplicationBuilder :

using Microsoft.EntityFrameworkCore;

var builder = WebApplication.CreateBuilder(args);

// Add EF Core
builder.Services.AddDbContext<AppDbContext>(options =>
    options.UseSqlite(builder.Configuration.GetConnectionString("DefaultConnection")));

// ...

在上面的代码片段中,我们将 EF Core 配置为使用我们的 AppDbContext 类、使用 SQLite 以及使用名为 "DefaultConnection" .我们可以在 appsettings.json 中定义 connectionstring(请注意,我没有显示现有配置,只显示新键):

{
  "ConnectionStrings": {
    "DefaultConnection": "Data Source=my_test_app.db"
  }
}

这是 EF Core 配置的,因此现在我们可以配置 ASP.NET Core Identity。

添加Identity和 Identity API endpoint服务

在添加身份服务之前,我们需要做两件事:

  • 创建派生自 IdentityUser
  • 更新我们的 AppDbContext 派生自 IdentityDbContext<>

第一点并不是绝对必要的,因为如果需要,您可以直接在应用程序中使用 IdentityUser 。但考虑到你可能希望在某个时候自定义你的用户类型,我认为尽早使用自定义类型是有意义的。

public class AppUser : IdentityUser
{
    // Add customisations here later
}

                            // 👇 Change from DbContext to IdentityDbContext<>
public class AppDbContext : IdentityDbContext<AppUser>
{
    public AppDbContext(DbContextOptions<AppDbContext> options) : base(options)
    {
    }
}

在应用程序中创建这些类型后,现在可以使用新 AddIdentityApiEndpoints<> 方法添加标识服务,并配置标准Identity EF Core 存储:

builder.Services
    .AddIdentityApiEndpoints<AppUser>()
    .AddEntityFrameworkStores<AppDbContext>();

该 AddIdentityApiEndpoints<> 方法执行以下几项操作:

  • 配置 Bearer 和 cookie 身份验证(在后续文章中将详细介绍 cookie 身份验证!
  • 添加核心身份服务, 例如 UserManager
  • 添加 Identity API 端点所需的服务 SignInManager ,例如 、令牌提供程序和无操作 IEmailSender 实现
    添加所有这些服务后,我们终于可以搭建一些迁移的基架并创建数据库。

创建数据库

由于应用使用 EF Core,因此需要创建迁移并更新数据库。如果应用已成功生成,则应能够使用以下命令执行这两个步骤:

dotnet ef migrations add InitialSchema
dotnet ef database update

如果一切顺利,这应该会创建my_test_app.db SQLite 数据库文件。我们几乎已经准备好尝试我们的应用程序了。

向 API 添加授权

最终,我们希望能够使用授权来保护我们的 API,因此为了确保它正常工作,让我们向天气预报端点添加授权要求:

app.MapGet("/weatherforecast", () => /* not show for brevity */)
  .WithName("GetWeatherForecast")
  .RequireAuthorization() // 👈 Add this
  .WithOpenApi();

如果运行应用程序并尝试命中此终结点,则会收到一个错误,警告你尚未将授权中间件添加到应用程序:

System.InvalidOperationException: Endpoint HTTP: GET /weatherforecast contains
authorization metadata, but a middleware was not found that supports authorization.
Configure your application startup by adding app.UseAuthorization() in the application
startup code. If there are calls to app.UseRouting() and app.UseEndpoints(...), 
the call to app.UseAuthorization() must go between them.

因此,要解决此问题,请将授权服务添加到并 WebApplicationBuilder 添加授权中间件。您的应用现在应如下所示:

var builder = WebApplication.CreateBuilder(args);

// 👇 Add the authorization services
builder.Services.AddAuthorization();

// Add EF Core
builder.Services.AddDbContext<AppDbContext>(options =>
    options.UseSqlite(builder.Configuration.GetConnectionString("DefaultConnection"))); 

// add identity services
builder.Services
    .AddIdentityApiEndpoints<AppUser>()
    .AddEntityFrameworkStores<AppDbContext>();

// Swagger/OpenAPI services
builder.Services.AddEndpointsApiExplorer();
builder.Services.AddSwaggerGen();

var app = builder.Build();

if (app.Environment.IsDevelopment())
{
    app.UseSwagger();
    app.UseSwaggerUI();
}

app.UseHttpsRedirection();
app.UseAuthorization(); // 👈 Add Authorization middleware

// ...

剩下的就是添加 Identity endpoints!

添加身份 API endpoints

您可以通过调用 MapIdentityApi<> 将 WebApplication Identity 端点添加到您的应用程序。这会将各种终结点添加到应用(稍后会看到)。

例如,您很可能希望将这些范围限定为子路径(如 /account 或 /identity ),以便您将拥有 /account/login and /account/confirmEmail 等,而不是端点 /login 和 /confirmEmail 。您可以使用 添加前缀 MapGroup ,如以下示例所示:

app.MapGroup("/account").MapIdentityApi<AppUser>();

这样,我们就完成了!是时候探索该应用程序了,看看我们可以用它做什么。

探索Identity API

查看可用endpoints的最简单方法是运行应用程序并导航到 /swagger/index.html SwaggerUI 文档:

image

在列表顶部,您可以看到 /weatherforecast API,下面是 添加的所有端点 MapIdentityApi<>() 。您可以使用 SwaggerUI 与 API 进行交互,但我不想在 UI 中弄乱令牌,所以我决定改用 Rider 的内置 HttpClient 支持。

Visual Studio 最近也获得了对 .http 文件的支持,因此你可以在那里执行几乎相同的操作。

测试受保护的端点

对我来说,第一个有趣的点是 Rider 的 Endpoints 窗口选取了端点,它没有选取 Identity 端点,即使启用了 /weatherforecast Show from libraries 选项。尽管如此,这是我们无论如何都要测试的第一个端点,以确保它是安全的。我右键单击端点并选择了 Generate Request in HTTP Client:
image

这会在 .http 文件中生成对终结点的简单“GET”请求:
image

单击装订线中的“运行”图标,将请求设置为端点,不出所料,返回响应 401 Unauthorized ,指示 API 受到正确保护:

GET http://localhost:5117/weatherforecast

HTTP/1.1 401 Unauthorized
Content-Length: 0
Date: Sat, 02 Sep 2023 19:46:02 GMT
Server: Kestrel
WWW-Authenticate: Bearer

调用此端点的第一步是创建一个我们可以登录的用户。

注册新用户

我们将查看的第一个端点是 /register 端点(在我的示例中公开)。 /account/register 这是一个 POST 端点,我们向其发送电子邮件、用户名和密码:

### Register a new user
POST http://localhost:5117/account/register
Content-Type: application/json

{
  "username": "andrew@example.com",
  "password": "SuperSecret1!",
  "email": "andrew@example.com"
}

如果此操作成功,您将收到回复 200 。请注意,您发送的密码必须满足所有标准 IdentityOptions 要求,因此,如果您不符合长度或复杂性要求,则该密码将被拒绝。

顺便说一句,我建议你摆脱对用户密码的大部分复杂性要求,增加最小长度要求,并集成一个验证器来检查常用或违反的密码,例如 haveibeenpwned.com 上公开的密码。

检索访问令牌

假设用户创建正确,我们现在需要检索访问令牌。为此,我们可以使用以下 /login 路径:

### Login and retrieve tokens
POST http://localhost:5117/account/login
Content-Type: application/json

{
  "username": "andrew@example.com",
  "password": "SuperSecret1!"
}

如果提供有效的用户名和密码,则会返回与之前看到的响应类似的响应:

{
  "token_type": "Bearer",
  "access_token": "CfDJ8CuDyfVIT-VKm_2z2YS9T0jen4IyKKwsovVDRrrFyC_nU4HRXb...",
  "expires_in": 3600,
  "refresh_token": "CfDJ8CuDyfVIT-VKm_2z2YS9T0gvL1EYfbVBnppccNrI6WrfRcsOb..."
}

我们可以使用 Rider HttpClient 的内置脚本自动从响应中获取此令牌,并将其保存到我们稍后可以使用的变量中:

> {% 
    client.global.set("access_token", response.body.access_token); 
    client.global.set("refresh_token", response.body.refresh_token); 
%}

所以整个事情在 Rider 中看起来像这样:
image

调用受保护的 API

现在我们有了access_token,我们可以使用它来调用受保护的 API:

### Call Forecast API with bearer token
GET http://localhost:5117/weatherforecast
Authorization: Bearer {{access_token}}

现在,这将返回我们预期的 JSON 响应!

[
  {
    "date": "2023-09-03",
    "temperatureC": 20,
    "summary": "Chilly",
    "temperatureF": 67
  },
  ...
]

生成刷新令牌

最终,访问令牌将过期,我们需要获取一个新的令牌。我们可以使用 /refresh 端点来做到这一点:

### Fetch a new access token
POST http://localhost:5117/account/refresh
Content-Type: application/json

{
  "refreshToken": "{{refresh_token}}"
}

请注意,这还会返回一个新的刷新令牌。
您可以调用许多其他端点来为用户管理 2fa 令牌等,但这篇文章已经足够长了,所以我将把它留给读者作为练习!

Summary 总结

在这篇文章中,我介绍了 ASP.NET Core Identity 的现有状态、它提供的各种抽象,以及 Razor Pages 默认 UI 提供的功能量。然后,我描述了 UI 的一些问题,特别是样式的复杂性以及与 SPA 或移动应用程序的集成。

接下来,我演示了如何将 ASP.NET Core Identity 添加到新应用程序并使用 Identity API。我从空白模板开始,因此添加了 EF Core、标识,最后添加了标识 API。然后,我演示了如何使用 HTTP 客户端与 API 进行交互。

posted @ 2023-11-03 19:34  dongfo  阅读(1031)  评论(0编辑  收藏  举报