-
Notifications
You must be signed in to change notification settings - Fork 893
Open
Description
问题描述及重现代码:
背景:
对于身份证号和手机号等敏感信息,要求在数据库中加密存储。
EFCore:之前使用的EFCore,里面有一个值转换器的东西可以自动实现插入时加密,查询时解密。非常好用,业务只关心原始值,而不用关心加密值。
FreeSQL:没有找到类似的值转换器功能,只能通过AOP事件来实现类似功能。
bug描述:多个 FreeSql 实例时,AOP事件会互相影响,导致状态不一致。
bug重现步骤(以自动加解密的场景为例):
第一步:定义两个 FreeSql 实例 fsql1 和 fsql2,fsql1 配置AOP的自动值处理(插入加密)或DataReader拦截(查询解密),fsql2 不配置。
第二步:使用 fsql1 插入一条数据,触发AOP自动加密。
第三步:使用 fsql1(触发自动解密) 和 fsql2 分别查询该数据。
第四步:期望 fsql1 查询结果为解密后的原始值,fsql2 查询结果为加密后的存储值。但实际结果是两个实例查询结果均为解密后的原始值。
public class FreesqlAutoCryptoTest2
{
public record Fsql1;
public record Fsql2;
/// <summary>
/// 背景:对于身份证号和手机号等敏感信息,要求在数据库中加密存储。
/// EFCore:之前使用的EFCore,里面有一个值转换器的东西可以自动实现插入时加密,查询时解密。非常好用,业务只关心原始值,而不用关心加密值。
/// FreeSQL:没有找到类似的值转换器功能,只能通过AOP事件来实现类似功能。
/// bug描述:多个 FreeSql 实例时,AOP事件会互相影响,导致状态不一致。
/// bug重现步骤(以自动加解密的场景为例):
/// 第一步:定义两个 FreeSql 实例 fsql1 和 fsql2,fsql1 配置AOP的自动值处理(插入加密)或DataReader拦截(查询解密),fsql2 不配置。
/// 第二步:使用 fsql1 插入一条数据,触发AOP自动加密。
/// 第三步:使用 fsql1(触发自动解密) 和 fsql2 分别查询该数据。
/// 第四步:期望 fsql1 查询结果为解密后的原始值,fsql2 查询结果为加密后的存储值。但实际结果是两个实例查询结果均为解密后的原始值。
/// </summary>
[Test]
public void InsertUpdateAutoEnrcyptTests()
{
var fsql1 = ProvideFsql<Fsql1>();
// 在第一个 FreeSql 实例上配置自动加解密
AopConfig(fsql1);
// 第二个 FreeSql 实例不配置自动加解密
var fsql2 = ProvideFsql<Fsql2>();
var stu0 = new Student
{
StuName = "测试ceshics张三A测试ceshi",
CertificateType = "1",
CertificateNo = "516423156476894261",
MobilePhone = "12546987465",
MobilePhone2 = "12546987789",
Password = "123456789456",
StudentStatus = 1,
RegisterTime = DateTime.Now
};
// 第一个实例插入数据,自动加密后存到数据库。
// bug1:这里自动加密会修改原始对象stu0的值。按逻辑说,这里应该修改,但是这种场景应用较少。
// 能不能提供一个选项或方法,不修改原始对象?
var stuId = fsql1.Insert(stu0).ExecuteIdentity();
// 第一个实例查询,触发自动解密,查询结果是解密后的值
var stu1 = fsql1.Select<Student>().Where(s => s.StuName.Equals(stu0.StuName)).ToOne();
// 第二个实例查询,不触发自动解密,查询结果是加密后的值
var stu2 = fsql2.Select<Student>().Where(s => s.StuName.Equals(stu0.StuName)).ToOne();
fsql1.Delete<Student>().Where(s => s.StuName.Equals(stu0.StuName)).ExecuteAffrows();
using (Assert.EnterMultipleScope())
{
Assert.That(stu1, Is.Not.EqualTo(stu2));
// 验证第一个实例查询结果是解密后的值
Assert.That(stu1.CertificateNo, Is.EqualTo("516423156476894261"));
Assert.That(stu1.MobilePhone, Is.EqualTo("12546987465"));
Assert.That(stu1.MobilePhone2, Is.EqualTo("12546987789"));
// 验证第二个实例查询结果是加密后的值
Assert.That(stu2.CertificateNo, Is.EqualTo("D3DF4F22F89BC68C485BD7056A9DABB1D07E061A156FCCB7C4CA10FCD054F2335A5B53"));
Assert.That(stu2.MobilePhone, Is.EqualTo("83CD25466A9085D9A2BE0C34456E58DC5F8B7B"));
Assert.That(stu2.MobilePhone2, Is.EqualTo("19BC0F7AC7258992C25005E67E6C7A36BF07E2"));
}
}
private static IFreeSql<T> ProvideFsql<T>()
{
var config = new ConfigurationBuilder().AddEnvironmentVariables().Build();
var dbConn = ConnectionStringHelper.BuildDbPlaceholder(config.GetConnectionString("ZhongkaoDbPgsql"), "guang_an");
var fsql = new FreeSqlBuilder()
.UseConnectionString(DataType.PostgreSQL, dbConn)
.UseNameConvert(NameConvertType.PascalCaseToUnderscoreWithLower)
.Build<T>();
return fsql;
}
private static IFreeSql AopConfig(IFreeSql fsql)
{
fsql.Aop.AuditValue += (s, e) =>
{
if (typeof(Student) == e.Property.DeclaringType)
{
switch (e.Property.Name)
{
case nameof(Student.CertificateNo):
case nameof(Student.MobilePhone):
case nameof(Student.MobilePhone2):
e.Value = DataEncryptConverterUtility.Forward(e.Value as string)!;
break;
}
}
};
fsql.Aop.AuditDataReader += (s, e) =>
{
Console.WriteLine($"FreeSql自动解密触发,字段: {e.Property.Name}");
switch (e.Property.Name)
{
case nameof(Student.CertificateNo):
case nameof(Student.MobilePhone):
case nameof(Student.MobilePhone2):
try
{
e.Value = DataEncryptConverterUtility.Backward(e.Value as string)!;
}
catch (Exception ex)
{
Console.WriteLine($"FreeSql自动解密失败,字段: {e.Property.Name}, 错误: {ex.Message}");
throw;
}
break;
}
};
return fsql;
}
}
执行结果
数据库版本
(openGauss 3.0.3 build 46134f73) compiled at 2023-01-10 22:43:35 commit 0 last mr on x86_64-unknown-linux-gnu, compiled by g++ (GCC) 7.3.0, 64-bit
安装的Nuget包
FreeSql.Provider.PostgreSQL" Version="3.5.212"
.net framework/. net core? 及具体版本
net10.0
Metadata
Metadata
Assignees
Labels
No labels