【问题标题】:How to sanitize JSON input parameters when using MVC4 ApiController?使用 MVC4 ApiController 时如何清理 JSON 输入参数?
【发布时间】:2013-09-27 08:47:59
【问题描述】:

我构建了一个基于 AntiXSS 的 HTML 清理器,通过覆盖默认模型绑定器来自动清理用户输入字符串,该模型绑定器在标准发布请求上运行良好。然而,当使用新的 ApiController 时,默认的模型绑定器永远不会被调用,我认为这是因为这个新的 MVC 控制器使用 JSON 格式化程序而不是绑定来自请求正文的输入数据。

那么如何扩展格式化程序以便在字符串属性被 JSON 绑定后修改它们?我宁愿不必在控制器级别实现这一点,而且应该有办法在它到达控制器之前做到这一点。

【问题讨论】:

    标签: asp.net-mvc-4 json.net modelbinders defaultmodelbinder


    【解决方案1】:

    我通过创建一个修改过的 Json 格式化程序解决了我的问题,但是大多数关于如何做到这一点的文档都是基于 .Net 4.0 的预发布代码。

    using System;
    using System.Collections.Concurrent;
    using System.Collections.Generic;
    using System.Diagnostics.Contracts;
    using System.Linq;
    using System.IO;
    using System.Net.Http;
    using System.Net.Http.Headers;
    using System.Net.Http.Formatting;
    using System.Runtime.Serialization;
    using System.Runtime.Serialization.Json;
    using System.Text;
    using System.Reflection;
    using System.Threading.Tasks;
    using System.Web;
    using System.Web.Script.Serialization;
    using Newtonsoft.Json;
    using Newtonsoft.Json.Serialization;
    
    public class JsonNetFormatterAntiXss : JsonMediaTypeFormatter
    {
        public override bool CanReadType(Type type)
        {
            return base.CanReadType(type);
        }
        public override bool CanWriteType(Type type)
        {
            return base.CanWriteType(type);
        }
    
        public override Task<object> ReadFromStreamAsync(Type type, Stream readStream, HttpContent content, IFormatterLogger formatterLogger)
        {
            HttpContentHeaders contentHeaders = content == null ? null : content.Headers;
            // If content length is 0 then return default value for this type
            if (contentHeaders != null && contentHeaders.ContentLength == 0)
            {
                return Task.FromResult(MediaTypeFormatter.GetDefaultValueForType(type));
            }
    
            // Get the character encoding for the content
            Encoding effectiveEncoding = SelectCharacterEncoding(contentHeaders);
    
            try
            {
                using (JsonTextReader jsonTextReader = new JsonTextReader(new StreamReader(readStream, effectiveEncoding)) { CloseInput = false, MaxDepth = _maxDepth })
                {
                    JsonSerializer jsonSerializer = JsonSerializer.Create(_jsonSerializerSettings);
                    if (formatterLogger != null)
                    {
                        // Error must always be marked as handled
                        // Failure to do so can cause the exception to be rethrown at every recursive level and overflow the stack for x64 CLR processes
                        jsonSerializer.Error += (sender, e) =>
                        {
                            Exception exception = e.ErrorContext.Error;
                                formatterLogger.LogError(e.ErrorContext.Path, exception);
                            e.ErrorContext.Handled = true;
                        };
                    }
    
                    return Task.FromResult(DeserializeJsonString(jsonTextReader, jsonSerializer, type));
                }
    
            }
            catch (Exception e)
            {
                if (formatterLogger == null)
                {
                    throw;
                }
                formatterLogger.LogError(String.Empty, e);
                return Task.FromResult(MediaTypeFormatter.GetDefaultValueForType(type));
            }
        }
    
        private object DeserializeJsonString(JsonTextReader jsonTextReader, JsonSerializer jsonSerializer, Type type)
        {
            object data = jsonSerializer.Deserialize(jsonTextReader, type);
    
            // sanitize strings if we are told to do so
            if(_antiXssOptions != AntiXssOption.None)
                data = CleanAntiXssStrings(data); // call your custom XSS cleaner
    
            return data;
        }
    
        /// <summary>
        /// Clean all strings using internal AntiXss sanitize operation
        /// </summary>
        /// <param name="data"></param>
        /// <returns></returns>
        private object CleanAntiXssStrings(object data)
        {
            PropertyInfo[] properties = data.GetType().GetProperties();
            foreach (PropertyInfo property in properties)
            {
                Type ptype = property.PropertyType;
                if (ptype == typeof(string) && ptype != null)
                {
                    // sanitize the value using the preferences set
                    property.SetValue(data, DO_MY_SANITIZE(property.GetValue(data).ToString()));
                    }
                }
                return data;
            }
    
        public override Task WriteToStreamAsync(Type type, object value, Stream writeStream, HttpContent content, System.Net.TransportContext transportContext)
        {
            return base.WriteToStreamAsync(type, value, writeStream, content, transportContext);
        }
    
        private DataContractJsonSerializer GetDataContractSerializer(Type type)
        {
            Contract.Assert(type != null, "Type cannot be null");
            DataContractJsonSerializer serializer = _dataContractSerializerCache.GetOrAdd(type, (t) => CreateDataContractSerializer(type, throwOnError: true));
    
            if (serializer == null)
            {
                // A null serializer means the type cannot be serialized
                throw new InvalidOperationException(String.Format("Cannot serialize '{0}'", type.Name));
            }
    
            return serializer;
        }
    
        private static DataContractJsonSerializer CreateDataContractSerializer(Type type, bool throwOnError)
        {
            if (type == null)
            {
                throw new ArgumentNullException("type");
            }
    
            DataContractJsonSerializer serializer = null;
            Exception exception = null;
    
            try
            {
                // Verify that type is a valid data contract by forcing the serializer to try to create a data contract
                XsdDataContractExporter xsdDataContractExporter = new XsdDataContractExporter();
                xsdDataContractExporter.GetRootElementName(type);
                serializer = new DataContractJsonSerializer(type);
            }
            catch (InvalidDataContractException invalidDataContractException)
            {
                exception = invalidDataContractException;
            }
    
            if (exception != null)
            {
                if (throwOnError)
                {
                    throw new InvalidOperationException(String.Format("Can not serialize type '{0}'.", type.Name), exception);
                }
            }
    
            return serializer;
        }
    
    }
    

    我基于 JsonMediaTypeFormatter 的 Json.NET implementationarticle 构建了这段代码。至于我的 AntiXSS 实现,我没有费心发布它,它结合了 AntiXSS 4.2.1 和自定义解析,因为该库过度保护了。

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 2011-05-20
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2014-08-12
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      相关资源
      最近更新 更多