如何使用 Identity 保护 SPA 的 Web API 后端

注意

此版本不是本文的最新版本。 有关当前版本,请参阅本文.NET 9 版本。

警告

此版本的 ASP.NET Core 不再受支持。 有关详细信息,请参阅 .NET 和 .NET Core 支持策略。 有关当前版本,请参阅本文.NET 9 版本。

重要

此信息与预发布产品相关,相应产品在商业发布之前可能会进行重大修改。 Microsoft 对此处提供的信息不提供任何明示或暗示的保证。

有关当前版本,请参阅本文.NET 9 版本。

ASP.NET Core Identity 提供用于处理身份验证、授权和 identity 管理的 API。 通过 API,可以使用基于 cookie 身份验证保护 Web API 后端的终结点。 基于令牌的选项适用于无法使用 Cookie 的客户端,但在使用此选项时,你负责确保令牌保持安全。 建议对基于浏览器的应用程序使用 Cookie,因为默认情况下,浏览器会自动处理它们,而不会将它们公开给 JavaScript。

本文演示如何使用 Identity 保护 SPA 的 Web API 后端,例如 Angular、React 和 Vue 应用。 同一后端 API 可用于保护 Blazor WebAssembly 应用

先决条件

本文中介绍的步骤将身份验证和授权添加到 ASP.NET 核心 Web API 应用,该应用:

  • 尚未为身份验证配置。
  • net8.0 或更高版本为目标。
  • 可以是最小 API 或基于控制器的 API。

本文中的一些测试说明使用了项目模板随附的 Swagger UI。 Swagger UI 不需要与 Web API 后端一起使用 Identity。

安装 NuGet 包

安装以下 NuGet 包:

若要快速开始使用,请使用内存中数据库。

稍后将数据库更改为 SQLite 或 SQL Server,以便在测试或供生产使用时在会话之间保存用户数据。 与内存中相比,这引入了一些复杂性,因为它需要通过迁移创建数据库,如入门教程EF Core所示。

使用 Visual Studio 中的 NuGet 包管理器或 dotnet add 包 CLI 命令安装这些包。

创建 IdentityDbContext

添加一个从 IdentityDbContext<TUser> 继承的名为 ApplicationDbContext 的类:

using Microsoft.AspNetCore.Identity.EntityFrameworkCore;
using Microsoft.AspNetCore.Identity;
using Microsoft.EntityFrameworkCore;

public class ApplicationDbContext : IdentityDbContext<IdentityUser>
{
    public ApplicationDbContext(DbContextOptions<ApplicationDbContext> options) :
        base(options)
    { }
}

显示的代码提供了一个特殊的构造函数,可用于为不同的环境配置数据库。

添加这些步骤中显示的代码时,根据需要添加以下 using 一个或多个指令。

using Microsoft.AspNetCore.Identity;
using Microsoft.AspNetCore.Identity.EntityFrameworkCore;
using Microsoft.EntityFrameworkCore;

配置 EF Core 下文

如前所述,最简单的入门方法是使用内存中数据库。 在内存中,每次运行都以新的数据库开头,无需使用迁移。 调用 WebApplication.CreateBuilder(args) ,添加以下代码以配置 Identity,以使用内存中数据库:

builder.Services.AddDbContext<ApplicationDbContext>(
    options => options.UseInMemoryDatabase("AppDb"));

若要在测试或供生产使用时在会话之间保存用户数据,请稍后将数据库更改为 SQLite 或 SQL Server。

向容器添加 Identity 服务

调用 WebApplication.CreateBuilder(args) ,调用 AddAuthorization 以将服务添加到依赖项注入 (DI) 容器:

builder.Services.AddAuthorization();

激活 Identity API

调用 WebApplication.CreateBuilder(args) ,调用 AddIdentityApiEndpoints<TUser>(IServiceCollection)AddEntityFrameworkStores<TContext>(IdentityBuilder)

builder.Services.AddIdentityApiEndpoints<IdentityUser>()
    .AddEntityFrameworkStores<ApplicationDbContext>();

默认情况下,激活 Cookie 令牌和专有令牌。 如果登录终结点中的 useCookies 查询字符串参数为 true,则会在登录时发出 Cookie 和令牌。

映射 Identity 路由

调用 builder.Build() 后,调用 MapIdentityApi<TUser>(IEndpointRouteBuilder) 以映射 Identity 终结点:

app.MapIdentityApi<IdentityUser>();

保护所选的终结点

若要保护终结点,请在定义路由的 Map{Method} 调用上使用 RequireAuthorization 扩展方法。 例如:

app.MapGet("/weatherforecast", (HttpContext httpContext) =>
{
    var forecast = Enumerable.Range(1, 5).Select(index =>
        new WeatherForecast
        {
            Date = DateOnly.FromDateTime(DateTime.Now.AddDays(index)),
            TemperatureC = Random.Shared.Next(-20, 55),
            Summary = summaries[Random.Shared.Next(summaries.Length)]
        })
        .ToArray();
    return forecast;
})
.WithName("GetWeatherForecast")
.WithOpenApi()
.RequireAuthorization();

RequireAuthorization 方法还可用于:

  • 保护 Swagger UI 终结点,如以下示例所示:

    app.MapSwagger().RequireAuthorization();
    
  • 使用特定声明或权限进行保护,如以下示例所示:

    .RequireAuthorization("Admin");
    

在基于控制器的 Web API 项目中,通过将 [Authorize] 属性应用于控制器或操作来保护终结点。

测试 API

测试身份验证的一种快速方法是使用项目模板附带的内存中数据库和 Swagger UI。 以下步骤演示如何使用 Swagger UI 测试 API。 确保 Swagger UI 终结点不受保护

尝试访问受保护的终结点

  • 运行应用并导航到 Swagger UI。
  • 展开受保护的终结点,例如 Web API 模板在项目中创建的 /weatherforecast
  • 选择“试用”。
  • 选择“执行”。 响应为 401 - not authorized

测试注册

  • 展开 /register 并选择“试用”。

  • 在 UI 的“参数”部分中,会显示示例请求正文:

    {
      "email": "string",
      "password": "string"
    }
    
  • 将“string”替换为有效的电子邮件地址和密码,然后选择“执行”。

    要遵守默认密码验证规则,密码长度必须至少为 6 个字符,并且至少包含以下每种字符之一:

    • 大写字母
    • 小写字母
    • 数字
    • 非字母数字字符

    如果输入无效的电子邮件地址或密码错误,则结果包括验证错误。 下面是包含验证错误的响应正文的示例:

    {
      "type": "https://tools.ietf.org/html/rfc9110#section-15.5.1",
      "title": "One or more validation errors occurred.",
      "status": 400,
      "errors": {
        "PasswordTooShort": [
          "Passwords must be at least 6 characters."
        ],
        "PasswordRequiresNonAlphanumeric": [
          "Passwords must have at least one non alphanumeric character."
        ],
        "PasswordRequiresDigit": [
          "Passwords must have at least one digit ('0'-'9')."
        ],
        "PasswordRequiresLower": [
          "Passwords must have at least one lowercase ('a'-'z')."
        ]
      }
    }
    

    错误是以 ProblemDetails 格式返回的,因此客户端可以对其进行分析,并根据需要显示验证错误。

    成功注册会带来 200 - OK 响应。

