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

基于特性的ABP实体属性加解密通用实现方案求助

实体属性加解密的通用读取方案(关联实体场景)

问题描述

通过特性实现实体属性加解密,目前已在应用服务层处理DTO的加解密,也通过DbContext的SavingChanges事件实现了写入时的加密逻辑。但遇到关联实体场景(如Domain关联WindowsUser)时,需要在所有关联服务中重复编写解密逻辑,不想通过反射遍历所有实体子类来检查特性,希望找到读取时自动解密(含关联实体)的通用方案,比如利用实体的materialise事件处理。

示例实体代码:

public class WindowsUser : Entity, IMayHaveTenant
{
   [Required] [StringLength(260)] 
   public string Username { get; set; }

   [Encrypt]
   [Required]
   public string Password { get; set; }
}
public class Domain : Entity, IMayHaveTenant
{
  [Required]
  [StringLength(260)]
  public string DomainName { get; set; }

  public ICollection<WindowsUser> WindowsUsers { get; set; }

  public int? TenantId { get; set; }
}

已实现的DbContext初始化:

public YourDbContext(DbContextOptions<YourDbContext> options)
: base(options)
{
   this.SavingChanges += DbContext_SavingChanges;
}

可行方案

方案1:利用EF Core的Materialized事件(EF Core 5+)

订阅实体的Materialized事件,当实体从数据库加载完成后自动解密带[Encrypt]特性的属性,关联实体加载时也会触发该事件,无需在服务层额外处理。

修改DbContext实现:

public class YourDbContext : DbContext
{
    public YourDbContext(DbContextOptions<YourDbContext> options) : base(options)
    {
        this.SavingChanges += DbContext_SavingChanges;
        
        // 为所有实体类型注册Materialized事件
        foreach (var entityType in Model.GetEntityTypes())
        {
            var materializerSource = entityType.GetService<IEntityMaterializerSource>();
            materializerSource.Materialized += (_, args) => DecryptEntity(args.Entity);
        }
    }

    // 已有的写入加密逻辑
    private void DbContext_SavingChanges(object? sender, SavingChangesEventArgs e)
    {
        foreach (var entry in ChangeTracker.Entries())
        {
            if (entry.State is EntityState.Added or EntityState.Modified)
            {
                EncryptEntity(entry.Entity);
            }
        }
    }

    // 实体加密方法
    private void EncryptEntity(object entity)
    {
        var encryptProps = entity.GetType().GetProperties()
            .Where(p => p.GetCustomAttribute<EncryptAttribute>() != null && p.PropertyType == typeof(string));

        foreach (var prop in encryptProps)
        {
            var value = prop.GetValue(entity) as string;
            if (!string.IsNullOrEmpty(value))
            {
                // 替换为你的安全加密实现
                prop.SetValue(entity, Encrypt(value));
            }
        }
    }

    // 实体解密方法
    private void DecryptEntity(object entity)
    {
        var encryptProps = entity.GetType().GetProperties()
            .Where(p => p.GetCustomAttribute<EncryptAttribute>() != null && p.PropertyType == typeof(string));

        foreach (var prop in encryptProps)
        {
            var value = prop.GetValue(entity) as string;
            if (!string.IsNullOrEmpty(value))
            {
                // 替换为你的安全解密实现
                prop.SetValue(entity, Decrypt(value));
            }
        }
    }

    // 示例加密解密逻辑(请替换为AES等安全算法)
    private string Encrypt(string plainText) => Convert.ToBase64String(Encoding.UTF8.GetBytes(plainText));
    private string Decrypt(string cipherText) => Encoding.UTF8.GetString(Convert.FromBase64String(cipherText));
}

方案2:自定义值转换器(推荐,EF Core原生机制)

为带[Encrypt]特性的string属性注册值转换器,EF Core会自动在读写数据库时处理加解密,无需依赖事件,性能更优且代码更简洁。

步骤1:实现值转换器

public class EncryptedStringConverter : ValueConverter<string, string>
{
    public EncryptedStringConverter(ConverterMappingHints? mappingHints = null)
        : base(
            plainText => Encrypt(plainText),
            cipherText => Decrypt(cipherText),
            mappingHints)
    {
    }

    private static string Encrypt(string plainText)
    {
        if (string.IsNullOrEmpty(plainText)) return plainText;
        // 替换为AES等安全加密算法实现
        return Convert.ToBase64String(Encoding.UTF8.GetBytes(plainText));
    }

    private static string Decrypt(string cipherText)
    {
        if (string.IsNullOrEmpty(cipherText)) return cipherText;
        // 替换为对应解密算法实现
        return Encoding.UTF8.GetString(Convert.FromBase64String(cipherText));
    }
}

步骤2:在DbContext中全局注册

public class YourDbContext : DbContext
{
    public YourDbContext(DbContextOptions<YourDbContext> options) : base(options)
    {
        // 不需要SavingChanges事件,值转换器会自动处理写入加密
    }

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

        // 遍历所有实体属性,自动应用加密转换器
        foreach (var entityType in modelBuilder.Model.GetEntityTypes())
        {
            foreach (var property in entityType.GetProperties())
            {
                if (property.ClrType == typeof(string) && 
                    property.PropertyInfo?.GetCustomAttribute<EncryptAttribute>() != null)
                {
                    property.SetValueConverter(new EncryptedStringConverter());
                }
            }
        }
    }
}

该方案优势:

  • 完全由EF Core原生机制处理,主实体和关联实体的读写加解密自动完成,无需额外代码。
  • 性能优于事件反射,值转换器是EF Core查询 pipeline的一部分,开销更低。
  • 代码更简洁,无需维护SavingChanges和Materialized事件逻辑。

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.07.05 10:24:58