需要加密asp.net MVC动作参数

时间:2017-04-05 20:43:14

标签: c# asp.net-mvc asp.net-mvc-4

我一直致力于一个解决方案,允许我们加密和解密asp.net MVC项目的URL中的数据。我的目标是允许我们加密路由数据和/或查询字符串数据,而开发人员不必在控制器或动作中执行任何特殊操作来解密并将数据绑定到模型。我通过编写自己的IValueProvider实现了这一目标。我的值提供程序在传入的RouteData和查询字符串键中搜索指示它们是加密值的前缀。如果它们以前缀开头,则会解密它们并为它们提供值。如果你对TL感兴趣,那就是大部分内容; DR(现在用于测试我只是使用base 64编码,而不是加密):

public class EncryptedDataValueProvider : IValueProvider
{
    string _encryptionPrefix = "_enc!";
    Dictionary<string, string> _values;

    public EncryptedDataValueProvider(ControllerContext controllerContext)
    {
        _values = new Dictionary<string, string>();

        foreach (KeyValuePair<string, object> entry in controllerContext.RouteData.Values)
        {
            object val = entry.Value;
            if (val != null && val is string && ((string)val).StartsWith(_encryptionPrefix))
            {
                //TODO: Decrypt substring
                string encryptedVal = ((string)val).Substring(_encryptionPrefix.Length);
                string decryptedVal = Encoding.UTF8.GetString(Convert.FromBase64String(encryptedVal));
                _values.Add(entry.Key, decryptedVal);
            }
        }

        //Note: The .NET source code seems to indicate accessing QS keys might trigger request validation before we want it to. 
        //May have to use controllerContext.HttpContext.Request.Unvalidated to get at the keys instead. See 
        //QueryStringValueProvider.cs, NameValueCollectionValueProvider.cs, and UnvalidatedRequestValuesWrapper.cs ("M.W.I" is Microsoft.Web.Infrastructure where request validation is built)
        foreach (string key in controllerContext.HttpContext.Request.QueryString) 
        {
            if (key.StartsWith(_encryptionPrefix))
            {
                string val = controllerContext.HttpContext.Request.QueryString[key];

                if (val != null)
                {
                    //TODO: Decrypt val
                    string decryptedVal = Encoding.UTF8.GetString(Convert.FromBase64String(val));
                    NameValueCollection decryptedQs = HttpUtility.ParseQueryString(decryptedVal);

                    foreach (string decryptedKey in decryptedQs)
                        if (!_values.ContainsKey(decryptedKey))
                            _values.Add(decryptedKey, decryptedQs[decryptedKey]);
                }
            }
        }
    }

    public bool ContainsPrefix(string prefix)
    {
        return _values.ContainsKey(prefix);
    }

    public ValueProviderResult GetValue(string key)
    {
        if (_values.ContainsKey(key))
            return new ValueProviderResult(_values[key], _values[key], System.Globalization.CultureInfo.InvariantCulture);
        else
            return null;
    }
}

这可以很好地解密传入的加密(编码)值。我现在需要做的是提供一种方法来指定给定的动作参数(a.k.a. model)必须加密。也就是说,我不想接受未加密的URL值。需要注意的是,我希望尽可能保持灵活性。理想情况下,我会有一个属性,我可以应用于单个操作方法参数,整个操作方法或整个控制器,以便我们仍然可以选择接受未加密的参数(如果需要)。我正在想象这样的事情:

[RequireEncryption]
public class MyController : Controller
{
    public ActionResult Action1(string secret)
    {
    }

    [RequireEncryption(false)]
    public ActionResult Action2(string notSecret)
    {

    }

    public ActionResult Action3(string secret, [RequireEncryption(false)]string notSecret)
    {
    }
}

好吧,我几乎也实现了这一点(除了class / method-level属性总是覆盖参数级属性),但它只是感觉像一个丑陋的黑客。为此,我必须创建一个ModelBinder,一个CustomModelBinderAttribute和一个IAuthorizationFilter FilterAttribute。我使用这些替换内置的ValueProviderCollection只使用我的单个EncryptedDataValueProvider,这样如果它没有模型的解密值,模型将保持为null。两个属性都使用相同的方法,但我必须使用CustomModelBinderAttribute来获取参数级属性,并使用授权过滤器来获取操作&amp;控制器级属性。

我不喜欢这个是1)我必须有两个不同的属性,它不能完全实现我的愿景,但2)ModelBinder和授权过滤器正在修改值提供者集合。这似乎超出了他们预期的范围。有没有更合适的方法来扩展MVC框架来实现这一目标?

public class RequireEncryptionOnAllAttribute : FilterAttribute, IAuthorizationFilter
{
    public void OnAuthorization(AuthorizationContext filterContext)
    {
        filterContext.Controller.ValueProvider = new EncryptedDataValueProvider(filterContext.Controller.ControllerContext);
    }
}

public class RequireEncryption : CustomModelBinderAttribute
{
    public override IModelBinder GetBinder()
    {
        return new EncryptionRequiredBinder();
    }
}

/// <summary>
/// Binds the model using only the EncryptedDataValueProvider. Unencrypted values will not be bound.
/// </summary>
public class EncryptionRequiredBinder : DefaultModelBinder
{
    public override object BindModel(ControllerContext controllerContext, ModelBindingContext bindingContext)
    {
        bindingContext.ValueProvider = new EncryptedDataValueProvider(controllerContext);

        return base.BindModel(controllerContext, bindingContext);
    }
}

0 个答案:

没有答案