【问题标题】:How to mask sensetive data for particular requests (NLog)如何屏蔽特定请求的敏感数据 (NLog)
【发布时间】:2022-12-01 15:53:42
【问题描述】:

我的一些动作接受以下模型:

    public class PaymentRequest
    {
        public decimal Amount { get; set; }
        public bool? SaveCard { get; set; }
        public int? SmsCode { get; set; }
        public BankCardDetails Card { get; set; }
    }

    public class BankCardDetails
    {
        public string Number { get; set; }
        public string HolderName { get; set; }
        public string ExpiryDate { get; set; }
        public string ValidationCode { get; set; }
    }

动作方法如下所示:

        [HttpPost]
        [Route("api/v1/payment/pay")]
        public Task<BankCardActionResponse> Pay([FromBody] PaymentRequest request)
        {
            if (request == null)
                throw new HttpResponseException(HttpStatusCode.BadRequest);

            return _paymentService.PayAsync(DataUserHelper.PhoneNumber, request);
        }

我使用 Nlog。我认为记录所有这些银行数据显然不是一个好主意。我的日志配置文件包含以下行:

<attribute name="user-requestBody" layout="${aspnet-request-posted-body}"/>

我记录了请求。我决定重构它并计划以下策略。在他们的请求中包含敏感数据的操作我将用类似的属性标记

 [RequestMethodFormatter(typeof(PaymentRequest))]

然后看看我的自定义渲染器:

    [LayoutRenderer("http-request")]
    public class NLogHttpRequestLayoutRenderer : AspNetRequestPostedBody
    {
        protected override void DoAppend(StringBuilder builder, LogEventInfo logEvent)
        {
            base.DoAppend(builder, logEvent);

            var body = builder.ToString();
            
            // Get attribute of the called action. 
            var type = ... // How can I get "PaymentRequest" from the [RequestMethodFormatter(typeof(PaymentRequest))] 
            var res = MaskHelper.GetMaskedJsonString(body, type);
           
           
            // ... and so on
        }
    }

我想你明白这个想法。我需要方法的 RequestMethodFormatter 属性中的类型。甚至有可能将它放入渲染器中吗?我需要它,因为我要将请求 JSON 反序列化为特定模型(它将进入 MaskHelper.GetMaskedJsonString),使用屏蔽数据的模型,将其序列化回 JSON。

那么,我是否选择了错误的方法?或者可以从属性中获取类型到渲染器中?

【问题讨论】:

  • 如果你想记录银行数据,我不建议这样做,那么至少要疯狂地加密它,或者用一种疯狂的单向密码将它包装起来。显然,后者带来的问题是您无法重新创建数据,只能确认它是您所拥有的,是您的密码验证它的内容。但理论上,您可以做很多事情。如果你想把你所说的“屏蔽”设为可选,我只想说,创建一个工厂/策略模式,并根据你提供的参数,创建一个屏蔽或不屏蔽数据的类,发送到日志。
  • 您可以通过使用装饰器进一步使其整洁,因此屏蔽被称为记录方法的装饰,然后仅在通过策略模式找到策略时应用。这将使它成为一个非常干净的实现,并且易于测试。或者,您可以采用面向方面的编程方式,在调用某些方法时编织掩码。
  • 请注意 NLog.Web 存储库已开放请求请求以改进 ${aspnet-request-posted-body} 的中间件,因此您可以在中间件中进行清理/屏蔽,而不是使用自定义布局渲染器。

标签: c# .net asp.net-mvc nlog .net-4.8


【解决方案1】:

经过一些研究,我最终得到了以下解决方案:


namespace ConsoleApp7
{
    internal class Program
    {
        private static void Main()
        {
            var sourceJson = GetSourceJson();
            var userInfo = JsonConvert.DeserializeObject(sourceJson, typeof(User));

            Console.WriteLine("----- Serialize without Resolver-----");
            Console.WriteLine(JsonConvert.SerializeObject(userInfo));

            Console.WriteLine("----- Serialize with Resolver-----");
            Console.WriteLine(JsonConvert.SerializeObject(userInfo, new JsonSerializerSettings
            {
                ContractResolver = new MaskPropertyResolver()
            }));
        }