测试登录

  • 展开 /login 并选择“试用”。示例请求正文显示两个附加参数:

    {
      "email": "string",
      "password": "string",
      "twoFactorCode": "string",
      "twoFactorRecoveryCode": "string"
    }
    

    此示例不需要额外的 JSON 属性,可以删除。 将 useCookies 设置为 true

  • 将“string”替换为用于注册的电子邮件地址和密码,然后选择“执行”。

    成功登录会带来 200 - OK 响应及响应标头中包含 cookie。

重新测试安全终结点

成功登录后,重新运行受保护的终结点。 cookie 身份验证随请求一起自动发送,并授权终结点。 基于 Cookie 的身份验证安全地内置到浏览器,并“正常工作”。

使用非浏览器客户端进行测试

默认情况下,某些 Web 客户端的标头中可能不包括 Cookie:

  • 如果使用工具来测试 API,则可能需要在设置中启用 Cookie。

  • 默认情况下,JavaScript fetch API 不包含 Cookie。 通过在选项中将 credentials 设置为值 include,以启用它们。

  • 在 Blazor WebAssembly 应用中运行的 HttpClient 需要 HttpRequestMessage,以包括凭据,如下所示:

    request.SetBrowserRequestCredential(BrowserRequestCredentials.Include);
    

使用基于令牌的身份验证

建议在基于浏览器的应用程序中使用 Cookie,因为默认情况下,浏览器会自动处理它们,而不会将它们公开给 JavaScript。

将颁发一个可用于对后续请求进行身份验证的自定义令牌(ASP.NET Core identity 平台专有的令牌)。 该令牌作为持有者令牌在 Authorization 标头中传递。 还会提供刷新令牌。 此令牌允许应用在旧令牌过期时请求新令牌,而无需强制用户再次登录。

该令牌不是标准的 JSON Web 令牌 (JWT)。 使用自定义令牌是有意而为的,因为内置 Identity API 主要用于简单方案。 令牌选项并不旨在作为全功能 identity 服务提供程序或令牌服务器,而是为无法使用 Cookie 的客户端提供 cookie 选项的备用项。

要使用基于令牌的身份验证,请在调用 /login 终结点时将 useCookies 查询字符串参数设置为 false。 令牌使用持有者身份验证方案。 使用从对 /login 的调用中返回的令牌,对受保护终结点的后续调用应添加标头 Authorization: Bearer <token>,其中 <token> 为访问令牌。 有关详细信息,请参阅本文后面的使用 POST /login 终结点

注销

要为用户提供注销方法,请定义一个 /logout 终结点,如以下示例所示:

app.MapPost("/logout", async (SignInManager<IdentityUser> signInManager,
    [FromBody] object empty) =>
{
    if (empty != null)
    {
        await signInManager.SignOutAsync();
        return Results.Ok();
    }
    return Results.Unauthorized();
})
.WithOpenApi()
.RequireAuthorization();

调用此终结点时,在请求正文中提供空 JSON 对象 ({})。 以下代码是调用注销终结点的示例:

