如何在使用 MVC4 ApiController 时清理 JSON 输入参数



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

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

我通过创建修改后的 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 实现以及本文基于此代码。至于我的AntiXSS实现,我没有费心发布它,它使用AntiXSS 4.2.1和自定义解析的组合,因为该库过于过度保护。

相关内容

  • 没有找到相关文章

最新更新