        #region Получаем  json типа с реквеста
        private static string GetSourceJson()
        {
            var guid = Guid.Parse("3e92f0c4-55dc-474b-ae21-8b3dac1a0942");
            return JsonConvert.SerializeObject(new User
            {
                UserId = guid,
                Age = 19,
                Name = "John",
                BirthDate = new DateTime(1990, 5, 12),
                Hobbies = new[]
                {
                    new Hobby
                    {
                        Name = "Football",
                        Rating = 5,
                        DurationYears = 3,
                    },
                    new Hobby
                    {
                        Name = "Basketball",
                        Rating = 7,
                        DurationYears = 4,
                    }
                }
            });
        }

        #endregion
    }

    public class User
    {
        [MaskGuidValue]
        public Guid UserId { get; set; }
        [MaskStringValue("***")] public string Name { get; set; }
        public int Age { get; set; }
        [MaskDateTimeValue]
        public DateTime BirthDate { get; set; }

        public Hobby[] Hobbies { get; set; }
    }

    public class Hobby
    {
        [MaskStringValue("----")] 
        public string Name { get; set; }

        [MaskIntValue(replacement: 11111)]
        public int Rating { get; set; }

        public int DurationYears { get; set; }
    }

    public class MaskPropertyResolver : DefaultContractResolver
    {
        protected override IList<JsonProperty> CreateProperties(Type type, MemberSerialization memberSerialization)
        {
            var props = base.CreateProperties(type, memberSerialization);
            var allowedPropertyTypes = new Type[]
            {
                typeof(Guid),
                typeof(DateTime),
                typeof(string),
                typeof(int),
            };

            foreach (var prop in props.Where(p => allowedPropertyTypes.Contains(p.PropertyType)))
            {
                if (prop.UnderlyingName == null)
                    continue;

                var propertyInfo = type.GetProperty(prop.UnderlyingName);
                var attribute =
                    propertyInfo?.GetCustomAttributes().FirstOrDefault(x => x is IMaskAttribute) as IMaskAttribute;

                if (attribute == null)
                {
                    continue;
                }

                if (attribute.Type != propertyInfo.PropertyType)
                {
                    // Log this case, cause somebody used wrong attribute
                    continue;
                }
                
                prop.ValueProvider = new MaskValueProvider(propertyInfo, attribute.Replacement, attribute.Type);
            }

            return props;
        }

        private class MaskValueProvider : IValueProvider
        {
            private readonly PropertyInfo _targetProperty;
            private readonly object _replacement;
            private readonly Type _type;

            public MaskValueProvider(PropertyInfo targetProperty, object replacement, Type type)
            {
                _targetProperty = targetProperty;
                _replacement = replacement;
                _type = type;
            }

            public object GetValue(object target)
            {
                return _replacement;
            }

            public void SetValue(object target, object value)
            {
                _targetProperty.SetValue(target, value);
            }
        }
    }
    

    [AttributeUsage(AttributeTargets.Property)]
    public class MaskStringValueAttribute : Attribute, IMaskAttribute
    {
        public Type Type => typeof(string);
        public object Replacement { get; }

        public MaskStringValueAttribute(string replacement)
        {
            Replacement = replacement;
        }
    }

    [AttributeUsage(AttributeTargets.Property)]
    public class MaskIntValueAttribute : Attribute, IMaskAttribute
    {
        public object Replacement { get; }
        public Type Type => typeof(int);

        public MaskIntValueAttribute(int replacement)
        {
            Replacement = replacement;
        }
    }

    [AttributeUsage(AttributeTargets.Property)]
    public class MaskGuidValueAttribute : Attribute, IMaskAttribute
    {
        public Type Type => typeof(Guid);
        public object Replacement => Guid.Parse("ffffffff-ffff-ffff-ffff-ffffffffffff");
    }


    [AttributeUsage(AttributeTargets.Property)]
    public class MaskDateTimeValueAttribute : Attribute, IMaskAttribute
    {
        public Type Type => typeof(DateTime);
        public object Replacement => new DateTime(1970, 1, 1);
    }

    public interface IMaskAttribute
    {
        Type Type { get; }
        object Replacement { get; }
    }
}

我希望有人会觉得它有帮助。

【讨论】:

    猜你喜欢
    • 2020-05-01
    • 1970-01-01
    • 2023-03-29
    • 1970-01-01
    • 2018-08-15
    • 2015-07-18
    • 1970-01-01
    • 2017-03-22
    • 1970-01-01
    相关资源
    最近更新 更多