ASP.NET Core 中的密钥存储提供程序

数据保护系统默认采用发现机制来确定加密密钥的保存位置。 开发人员可以替代默认发现机制并手动指定该位置。

警告

如果指定显式密钥持久性位置,数据保护系统会取消注册默认密钥静态加密机制,因此不再对密钥进行静态加密。 建议另外为生产部署指定显式密钥加密机制

文件系统

若要配置基于文件系统的密钥存储库,请调用 PersistKeysToFileSystem 配置例程,如下所示。 提供一个 DirectoryInfo,指向用于存储密钥的存储库。

public void ConfigureServices(IServiceCollection services)
{
    services.AddDataProtection()
        .PersistKeysToFileSystem(new DirectoryInfo(@"c:\temp-keys\"));
}

DirectoryInfo 可以指向本地计算机上的目录,也可以指向网络共享上的文件夹。 如果指向本地计算机上的目录(并且场景是只有本地计算机上的应用才需要获权使用此存储库),请考虑使用 Windows DPAPI(在 Windows 上)对密钥进行静态加密。 否则,请考虑使用 X.509 证书 来加密静态密钥。

Azure 存储

Azure.Extensions.AspNetCore.DataProtection.Blobs NuGet 包提供用于在 Azure Blob 存储中存储数据保护密钥的 API。 可以在 Web 应用的多个实例之间共享密钥。 应用可以跨多个服务器共享身份验证 Cookie 或 CSRF 保护。

注意

有关将包添加到 .NET 应用的指南,请参阅包使用工作流(NuGet 文档)中“安装和管理包”下的文章。 在 NuGet.org 中确认正确的包版本。

若要使用开发人员凭据在本地与 Azure Key Vault 交互,请在 Visual Studio 中登录到存储帐户,或使用 Azure CLI 登录。 如果尚未安装 Azure CLI,请参阅 如何安装 Azure CLI。 不使用 Visual Studio 时,可以在 Visual Studio 的开发人员 PowerShell 面板中或命令行界面中执行以下命令:

az login

有关详细信息,请参阅 使用开发人员工具登录到 Azure

配置 Azure Blob 存储以维护数据保护密钥:

  • 创建 Azure 存储帐户。

  • 创建用于保存数据保护密钥文件的容器。

  • 建议使用 Azure 托管的 Identity 和基于角色的访问控制 (RBAC) 来访问密钥存储的 Blob。 无需创建密钥文件并将其上传到存储帐户的容器。 框架会为你创建文件。 若要检查密钥文件的内容,请在门户中键行末尾使用上下文菜单的 “视图/编辑 ”命令。

注意

如果计划使用具有共享访问签名(SAS)的 Blob URI 而不是托管 IdentityURI,请使用文本编辑器在本地计算机上创建 XML 密钥文件:

<?xml version="1.0" encoding="utf-8"?>
<repository>
</repository>

将密钥文件上传到存储帐户的容器。 在门户的键行末尾使用上下文菜单的“查看/编辑”命令,确认 Blob 包含上述内容。 通过手动创建文件,可以从门户中获取包含 SAS 的 Blob URI,以便在后续步骤中配置应用。

  • 使用Identity角色创建 Azure 托管 Identity(或向计划使用的现有托管 添加角色)。 将托管Identity分配给托管部署的 Azure 应用服务:设置>Identity>用户分配>添加

    注意

    如果你还计划通过授权用户,使用 Azure CLI 或 Visual Studio 的 Azure 服务身份验证,在本地运行应用程序以进行 Blob 访问,请在访问控制 (IAM) 中以存储 Blob 数据参与者角色添加开发人员 Azure 用户帐户。 若要通过 Visual Studio 使用 Azure CLI,请从开发人员 PowerShell 面板执行 az login 该命令,并按照提示向租户进行身份验证。

若要配置 Azure Blob 存储提供程序,请在应用中调用PersistKeysToAzureBlobStorage 重载函数之一。 以下示例使用接受 Blob URI 和令牌凭据 (TokenCredential) 的重载,并依赖于 Azure 托管 Identity 进行基于角色的访问控制 (RBAC)。

其他重载基于:

  • Blob URI 和存储共享密钥凭据 (StorageSharedKeyCredential)。
  • 带有共享访问签名 (SAS) 的 Blob URI。
  • 连接字符串、容器名称和 Blob 名称。
  • Blob 客户端 (BlobClient)。

有关 Azure SDK 的 API 和身份验证的详细信息,请参阅 使用 Azure 库向 Azure Identity 服务验证 .NET 应用。 有关日志记录指南,请参阅 使用适用于 .NET 的 Azure SDK 进行日志记录:在没有客户端注册的情况下进行日志记录。 对于使用依赖注入的应用,应用可以调用 AddAzureClientsCore,对 true 传递 enableLogForwarding,以创建和连接日志记录基础结构。

在注册了服务的 Program 文件中:

TokenCredential? credential;

if (builder.Environment.IsProduction())
{
    credential = new ManagedIdentityCredential("{MANAGED IDENTITY CLIENT ID}");
}
else
{
    // Local development and testing only
    DefaultAzureCredentialOptions options = new()
    {
        // Specify the tenant ID to use the dev credentials when running the app locally
        // in Visual Studio.
        VisualStudioTenantId = "{TENANT ID}",
        SharedTokenCacheTenantId = "{TENANT ID}"
    };

    credential = new DefaultAzureCredential(options);
}

builder.Services.AddDataProtection()
    .SetApplicationName("{APPLICATION NAME}")
    .PersistKeysToAzureBlobStorage(new Uri("{BLOB URI}"), credential);

{MANAGED IDENTITY CLIENT ID}:Azure 托管 Identity 客户端 ID (GUID)。

{TENANT ID}:租户 ID。

{APPLICATION NAME}SetApplicationName 在数据保护系统中设置此应用的唯一名称。 该值应在应用的各个部署之间保持一致。

{BLOB URI}:密钥文件的完整 URI。 创建密钥文件时,Azure 存储会生成 URI。 请勿使用 SAS。

备用共享访问签名 (SAS) 方法:作为使用托管 Identity 访问 Azure Blob 存储中的密钥 Blob 的替代方法,可以调用接受具有 SAS 令牌的 Blob URI 的 PersistKeysToAzureBlobStorage 重载:

builder.Services.AddDataProtection()
    .SetApplicationName("{APPLICATION NAME}")
    .PersistKeysToAzureBlobStorage(new Uri("{BLOB URI WITH SAS}"));

Startup.ConfigureServices中:

TokenCredential? credential;

if (_env.IsProduction())
{
    credential = new ManagedIdentityCredential("{MANAGED IDENTITY CLIENT ID}");
}
else
{
    // Local development and testing only
    DefaultAzureCredentialOptions options = new()
    {
        // Specify the tenant ID to use the dev credentials when running the app locally
        // in Visual Studio.
        VisualStudioTenantId = "{TENANT ID}",
        SharedTokenCacheTenantId = "{TENANT ID}"
    };

    credential = new DefaultAzureCredential(options);
}

services.AddDataProtection()
    .SetApplicationName("{APPLICATION NAME}")
    .PersistKeysToAzureBlobStorage(new Uri("{BLOB URI}"), credential);

{MANAGED IDENTITY CLIENT ID}:Azure 托管 Identity 客户端 ID (GUID)。

{TENANT ID}:租户 ID。

{APPLICATION NAME}SetApplicationName 在数据保护系统中设置此应用的唯一名称。 该值应在应用的各个部署之间保持一致。

{BLOB URI}:密钥文件的完整 URI。 创建密钥文件时,Azure 存储会生成 URI。 请勿使用 SAS。

示例:

https://contoso.blob.core.windows.net/data-protection/keys.xml

备用共享访问签名 (SAS) 方法:作为使用托管 Identity 访问 Azure Blob 存储中的密钥 Blob 的替代方法,可以调用接受具有 SAS 令牌的 Blob URI 的 PersistKeysToAzureBlobStorage 重载:

services.AddDataProtection()
    .SetApplicationName("{APPLICATION NAME}")
    .PersistKeysToAzureBlobStorage(new Uri("{BLOB URI WITH SAS}"));

{APPLICATION NAME}SetApplicationName 在数据保护系统中设置此应用的唯一名称。 该值应在应用的各个部署之间保持一致。

{BLOB URI WITH SAS}:密钥文件应作为查询字符串参数与 SAS 令牌一起存储的完整 URI。 当你请求用于上传的密钥文件的 SAS 时,Azure 存储会生成 URI。 在以下示例中,容器名称为 data-protection,存储帐户名称为 contoso。 密钥文件命名 keys.xml。 共享访问签名 (SAS) 查询字符串位于 URI({SHARED ACCESS SIGNATURE} 占位符)的末尾。

示例:

https://contoso.blob.core.windows.net/data-protection/keys.xml{SHARED ACCESS SIGNATURE}

如果 Web 应用作为 Azure 服务运行,则可以使用连接字符串向 Azure 存储 BlobContainerClient进行身份验证,如以下示例所示。

警告

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

如果容器不存在,可以选择调用 CreateIfNotExistsAsync 自动预配该容器。

在 Entra 或 Azure 门户的“访问密钥”部分下,或通过运行以下 Azure CLI 命令,找到存储帐户的连接字符串({CONNECTION STRING} 占位符):

az storage account show-connection-string --name <account_name> --resource-group <resource_group>

在注册了服务的 Program 文件中:

string connectionString = "{CONNECTION STRING}";
string containerName = "{CONTAINER NAME}";
string blobName = "keys.xml";
var container = new BlobContainerClient(connectionString, containerName);
await container.CreateIfNotExistsAsync();
BlobClient blobClient = container.GetBlobClient(blobName);

builder.Services.AddDataProtection().PersistKeysToAzureBlobStorage(blobClient);

Startup.ConfigureServices中:

string connectionString = "{CONNECTION STRING}";
string containerName = "{CONTAINER NAME}";
string blobName = "keys.xml";
var container = new BlobContainerClient(connectionString, containerName);
await container.CreateIfNotExistsAsync();
BlobClient blobClient = container.GetBlobClient(blobName);

services.AddDataProtection().PersistKeysToAzureBlobStorage(blobClient);

Redis

Microsoft.AspNetCore.DataProtection.StackExchangeRedis 包允许将数据保护密钥存储在 Redis 缓存中。 可以在 Web 应用的多个实例之间共享密钥。 应用可以跨多个服务器共享身份验证 Cookie 或 CSRF 保护。

Microsoft.AspNetCore.DataProtection.Redis 包允许将数据保护密钥存储在 Redis 缓存中。 可以在 Web 应用的多个实例之间共享密钥。 应用可以跨多个服务器共享身份验证 Cookie 或 CSRF 保护。

若要在 Redis 上进行配置,请调用 PersistKeysToStackExchangeRedis 重载函数之一:

public void ConfigureServices(IServiceCollection services)
{
    var redis = ConnectionMultiplexer.Connect("<URI>");
    services.AddDataProtection()
        .PersistKeysToStackExchangeRedis(redis, "DataProtection-Keys");
}

若要在 Redis 上进行配置,请调用 PersistKeysToRedis 重载函数之一:

public void ConfigureServices(IServiceCollection services)
{
    var redis = ConnectionMultiplexer.Connect("<URI>");
    services.AddDataProtection()
        .PersistKeysToRedis(redis, "DataProtection-Keys");
}

有关详情,请参阅以下主题:

注册表

仅适用于 Windows 部署。

有时,应用可能对文件系统没有写入访问权限。 以应用作为虚拟服务帐户(例如 w3wp.exe 的应用池标识)运行的情况为例。 在这些情况下,管理员可以预配可由服务帐户标识访问的注册表项。 调用 PersistKeysToRegistry 扩展方法,如下所示。 请提供一个“RegistryKey”标签,以指示加密密钥的存储位置。

public void ConfigureServices(IServiceCollection services)
{
    services.AddDataProtection()
        .PersistKeysToRegistry(Registry.CurrentUser.OpenSubKey(@"SOFTWARE\Sample\keys", true));
}

重要

建议使用 Windows DPAPI 来加密静态密钥。

Entity Framework Core

Microsoft.AspNetCore.DataProtection.EntityFrameworkCore 包提供一种使用 Entity Framework Core 将数据保护密钥存储到数据库的机制。 必须将 Microsoft.AspNetCore.DataProtection.EntityFrameworkCore NuGet 包添加到项目文件,它不是 Microsoft.AspNetCore.App 元包的一部分。

使用此包,可以在 Web 应用的多个实例之间共享密钥。

要配置 EF Core 提供程序,请调用 PersistKeysToDbContext 方法:

public void ConfigureServices(IServiceCollection services)
{
    services.Configure<CookiePolicyOptions>(options =>
    {
        options.CheckConsentNeeded = context => true;
        options.MinimumSameSitePolicy = SameSiteMode.None;
    });

    services.AddDbContext<ApplicationDbContext>(options =>
        options.UseSqlServer(
            Configuration.GetConnectionString("DefaultConnection")));

    // Add a DbContext to store your Database Keys
    services.AddDbContext<MyKeysContext>(options =>
        options.UseSqlServer(
            Configuration.GetConnectionString("MyKeysConnection")));

    // using Microsoft.AspNetCore.DataProtection;
    services.AddDataProtection()
        .PersistKeysToDbContext<MyKeysContext>();

    services.AddDefaultIdentity<IdentityUser>()
        .AddDefaultUI(UIFramework.Bootstrap4)
        .AddEntityFrameworkStores<ApplicationDbContext>();
    services.AddMvc().SetCompatibilityVersion(CompatibilityVersion.Version_2_2);
}

若要查看翻译为非英语语言的代码注释,请在 此 GitHub 讨论问题中告诉我们。

泛型参数 TContext 必须继承自 DbContext,并且必须实现 IDataProtectionKeyContext

using Microsoft.AspNetCore.DataProtection.EntityFrameworkCore;
using Microsoft.EntityFrameworkCore;
using WebApp1.Data;

namespace WebApp1
{
    class MyKeysContext : DbContext, IDataProtectionKeyContext
    {
        // A recommended constructor overload when using EF Core 
        // with dependency injection.
        public MyKeysContext(DbContextOptions<MyKeysContext> options) 
            : base(options) { }

        // This maps to the table that stores keys.
        public DbSet<DataProtectionKey> DataProtectionKeys { get; set; }
    }
}

创建 DataProtectionKeys 表。

在包管理器控制台 (PMC) 窗口中执行以下命令:

Add-Migration AddDataProtectionKeys -Context MyKeysContext
Update-Database -Context MyKeysContext

MyKeysContext 是上述代码示例中定义的 DbContext。 如果使用具有不同名称的 DbContext,请将 DbContext 名称替换为 MyKeysContext

DataProtectionKeys 类/实体采用下表所示的结构。

属性/字段 CLR 类型 SQL 类型
Id int int、PK、IDENTITY(1,1)、非 null
FriendlyName string nvarchar(MAX)、null
Xml string nvarchar(MAX)、null

自定义密钥存储库

如果内置机制不适用,开发人员可以通过提供自定义 IXmlRepository 来指定自己的密钥保存机制。