如何在多应用中使用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