public signOut() {
  return this.http.post('/logout', {}, {
    withCredentials: true,
    observe: 'response',
    responseType: 'text'

MapIdentityApi<TUser> 终结点

MapIdentityApi<TUser> 进行调用会向应用添加以下终结点:

使用 POST /register 终结点

请求正文必须包含 EmailPassword 属性:

{
  "email": "string",
  "password": "string",
}

有关详细信息,请参阅:

使用 POST /login 终结点

在请求正文中,EmailPassword 是必需的。 如果启用了双因素身份验证 (2FA),则 TwoFactorCodeTwoFactorRecoveryCode 是必需的。 如果未启用 2FA,则省略 twoFactorCodetwoFactorRecoveryCode。 有关详细信息,请参阅本文后面的使用 POST /manage/2fa 终结点

下面是未启用 2FA 的请求正文示例:

{
  "email": "string",
  "password": "string"
}

下面是启用了 2FA 的请求正文示例:

  • {
      "email": "string",
      "password": "string",
      "twoFactorCode": "string",
    }
    
  • {
      "email": "string",
      "password": "string",
      "twoFactorRecoveryCode": "string"
    }
    

终结点需要查询字符串参数:

  • useCookies - 设置为 true,以进行基于 cookie 的身份验证。 设置为 false 或将其省略,以进行基于令牌的身份验证。

有关基于 cookie 的身份验证的详细信息,请参阅本文前面的测试登录

基于令牌的身份验证

如果 useCookiesfalse 或省略,则启用基于令牌的身份验证。 响应正文包含以下属性:

{
  "tokenType": "string",
  "accessToken": "string",
  "expiresIn": 0,
  "refreshToken": "string"
}

有关这些属性的详细信息,请参阅 AccessTokenResponse

将访问令牌放在标头中以发出经过身份验证的请求,如以下示例所示

Authorization: Bearer {access token}

当访问令牌即将过期时,调用 /refresh 终结点。

使用 POST /refresh 终结点

仅用于基于令牌的身份验证。 获取新的访问令牌,而无需强制用户再次登录。 请在访问令牌即将过期时调用此终结点。

请求正文仅包含 RefreshToken。 下面是请求正文示例:

{
  "refreshToken": "string"
}

如果调用成功,响应正文为新的 AccessTokenResponse,如以下示例所示:

{
  "tokenType": "string",
  "accessToken": "string",
  "expiresIn": 0,
  "refreshToken": "string"
}

使用 GET /confirmEmail 终结点

如果设置 Identity 以确认电子邮件,则成功调用终结点 /register 会发送一封电子邮件,其中包含指向 /confirmEmail 终结点的链接。 该链接包含以下查询字符串参数:

  • userId
  • code
  • changedEmail - 仅当用户在注册期间更改了电子邮件地址时才包含。

Identity 为确认电子邮件提供默认文本。 默认情况下,电子邮件主题为“确认电子邮件”,电子邮件正文的外观则如以下示例所示:

 Please confirm your account by <a href='https://contoso.com/confirmEmail?userId={user ID}&code={generated code}&changedEmail={new email address}'>clicking here</a>.

如果将 RequireConfirmedEmail 属性设置为 true,则在通过单击电子邮件中的链接确认电子邮件地址之前,用户无法登录。 /confirmEmail 终结点:

  • 确认电子邮件地址,以支持用户登录。
  • 在响应正文中返回文本“感谢确认电子邮件”。

要设置 Identity 以实现电子邮件确认,请在 Program.cs 中添加代码,以将 RequireConfirmedEmail 设置为 true,并向 DI 容器添加可实现 IEmailSender 的类。 例如:

builder.Services.Configure<IdentityOptions>(options =>
{
    options.SignIn.RequireConfirmedEmail = true;
});

builder.Services.AddTransient<IEmailSender, EmailSender>();

有关详细信息,请参阅 ASP.NET Core 中的帐户确认和密码恢复

Identity 还为需要发送的其他电子邮件提供默认文本,例如 2FA 和密码重置。 若要自定义这些电子邮件,请提供 IEmailSender 接口的自定义实现。 在前面的示例中,EmailSender 是实现 IEmailSender 的类。 有关详细信息,包括实现 IEmailSender的类的示例,请参阅 在 ASP.NET Core 中确认帐户和恢复密码

使用 POST /resendConfirmationEmail 终结点

仅当地址对已注册用户有效时,才发送电子邮件。

请求正文仅包含 Email。 下面是请求正文示例:

{
  "email": "string"
}

有关详细信息,请参阅本文前面的使用 GET /confirmEmail 终结点

使用 POST /forgotPassword 终结点

生成包含密码重置代码的电子邮件。 使用新密码将该代码发送到 /resetPassword

请求正文仅包含 Email。 下面是一个示例:

{
  "email": "string"
}

有关如何启用 Identity 来发送电子邮件的信息,请参阅使用 GET /confirmEmail 终结点

使用 POST /resetPassword 终结点

通过调用 /forgotPassword 终结点在获取重置代码后调用此终结点。

请求正文需要 EmailResetCodeNewPassword。 下面是一个示例:

{
  "email": "string",
  "resetCode": "string",
  "newPassword": "string"
}

使用 POST /manage/2fa 终结点

为用户配置双因素身份验证 (2FA)。 启用 2FA 后,除了电子邮件地址和密码之外,成功登录还需要使用验证器应用生成的代码。

启用 2FA

要为当前经过身份验证的用户启用 2FA,请:

  • 调用 /manage/2fa 终结点,以在请求正文中发送一个空的 JSON 对象 ({})。

  • 响应正文会提供 SharedKey,以及目前并不需要的一些其他属性。 共享密钥用于设置验证器应用。 响应正文示例:

    {
      "sharedKey": "string",
      "recoveryCodesLeft": 0,
      "recoveryCodes": null,
      "isTwoFactorEnabled": false,
      "isMachineRemembered": false
    }
    
  • 使用共享密钥获取基于时间的一次性密码 (TOTP)。 有关详细信息,请参阅在 ASP.NET Core 中为 TOTP 验证器应用启用 QR 码生成

  • 调用 /manage/2fa 终结点,以在请求正文中发送 TOTP 和 "enable": true。 例如:

    {
      "enable": true,
      "twoFactorCode": "string"
    }
    
  • 响应正文会确认 IsTwoFactorEnabled 为 true 并提供 RecoveryCodes。 恢复代码用于在验证器应用不可用时进行登录。 成功启用 2FA 后的响应正文示例:

    {
      "sharedKey": "string",
      "recoveryCodesLeft": 10,
      "recoveryCodes": [
        "string",
        "string",
        "string",
        "string",
        "string",
        "string",
        "string",
        "string",
        "string",
        "string"
      ],
      "isTwoFactorEnabled": true,
      "isMachineRemembered": false
    }
    

使用 2FA 登录

调用 /login 终结点,以在请求正文中发送电子邮件地址、密码和 TOTP。 例如:

{
  "email": "string",
  "password": "string",
  "twoFactorCode": "string"
}

如果用户无权访问验证器应用,请使用启用 2FA 时提供的恢复代码之一调用 /login 终结点来进行登录。 请求正文如下方示例所示:

{
  "email": "string",
  "password": "string",
  "twoFactorRecoveryCode": "string"
}

重置恢复代码

要获取新的恢复代码集合,请在将 ResetRecoveryCodes 设置为 true 的情况下调用此终结点。 下面是请求正文示例:

{
  "resetRecoveryCodes": true
}

获取共享密钥

要获取新的随机共享密钥,请在将 ResetSharedKey 设置为 true 的情况下调用此终结点。 下面是请求正文示例:

{
  "resetSharedKey": true
}

在以后的请求重新启用要求已经过身份验证的用户进行双因素登录之前,重置密钥会自动禁用该要求。

忘记计算机

要清除 cookie“记住我标记”(如果存在),请在将 ForgetMachine 设置为 true 的情况下调用此终结点。 下面是请求正文示例:

{
  "forgetMachine": true
}

此终结点不会影响基于令牌的身份验证。

使用 GET /manage/info 终结点

获取已登录用户的电子邮件地址和电子邮件确认状态。 出于安全原因,已从此终结点中省略声明。 如果需要声明,请使用服务器端 API 为声明设置终结点。 或提供接受声明并响应用户是否拥有该声明的验证终结点,而不是共享所有用户的声明。

请求不需要任何参数。 响应正文包括属性 EmailIsEmailConfirmed,如以下示例所示:

{
  "email": "string",
  "isEmailConfirmed": true
}

使用 POST /manage/info 终结点

更新已登录用户的电子邮件地址和密码。 在请求正文中发送 NewEmailNewPasswordOldPassword,如以下示例所示:

{
  "newEmail": "string",
  "newPassword": "string",
  "oldPassword": "string"
}

下面是响应正文的示例:

{
  "email": "string",
  "isEmailConfirmed": false
}

另请参阅

有关更多信息,请参阅以下资源:

ASP.NET Core 模板使用 API 授权支持在单页应用 (SPA) 中提供身份验证。 用于验证和存储用户身份信息的 ASP.NET Core Identity 与用于实现 Open ID Connect 的 Duende Identity Server 结合使用。

重要

Duende Software 可能会要求你为 Duende Identity Server 的生产使用支付许可证费用。 有关详细信息,请参阅从 ASP.NET Core 5.0 迁移到 6.0

一个身份验证参数已添加到 Angular 和 React 项目模板,该参数类似于 Web 应用(模型-视图-控制器)(MVC) 和 Web 应用 (Razor Pages) 项目模板中的身份验证参数。 允许的参数值为 None 和 Individual。 React.js 和 Redux 项目模板目前不支持该身份验证参数。

使用 API 授权支持创建应用

用户身份验证和授权可以与 Angular 和 React SPA 结合使用。 打开命令行界面,然后运行以下命令:

Angular:

dotnet new angular -au Individual

React:

dotnet new react -au Individual

上面的命令创建一个 ASP.NET Core 应用,它具有包含 SPA 的 ClientApp 目录。

应用的 ASP.NET Core 组件的一般说明

以下部分介绍了在包括身份验证支持的情况下对项目添加的内容:

Program.cs

下面的代码示例依赖于 Microsoft.AspNetCore.ApiAuthorization.IdentityServer NuGet 包。 该示例使用 AddApiAuthorizationAddIdentityServerJwt 扩展方法配置 API 身份验证和授权。 使用带身份验证的 React 或 Angular SPA 项目模板的项目包含对此包的引用。

dotnet new angular -au Individual 生成以下 Program.cs 文件:

using Microsoft.AspNetCore.Authentication;
using Microsoft.EntityFrameworkCore;
using output_directory_name.Data;
using output_directory_name.Models;

var builder = WebApplication.CreateBuilder(args);

var connectionString = builder.Configuration.GetConnectionString("DefaultConnection") ?? throw new InvalidOperationException("Connection string 'DefaultConnection' not found.");
builder.Services.AddDbContext<ApplicationDbContext>(options =>
    options.UseSqlite(connectionString));
builder.Services.AddDatabaseDeveloperPageExceptionFilter();

builder.Services.AddDefaultIdentity<ApplicationUser>(options => options.SignIn.RequireConfirmedAccount = true)
    .AddEntityFrameworkStores<ApplicationDbContext>();

builder.Services.AddIdentityServer()
    .AddApiAuthorization<ApplicationUser, ApplicationDbContext>();

builder.Services.AddAuthentication()
    .AddIdentityServerJwt();

builder.Services.AddControllersWithViews();
builder.Services.AddRazorPages();

var app = builder.Build();

if (app.Environment.IsDevelopment())
{
    app.UseMigrationsEndPoint();
}
else
{
    app.UseHsts();
}

app.UseHttpsRedirection();
app.UseStaticFiles();
app.UseRouting();

app.UseAuthentication();
app.UseIdentityServer();
app.UseAuthorization();

app.MapControllerRoute(
    name: "default",
    pattern: "{controller}/{action=Index}/{id?}");
app.MapRazorPages();

app.MapFallbackToFile("index.html");

app.Run();

上述代码配置:

  • 包含默认 UI 的 Identity:

    builder.Services.AddDbContext<ApplicationDbContext>(options =>
        options.UseSqlite(connectionString));
    builder.Services.AddDatabaseDeveloperPageExceptionFilter();
    
    builder.Services.AddDefaultIdentity<ApplicationUser>(options => options.SignIn.RequireConfirmedAccount = true)
        .AddEntityFrameworkStores<ApplicationDbContext>();
    
  • 包含附加 AddApiAuthorization 帮助器方法的 IdentityServer,该方法在 IdentityServer 之上设置默认 ASP.NET Core 约定:

    builder.Services.AddIdentityServer()
        .AddApiAuthorization<ApplicationUser, ApplicationDbContext>();
    
  • 包含附加 AddIdentityServerJwt 帮助器方法的身份验证,该方法将应用配置为验证由 IdentityServer 生成的 JWT 令牌:

    builder.Services.AddAuthentication()
    .AddIdentityServerJwt();
    
  • 负责验证请求凭据并在请求上下文中设置用户的身份验证中间件:

    app.UseAuthentication();
    
  • 公开 OpenID Connect 终结点的 IdentityServer 中间件:

    app.UseIdentityServer();
    

警告

本文介绍连接字符串的使用。 使用本地数据库时,用户无需进行身份验证,但在生产环境中,连接字符串有时包括进行身份验证的密码。 资源所有者密码凭据(ROPC)是在生产数据库中应避免的安全风险。 生产应用应使用可用的最安全的身份验证流。 有关部署到测试或生产环境的应用的身份验证的详细信息,请参阅 安全身份验证流

Linux 上的 Azure 应用服务

对于 Linux 上的 Azure 应用服务部署,请显式指定颁发者:

builder.Services.Configure<JwtBearerOptions>(
    IdentityServerJwtConstants.IdentityServerJwtBearerScheme, 
    options =>
    {
        options.Authority = "{AUTHORITY}";
    });

在上面的代码中,{AUTHORITY} 占位符是在进行 OpenID Connect 调用时要使用的 Authority

示例:

options.Authority = "https://contoso-service.azurewebsites.net";

AddApiAuthorization

此帮助程序方法将 IdentityServer 配置为使用支持的配置。 IdentityServer 是一个功能强大且可扩展的框架,用于处理应用安全问题。 同时,该框架会公开大多数情况下不必要的复杂性。 因此,提供了一组约定和配置选项作为良好的起点。 一旦身份验证需要更改,仍可使用 IdentityServer 的完整功能自定义身份验证以满足你的需求。

AddIdentityServerJwt

此帮助程序方法将应用的策略方案配置为默认身份验证处理程序。 该策略配置为允许 Identity 处理路由到 Identity URL 空间“/Identity”中任何子路径的所有请求。 JwtBearerHandler 处理所有其他请求。 此外,此方法向 IdentityServer 注册一个默认范围为 <<ApplicationName>>API<<ApplicationName>>API API 资源,并将 JWT 持有者令牌中间件配置为验证 IdentityServer 为应用颁发的令牌。

WeatherForecastController

在文件中,请注意应用于类的 [Authorize] 属性,该属性指示用户需要基于默认策略进行授权才能访问资源。 默认授权策略恰好配置为使用默认身份验证方案,而该方案由 AddIdentityServerJwt 设置为上面提到的策略方案,从而使此类帮助程序方法配置的 JwtBearerHandler 成为对应用进行的请求的默认处理程序。

ApplicationDbContext

在文件中,请注意,在 Identity 中使用了相同的 DbContext,只不过它会扩展 ApiAuthorizationDbContext(一个来自 IdentityDbContext 的派生程度更高的类)以包括 IdentityServer 的架构。

要获取对数据库架构的完全控制,请从其中一个可用的 IdentityDbContext 类继承,并通过对 OnModelCreating 方法调用 builder.ConfigurePersistedGrantContext(_operationalStoreOptions.Value) 来配置上下文以包括 Identity 架构。

OidcConfigurationController

在文件中,请注意为处理客户端需要使用的 OIDC 参数而预配的终结点。

appsettings.json

在项目根目录的 appsettings.json 文件中,有一个新的 IdentityServer 部分,描述已配置的客户端列表。 下例中存在一个客户端。 客户端名称对应于应用名称,并通过约定映射到 OAuth ClientId 参数。 配置文件指示正在配置的应用类型。 它在内部用于促进简化服务器配置过程的约定。 有多个配置文件可用(在应用程序配置文件部分中进行了说明)。

"IdentityServer": {
  "Clients": {
    "angularindividualpreview3final": {
      "Profile": "IdentityServerSPA"
    }
  }
}

appsettings.Development.json

在项目根目录的 appsettings.Development.json 文件中,有一个 IdentityServer 部分,描述用于对令牌进行签名的密钥。 部署到生产时,密钥需要与应用一起进行预配和部署(在部署到生产部分中进行了说明)。

"IdentityServer": {
  "Key": {
    "Type": "Development"
  }
}

Angular 应用的一般说明

Angular 模板中的身份验证和 API 授权支持位于其自己的 Angular 模块中(在 ClientApp/src/api-authorization 目录中)。 该模块由以下元素组成:

  • 3 个组件:
    • login.component.ts:处理应用的登录流。
    • logout.component.ts:处理应用的注销流。
    • login-menu.component.ts:显示以下链接集之一的小组件:
      • 用户已经过身份验证时的用户配置文件管理和注销链接。
      • 用户未经过身份验证时的注册和登录链接。
  • 一个路由防护 AuthorizeGuard,可以添加到路由,要求用户先经过身份验证,然后才能访问路由。
  • 一个 HTTP 侦听器 AuthorizeInterceptor,在用户经过身份验证时将访问令牌附加到针对 API 的传出 HTTP 请求。
  • 一个服务 AuthorizeService,处理身份验证过程的较低级别详细信息,并向应用的 rest 公开有关已经过身份验证的用户的信息以供使用。
  • 一个 Angular 模块,定义与应用的身份验证部分关联的路由。 它公开登录菜单组件、侦听器、保护措施和服务,以便从应用的 rest 进行使用。

React 应用的一般说明

React 模板中的身份验证和 API 授权支持位于 ClientApp/src/components/api-authorization 目录中。 它由以下元素组成:

  • 4 个组件:
    • Login.js:处理应用的登录流。
    • Logout.js:处理应用的注销流。
    • LoginMenu.js:显示以下链接集之一的小组件:
      • 用户已经过身份验证时的用户配置文件管理和注销链接。
      • 用户未经过身份验证时的注册和登录链接。
    • AuthorizeRoute.js:一个路由组件,要求用户先经过身份验证,然后才能呈现 Component 参数中指示的组件。
  • AuthorizeService 的一个导出 authService 实例,处理身份验证过程的较低级别详细信息,并向应用的 rest 公开有关已经过身份验证的用户的信息以供使用。

现在,你已了解了解决方案的主要组件,可以更深入地了解应用的各个方案。

要求对新 API 进行授权

默认情况下,系统配置为可轻松地要求为新 API 进行授权。 为此,请创建新控制器,并将 [Authorize] 属性添加到控制器类或控制器中的任何操作。

自定义 API 身份验证处理程序

若要自定义 API 的 JWT 处理程序的配置,请配置其 JwtBearerOptions 实例:

builder.Services.AddAuthentication()
    .AddIdentityServerJwt();

builder.Services.Configure<JwtBearerOptions>(
    IdentityServerJwtConstants.IdentityServerJwtBearerScheme,
    options =>
    {
        ...
    });

API 的 JWT 处理程序会引发相应事件,通过这些事件可使用 JwtBearerEvents 控制身份验证过程。 为了为 API 授权提供支持,AddIdentityServerJwt 会注册其自己的事件处理程序。

若要自定义事件的处理,请根据需要使用其他逻辑来包装现有事件处理程序。 例如:

builder.Services.Configure<JwtBearerOptions>(
    IdentityServerJwtConstants.IdentityServerJwtBearerScheme,
    options =>
    {
        var onTokenValidated = options.Events.OnTokenValidated;       

        options.Events.OnTokenValidated = async context =>
        {
            await onTokenValidated(context);
            ...
        }
    });

在上面的代码中,OnTokenValidated 事件处理程序替换为自定义实现。 此实现:

  1. 调用 API 授权支持提供的原始实现。
  2. 运行自己的自定义逻辑。

保护客户端路由 (Angular)

保护客户端路由的方法是,在配置路由时,将授权保障添加到要运行的保障列表中。 作为示例,可以查看如何在主应用 Angular 模块中配置 fetch-data 路由:

RouterModule.forRoot([
  // ...
  { path: 'fetch-data', component: FetchDataComponent, canActivate: [AuthorizeGuard] },
])

必须指出的是,保护路由不会保护实际终结点(这仍然需要向它应用 [Authorize] 属性),而是只会阻止用户在未经过身份验证时导航到给定客户端路由。

对 API 请求进行身份验证 (Angular)

通过使用应用定义的 HTTP 客户端侦听器自动对与应用一起托管的 API 的请求进行身份验证。

保护客户端路由 (React)

使用 AuthorizeRoute 组件(而不是普通 Route 组件)保护客户端路由。 例如,请注意如何在 App 组件中配置 fetch-data 路由:

<AuthorizeRoute path='/fetch-data' component={FetchData} />

保护路由:

  • 不保护实际终结点(这仍然需要向它应用 [Authorize] 属性)。
  • 仅阻止用户在未经过身份验证时导航到给定客户端路由。

对 API 请求进行身份验证 (React)

使用 React 对请求进行身份验证的方式是首先从 AuthorizeService 导入 authService 实例。 访问令牌从 authService 进行检索并附加到请求,如下所示。 在 React 组件中,此工作通常在 componentDidMount 生命周期方法中完成,或是作为某种用户交互的结果。

authService 导入组件

import authService from './api-authorization/AuthorizeService'

检索访问令牌并附加到响应

async populateWeatherData() {
  const token = await authService.getAccessToken();
  const response = await fetch('api/SampleData/WeatherForecasts', {
    headers: !token ? {} : { 'Authorization': `Bearer ${token}` }
  });
  const data = await response.json();
  this.setState({ forecasts: data, loading: false });
}

部署到生产

若要将应用部署到生产,需要预配以下资源:

  • 用于存储 Identity 用户帐户和 IdentityServer 授权的数据库。
  • 用于对令牌进行签名的生产证书。
    • 此证书没有特定要求;它可以是自签名证书,或是通过 CA 颁发机构预配的证书。
    • 它可以通过标准工具(如 PowerShell 或 OpenSSL)来生成。
    • 它可以安装到目标计算机上的证书存储中,也可以作为具有强密码 .pfx 文件进行部署。

示例:部署到非 Azure Web 托管提供者

在 Web 托管面板中,创建或加载证书。 然后在应用的 appsettings.json 文件中,修改 IdentityServer 部分以包含密钥详细信息。 例如:

"IdentityServer": {
  "Key": {
    "Type": "Store",
    "StoreName": "WebHosting",
    "StoreLocation": "CurrentUser",
    "Name": "CN=MyApplication"
  }
}

在上面的示例中:

  • StoreName 表示存储证书的证书存储的名称。 在此例中,它指向 Web 托管存储。
  • StoreLocation 表示从中加载证书的位置(在此例中为 CurrentUser)。
  • Name 对应于证书的可分辨主题。

示例:部署到 Azure 应用服务

此部分介绍如何使用存储在证书存储中的证书将应用部署到 Azure 应用服务。 若要修改应用以从证书存储加载证书,在以后的步骤中在 Azure 门户中配置应用时,需要标准层服务计划或更好的计划。

在应用的 appsettings.json 文件中,修改 IdentityServer 部分以包含密钥详细信息:

"IdentityServer": {
  "Key": {
    "Type": "Store",
    "StoreName": "My",
    "StoreLocation": "CurrentUser",
    "Name": "CN=MyApplication"
  }
}
  • 存储名称表示存储证书的证书存储的名称。 在此例中,它指向个人用户存储。
  • 存储位置表示从中加载证书的位置(CurrentUserLocalMachine)。
  • 证书中的名称属性对应于证书的可分辨主题。

若要部署到 Azure 应用服务,请按照将应用部署到 Azure 中的步骤进行操作,其中说明了如何创建所需 Azure 资源并将应用部署到生产。

按照以上说明进行操作后,应用会部署到 Azure,但尚无法正常运行。 应用使用的证书必须在 Azure 门户进行配置。 找到证书的指纹,并按照加载证书中所述的步骤进行操作。

虽然这些步骤涉及 SSL,但 Azure 门户中有一个“私有证书”部分,可在其中上传预配的证书以用于应用。

在 Azure 门户中配置应用和应用的设置之后,在门户中重启应用。

其他配置选项

对 API 授权的支持基于 IdentityServer,其中具有一组可简化 SPA 体验的约定、默认值和增强功能。 毋庸讳言,如果 ASP.NET Core 集成未涵盖你的方案,则可在后台使用 IdentityServer 的完整功能。 ASP.NET Core 支持侧重于“第一方”应用,其中所有应用都由我们的组织进行创建和部署。 因此,没有为同意或联合等内容提供支持。 对于这些方案,请使用 IdentityServer 并遵循其文档。

应用程序配置文件

应用程序配置文件是应用的预定义配置,可进一步定义其参数。 目前支持以下配置文件:

  • IdentityServerSPA:将与 IdentityServer 一起托管的 SPA 表示为单个单元。
    • redirect_uri 默认为 /authentication/login-callback
    • post_logout_redirect_uri 默认为 /authentication/logout-callback
    • 范围集包括 openidprofile 以及为应用中的 API 定义的每个范围。
    • 允许的 OIDC 响应类型集是 id_token token 或单独每个类型(id_tokentoken)。
    • 允许的响应模式为 fragment
  • SPA:表示未与 IdentityServer 一起托管的 SPA。
    • 范围集包括 openidprofile 以及为应用中的 API 定义的每个范围。
    • 允许的 OIDC 响应类型集是 id_token token 或单独每个类型(id_tokentoken)。
    • 允许的响应模式为 fragment
  • IdentityServerJwt:表示与 IdentityServer 一起托管的 API。
    • 应用配置为具有默认为应用名称的单个范围。
  • API:表示未与 IdentityServer 一起托管的 API。
    • 应用配置为具有默认为应用名称的单个范围。

通过 AppSettings 进行的配置

可通过配置系统配置应用,具体方法是将它们添加到 ClientsResources 的列表。

配置每个客户端的 redirect_uripost_logout_redirect_uri 属性,如下面的示例所示:

"IdentityServer": {
  "Clients": {
    "MySPA": {
      "Profile": "SPA",
      "RedirectUri": "https://www.example.com/authentication/login-callback",
      "LogoutUri": "https://www.example.com/authentication/logout-callback"
    }
  }
}

配置资源时,可以配置资源的范围,如下所示:

"IdentityServer": {
  "Resources": {
    "MyExternalApi": {
      "Profile": "API",
      "Scopes": "a b c"
    }
  }
}

通过代码进行的配置

还可使用执行操作来配置选项的 AddApiAuthorization 的重载,通过代码配置客户端和资源。

AddApiAuthorization<ApplicationUser, ApplicationDbContext>(options =>
{
    options.Clients.AddSPA(
        "My SPA", spa =>
        spa.WithRedirectUri("http://www.example.com/authentication/login-callback")
           .WithLogoutRedirectUri(
               "http://www.example.com/authentication/logout-callback"));

    options.ApiResources.AddApiResource("MyExternalApi", resource =>
        resource.WithScopes("a", "b", "c"));
});

其他资源

ASP.NET Core 3.1 及更高版本模板使用 API 授权支持在单页应用 (SPA) 中提供身份验证。 用于验证和存储用户身份信息的 ASP.NET Core Identity 与用于实现 Open ID Connect 的 IdentityServer 结合使用。

一个身份验证参数已添加到 Angular 和 React 项目模板,该参数类似于 Web 应用(模型-视图-控制器)(MVC) 和 Web 应用 (Razor Pages) 项目模板中的身份验证参数。 允许的参数值为 None 和 Individual。 React.js 和 Redux 项目模板目前不支持该身份验证参数。

使用 API 授权支持创建应用

用户身份验证和授权可以与 Angular 和 React SPA 结合使用。 打开命令行界面,然后运行以下命令:

Angular:

dotnet new angular -o <output_directory_name> 

React:

dotnet new react -o <output_directory_name> -au Individual

上面的命令创建一个 ASP.NET Core 应用,它具有包含 SPA 的 ClientApp 目录。

应用的 ASP.NET Core 组件的一般说明

以下部分介绍了在包括身份验证支持的情况下对项目添加的内容:

Startup

下面的代码示例依赖于 Microsoft.AspNetCore.ApiAuthorization.IdentityServer NuGet 包。 该示例使用 AddApiAuthorizationAddIdentityServerJwt 扩展方法配置 API 身份验证和授权。 使用带身份验证的 React 或 Angular SPA 项目模板的项目包含对此包的引用。

Startup 类包含以下添加项:

  • Startup.ConfigureServices 方法中:

    • 包含默认 UI 的 Identity:

      services.AddDbContext<ApplicationDbContext>(options =>
          options.UseSqlite(Configuration.GetConnectionString("DefaultConnection")));
      
      services.AddDefaultIdentity<ApplicationUser>()
          .AddEntityFrameworkStores<ApplicationDbContext>();
      
    • 包含附加 AddApiAuthorization 帮助器方法的 IdentityServer,该方法在 IdentityServer 之上设置默认 ASP.NET Core 约定:

      services.AddIdentityServer()
          .AddApiAuthorization<ApplicationUser, ApplicationDbContext>();
      
    • 包含附加 AddIdentityServerJwt 帮助器方法的身份验证,该方法将应用配置为验证由 IdentityServer 生成的 JWT 令牌:

      services.AddAuthentication()
          .AddIdentityServerJwt();
      
  • Startup.Configure 方法中:

    • 负责验证请求凭据并在请求上下文中设置用户的身份验证中间件:

      app.UseAuthentication();
      
    • 公开 OpenID Connect 终结点的 IdentityServer 中间件:

      app.UseIdentityServer();
      

警告

本文介绍连接字符串的使用。 使用本地数据库时,用户无需进行身份验证,但在生产环境中,连接字符串有时包括进行身份验证的密码。 资源所有者密码凭据(ROPC)是在生产数据库中应避免的安全风险。 生产应用应使用可用的最安全的身份验证流。 有关部署到测试或生产环境的应用的身份验证的详细信息,请参阅 安全身份验证流

Linux 上的 Azure 应用服务

对于 Linux 上的 Azure 应用服务部署,请在 Startup.ConfigureServices 中显式指定颁发者:

services.Configure<JwtBearerOptions>(
    IdentityServerJwtConstants.IdentityServerJwtBearerScheme, 
    options =>
    {
        options.Authority = "{AUTHORITY}";
    });

在上面的代码中,{AUTHORITY} 占位符是在进行 OpenID Connect 调用时要使用的 Authority

示例:

options.Authority = "https://contoso-service.azurewebsites.net";

AddApiAuthorization

此帮助程序方法将 IdentityServer 配置为使用支持的配置。 IdentityServer 是一个功能强大且可扩展的框架,用于处理应用安全问题。 同时,该框架会公开大多数情况下不必要的复杂性。 因此,提供了一组约定和配置选项作为良好的起点。 一旦身份验证需要更改,仍可使用 IdentityServer 的完整功能自定义身份验证以满足你的需求。

AddIdentityServerJwt

此帮助程序方法将应用的策略方案配置为默认身份验证处理程序。 该策略配置为允许 Identity 处理路由到 Identity URL 空间“/Identity”中任何子路径的所有请求。 JwtBearerHandler 处理所有其他请求。 此外,此方法向 IdentityServer 注册一个默认范围为 <<ApplicationName>>API<<ApplicationName>>API API 资源,并将 JWT 持有者令牌中间件配置为验证 IdentityServer 为应用颁发的令牌。

WeatherForecastController

在文件中,请注意应用于类的 [Authorize] 属性,该属性指示用户需要基于默认策略进行授权才能访问资源。 默认授权策略恰好配置为使用默认身份验证方案,而该方案由 AddIdentityServerJwt 设置为上面提到的策略方案,从而使此类帮助程序方法配置的 JwtBearerHandler 成为对应用进行的请求的默认处理程序。

ApplicationDbContext

在文件中,请注意,在 Identity 中使用了相同的 DbContext,只不过它会扩展 ApiAuthorizationDbContext(一个来自 IdentityDbContext 的派生程度更高的类)以包括 IdentityServer 的架构。

要获取对数据库架构的完全控制,请从其中一个可用的 IdentityDbContext 类继承,并通过对 OnModelCreating 方法调用 builder.ConfigurePersistedGrantContext(_operationalStoreOptions.Value) 来配置上下文以包括 Identity 架构。

OidcConfigurationController

在文件中,请注意为处理客户端需要使用的 OIDC 参数而预配的终结点。

appsettings.json

在项目根目录的 appsettings.json 文件中,有一个新的 IdentityServer 部分,描述已配置的客户端列表。 下例中存在一个客户端。 客户端名称对应于应用名称,并通过约定映射到 OAuth ClientId 参数。 配置文件指示正在配置的应用类型。 它在内部用于促进简化服务器配置过程的约定。 有多个配置文件可用(在应用程序配置文件部分中进行了说明)。

"IdentityServer": {
  "Clients": {
    "angularindividualpreview3final": {
      "Profile": "IdentityServerSPA"
    }
  }
}

appsettings.Development.json

在项目根目录的 appsettings.Development.json 文件中,有一个 IdentityServer 部分,描述用于对令牌进行签名的密钥。 部署到生产时,密钥需要与应用一起进行预配和部署(在部署到生产部分中进行了说明)。

"IdentityServer": {
  "Key": {
    "Type": "Development"
  }
}

Angular 应用的一般说明

Angular 模板中的身份验证和 API 授权支持位于其自己的 Angular 模块中(在 ClientApp/src/api-authorization 目录中)。 该模块由以下元素组成:

  • 3 个组件:
    • login.component.ts:处理应用的登录流。
    • logout.component.ts:处理应用的注销流。
    • login-menu.component.ts:显示以下链接集之一的小组件:
      • 用户已经过身份验证时的用户配置文件管理和注销链接。
      • 用户未经过身份验证时的注册和登录链接。
  • 一个路由防护 AuthorizeGuard,可以添加到路由,要求用户先经过身份验证,然后才能访问路由。
  • 一个 HTTP 侦听器 AuthorizeInterceptor,在用户经过身份验证时将访问令牌附加到针对 API 的传出 HTTP 请求。
  • 一个服务 AuthorizeService,处理身份验证过程的较低级别详细信息,并向应用的 rest 公开有关已经过身份验证的用户的信息以供使用。
  • 一个 Angular 模块,定义与应用的身份验证部分关联的路由。 它公开登录菜单组件、侦听器、保护措施和服务,以便从应用的 rest 进行使用。

React 应用的一般说明

React 模板中的身份验证和 API 授权支持位于 ClientApp/src/components/api-authorization 目录中。 它由以下元素组成:

  • 4 个组件:
    • Login.js:处理应用的登录流。
    • Logout.js:处理应用的注销流。
    • LoginMenu.js:显示以下链接集之一的小组件:
      • 用户已经过身份验证时的用户配置文件管理和注销链接。
      • 用户未经过身份验证时的注册和登录链接。
    • AuthorizeRoute.js:一个路由组件,要求用户先经过身份验证,然后才能呈现 Component 参数中指示的组件。
  • AuthorizeService 的一个导出 authService 实例,处理身份验证过程的较低级别详细信息,并向应用的 rest 公开有关已经过身份验证的用户的信息以供使用。

现在,你已了解了解决方案的主要组件,可以更深入地了解应用的各个方案。

要求对新 API 进行授权

默认情况下,系统配置为可轻松地要求为新 API 进行授权。 为此,请创建新控制器,并将 [Authorize] 属性添加到控制器类或控制器中的任何操作。

自定义 API 身份验证处理程序

若要自定义 API 的 JWT 处理程序的配置,请配置其 JwtBearerOptions 实例:

services.AddAuthentication()
    .AddIdentityServerJwt();

services.Configure<JwtBearerOptions>(
    IdentityServerJwtConstants.IdentityServerJwtBearerScheme,
    options =>
    {
        ...
    });

API 的 JWT 处理程序会引发相应事件,通过这些事件可使用 JwtBearerEvents 控制身份验证过程。 为了为 API 授权提供支持,AddIdentityServerJwt 会注册其自己的事件处理程序。

若要自定义事件的处理,请根据需要使用其他逻辑来包装现有事件处理程序。 例如:

services.Configure<JwtBearerOptions>(
    IdentityServerJwtConstants.IdentityServerJwtBearerScheme,
    options =>
    {
        var onTokenValidated = options.Events.OnTokenValidated;       

        options.Events.OnTokenValidated = async context =>
        {
            await onTokenValidated(context);
            ...
        }
    });

在上面的代码中,OnTokenValidated 事件处理程序替换为自定义实现。 此实现:

  1. 调用 API 授权支持提供的原始实现。
  2. 运行自己的自定义逻辑。

保护客户端路由 (Angular)

保护客户端路由的方法是,在配置路由时,将授权保障添加到要运行的保障列表中。 作为示例,可以查看如何在主应用 Angular 模块中配置 fetch-data 路由:

RouterModule.forRoot([
  // ...
  { path: 'fetch-data', component: FetchDataComponent, canActivate: [AuthorizeGuard] },
])

必须指出的是,保护路由不会保护实际终结点(这仍然需要向它应用 [Authorize] 属性),而是只会阻止用户在未经过身份验证时导航到给定客户端路由。

对 API 请求进行身份验证 (Angular)

通过使用应用定义的 HTTP 客户端侦听器自动对与应用一起托管的 API 的请求进行身份验证。

保护客户端路由 (React)

使用 AuthorizeRoute 组件(而不是普通 Route 组件)保护客户端路由。 例如,请注意如何在 App 组件中配置 fetch-data 路由:

<AuthorizeRoute path='/fetch-data' component={FetchData} />

保护路由:

  • 不保护实际终结点(这仍然需要向它应用 [Authorize] 属性)。
  • 仅阻止用户在未经过身份验证时导航到给定客户端路由。

对 API 请求进行身份验证 (React)

使用 React 对请求进行身份验证的方式是首先从 AuthorizeService 导入 authService 实例。 访问令牌从 authService 进行检索并附加到请求,如下所示。 在 React 组件中,此工作通常在 componentDidMount 生命周期方法中完成,或是作为某种用户交互的结果。

authService 导入组件

import authService from './api-authorization/AuthorizeService'

检索访问令牌并附加到响应

async populateWeatherData() {
  const token = await authService.getAccessToken();
  const response = await fetch('api/SampleData/WeatherForecasts', {
    headers: !token ? {} : { 'Authorization': `Bearer ${token}` }
  });
  const data = await response.json();
  this.setState({ forecasts: data, loading: false });
}

部署到生产

若要将应用部署到生产,需要预配以下资源:

  • 用于存储 Identity 用户帐户和 IdentityServer 授权的数据库。
  • 用于对令牌进行签名的生产证书。
    • 此证书没有特定要求;它可以是自签名证书,或是通过 CA 颁发机构预配的证书。
    • 它可以通过标准工具(如 PowerShell 或 OpenSSL)来生成。
    • 它可以安装到目标计算机上的证书存储中,也可以作为具有强密码 .pfx 文件进行部署。

示例:部署到非 Azure Web 托管提供者

在 Web 托管面板中,创建或加载证书。 然后在应用的 appsettings.json 文件中,修改 IdentityServer 部分以包含密钥详细信息。 例如:

"IdentityServer": {
  "Key": {
    "Type": "Store",
    "StoreName": "WebHosting",
    "StoreLocation": "CurrentUser",
    "Name": "CN=MyApplication"
  }
}

在上面的示例中:

  • StoreName 表示存储证书的证书存储的名称。 在此例中,它指向 Web 托管存储。
  • StoreLocation 表示从中加载证书的位置(在此例中为 CurrentUser)。
  • Name 对应于证书的可分辨主题。

示例:部署到 Azure 应用服务

此部分介绍如何使用存储在证书存储中的证书将应用部署到 Azure 应用服务。 若要修改应用以从证书存储加载证书,在以后的步骤中在 Azure 门户中配置应用时,需要标准层服务计划或更好的计划。

在应用的 appsettings.json 文件中,修改 IdentityServer 部分以包含密钥详细信息:

"IdentityServer": {
  "Key": {
    "Type": "Store",
    "StoreName": "My",
    "StoreLocation": "CurrentUser",
    "Name": "CN=MyApplication"
  }
}
  • 存储名称表示存储证书的证书存储的名称。 在此例中,它指向个人用户存储。
  • 存储位置表示从中加载证书的位置(CurrentUserLocalMachine)。
  • 证书中的名称属性对应于证书的可分辨主题。

若要部署到 Azure 应用服务,请按照将应用部署到 Azure 中的步骤进行操作,其中说明了如何创建所需 Azure 资源并将应用部署到生产。

按照以上说明进行操作后,应用会部署到 Azure,但尚无法正常运行。 应用使用的证书必须在 Azure 门户进行配置。 找到证书的指纹,并按照加载证书中所述的步骤进行操作。

虽然这些步骤涉及 SSL,但 Azure 门户中有一个“私有证书”部分,可在其中上传预配的证书以用于应用。

在 Azure 门户中配置应用和应用的设置之后,在门户中重启应用。

其他配置选项

对 API 授权的支持基于 IdentityServer,其中具有一组可简化 SPA 体验的约定、默认值和增强功能。 毋庸讳言,如果 ASP.NET Core 集成未涵盖你的方案,则可在后台使用 IdentityServer 的完整功能。 ASP.NET Core 支持侧重于“第一方”应用,其中所有应用都由我们的组织进行创建和部署。 因此,没有为同意或联合等内容提供支持。 对于这些方案,请使用 IdentityServer 并遵循其文档。

应用程序配置文件

应用程序配置文件是应用的预定义配置,可进一步定义其参数。 目前支持以下配置文件:

  • IdentityServerSPA:将与 IdentityServer 一起托管的 SPA 表示为单个单元。
    • redirect_uri 默认为 /authentication/login-callback
    • post_logout_redirect_uri 默认为 /authentication/logout-callback
    • 范围集包括 openidprofile 以及为应用中的 API 定义的每个范围。
    • 允许的 OIDC 响应类型集是 id_token token 或单独每个类型(id_tokentoken)。
    • 允许的响应模式为 fragment
  • SPA:表示未与 IdentityServer 一起托管的 SPA。
    • 范围集包括 openidprofile 以及为应用中的 API 定义的每个范围。
    • 允许的 OIDC 响应类型集是 id_token token 或单独每个类型(id_tokentoken)。
    • 允许的响应模式为 fragment
  • IdentityServerJwt:表示与 IdentityServer 一起托管的 API。
    • 应用配置为具有默认为应用名称的单个范围。
  • API:表示未与 IdentityServer 一起托管的 API。
    • 应用配置为具有默认为应用名称的单个范围。

通过 AppSettings 进行的配置

可通过配置系统配置应用,具体方法是将它们添加到 ClientsResources 的列表。

配置每个客户端的 redirect_uripost_logout_redirect_uri 属性,如下面的示例所示:

"IdentityServer": {
  "Clients": {
    "MySPA": {
      "Profile": "SPA",
      "RedirectUri": "https://www.example.com/authentication/login-callback",
      "LogoutUri": "https://www.example.com/authentication/logout-callback"
    }
  }
}

配置资源时,可以配置资源的范围,如下所示:

"IdentityServer": {
  "Resources": {
    "MyExternalApi": {
      "Profile": "API",
      "Scopes": "a b c"
    }
  }
}

通过代码进行的配置

还可使用执行操作来配置选项的 AddApiAuthorization 的重载,通过代码配置客户端和资源。

AddApiAuthorization<ApplicationUser, ApplicationDbContext>(options =>
{
    options.Clients.AddSPA(
        "My SPA", spa =>
        spa.WithRedirectUri("http://www.example.com/authentication/login-callback")
           .WithLogoutRedirectUri(
               "http://www.example.com/authentication/logout-callback"));

    options.ApiResources.AddApiResource("MyExternalApi", resource =>
        resource.WithScopes("a", "b", "c"));
});

其他资源