You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

如何在多应用中使用DataProtectionKeys?密钥未生成问题解决

问题描述

我运行了以下代码后,看到[DataProtectionKeys]表中已生成一个密钥:

services.AddDataProtection()
    .SetApplicationName(dataProtectionSettings.ApplicationName)
    .ProtectKeysWithCertificate(serviceCertificate)
    .UnprotectKeysWithAnyCertificate(serviceCertificate)
    .PersistKeysToDbContext<DataProtectionContext>();

我使用的数据库上下文如下(是否有遗漏?):

class DataProtectionContext : DbContext, IDataProtectionKeyContext
{
    /// <summary>
    /// A recommended constructor overload when using EF Core with dependency injection.
    /// </summary>
    /// <param name="options"></param>
    public DataProtectionContext(DbContextOptions<DataProtectionContext> options)
        : base(options) { }

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

但修改ApplicationName的值后,并未生成新密钥。请问原因是什么?如何调整才能让该数据库表支持多应用?


原因分析

ASP.NET Core Data Protection 默认以应用名称作为密钥隔离标识,但使用PersistKeysToDbContext时,默认的数据库存储实现不会自动按应用名称区分密钥。核心问题在于DataProtectionKey实体本身没有存储应用名称的字段,所有应用的密钥会存入同一张表,系统会尝试复用现有密钥,而非为新应用名称生成新密钥。你的DataProtectionContext配置本身没有遗漏,但缺少多应用场景下的密钥隔离逻辑。

解决方案

要让数据库表支持多应用,需扩展密钥实体并自定义存储逻辑,通过应用名称字段隔离不同应用的密钥,具体步骤如下:

1. 扩展DataProtectionKey实体

创建继承自DataProtectionKey的自定义实体,添加ApplicationName字段用于区分应用:

public class ApplicationDataProtectionKey : DataProtectionKey
{
    public string ApplicationName { get; set; }
}

2. 更新数据库上下文

修改DataProtectionContext,使用自定义密钥实体并配置映射规则:

class DataProtectionContext : DbContext, IDataProtectionKeyContext
{
    public DataProtectionContext(DbContextOptions<DataProtectionContext> options)
        : base(options) { }

    // 使用自定义密钥实体的DbSet
    public DbSet<ApplicationDataProtectionKey> DataProtectionKeys { get; set; }

    protected override void OnModelCreating(ModelBuilder modelBuilder)
    {
        base.OnModelCreating(modelBuilder);

        modelBuilder.Entity<ApplicationDataProtectionKey>(b =>
        {
            b.HasKey(k => k.Id);
            b.Property(k => k.FriendlyName).IsRequired();
            b.Property(k => k.Xml).IsRequired();
            b.Property(k => k.ApplicationName).IsRequired().HasMaxLength(256);
            // 添加唯一索引避免同一应用重复存储相同密钥
            b.HasIndex(k => new { k.ApplicationName, k.FriendlyName }).IsUnique();
        });
    }

    // 实现IDataProtectionKeyContext接口,返回自定义实体的查询
    IQueryable<DataProtectionKey> IDataProtectionKeyContext.DataProtectionKeys => DataProtectionKeys;
}

3. 自定义密钥存储提供程序

实现IXmlRepository,让系统按应用名称存储和读取密钥:

public class ApplicationXmlRepository : IXmlRepository
{
    private readonly DataProtectionContext _context;
    private readonly string _applicationName;

    public ApplicationXmlRepository(DataProtectionContext context, IOptions<DataProtectionSettings> settings)
    {
        _context = context;
        _applicationName = settings.Value.ApplicationName;
    }

    public IReadOnlyCollection<XElement> GetAllElements()
    {
        // 仅读取当前应用的密钥
        return _context.DataProtectionKeys
            .Where(k => k.ApplicationName == _applicationName)
            .Select(k => XElement.Parse(k.Xml))
            .ToList()
            .AsReadOnly();
    }

    public void StoreElement(XElement element, string friendlyName)
    {
        // 避免重复存储同一应用的相同密钥
        if (!_context.DataProtectionKeys.Any(k => 
            k.ApplicationName == _applicationName && k.FriendlyName == friendlyName))
        {
            _context.DataProtectionKeys.Add(new ApplicationDataProtectionKey
            {
                ApplicationName = _applicationName,
                FriendlyName = friendlyName,
                Xml = element.ToString(SaveOptions.DisableFormatting)
            });
            _context.SaveChanges();
        }
    }
}

4. 替换默认存储实现

在服务配置中,移除PersistKeysToDbContext,改用自定义的IXmlRepository:

services.AddDataProtection()
    .SetApplicationName(dataProtectionSettings.ApplicationName)
    .ProtectKeysWithCertificate(serviceCertificate)
    .UnprotectKeysWithAnyCertificate(serviceCertificate)
    .AddXmlRepository(sp => 
        new ApplicationXmlRepository(
            sp.GetRequiredService<DataProtectionContext>(),
            sp.GetRequiredService<IOptions<DataProtectionSettings>>()));

5. 更新数据库结构

运行EF Core迁移命令,添加ApplicationName字段:

dotnet ef migrations add AddApplicationNameToDataProtectionKeys
dotnet ef database update

验证

修改ApplicationName后重启应用,系统会为新的应用名称生成独立密钥,不同应用的密钥通过ApplicationName字段隔离,互不干扰。

内容的提问来源于stack exchange,提问作者Yovav

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.08.11 16:50:29