ASP.NET MVC: 使用自定义 ModelBinder 过滤敏感信息

昨天发表了一篇随笔《ASP.NET MVC: 使用 Filters 附加过滤敏感信息功能》(以下简称《Filter过滤》),今天一早醒来发现一处重大漏洞,于是在发了一条评论指出存在问题,并希望有朋友能指正。可到现在也没见有朋友找出问题,索引再发一篇随笔,进行更正。

存在的漏洞 

《Filter过滤》一文中使用的代码如下: 

 1       public   class  SensitiveWordsFilterAttribute: ActionFilterAttribute
 2      {
 3           public   override   void  OnActionExecuting(ActionExecutingContext filterContext)
 4          {
 5              var parameters  =  filterContext.ActionDescriptor.GetParameters();
 6               foreach  (var parameter  in  parameters)
 7              {
 8                   if  (parameter.ParameterType  ==   typeof ( string ))
 9                  {
10                       // 获取字符串参数原值
11                      var orginalValue  =  filterContext.ActionParameters[parameter.ParameterName]  as   string ;
12                       // 使用过滤算法处理字符串
13                      var filteredValue  =  SensitiveWordsFilter.Instance.Filter(orginalValue);
14                       // 将处理后值赋给参数
15                      filterContext.ActionParameters[parameter.ParameterName]  =  filteredValue;
16                  }
17              }
18          }
19      }

 问题在第8行,SensitiveWordsFilterAttribute 目前只能对字符串类型这样的简单数据进行过滤,没有考虑到复杂类型,对下面代码就则无法实现过滤:

1       public   class  ArticlesController : Controller
2      {
3          [HttpPost]
4           public  ActionResult Create(Article article)
5          {
6               // ...
7          }
8           // ...
9      }

 Article是一个自定义类:

1       public   class  Article: DomainModel
2      {
3           public   int  ID {  get set ; }
4           public   string  Title {  get set ; }
5           public   string  Content {  get set ; }
6           public  DateTime CreationTime {  get set ; }
7      }

SensitiveWordsFilterAttribute 对自定义类型是不太好处理的,当然也有解决办法,如使用反射遍历访问Article的每一个字符串属性,进行过滤。

本文采用 ASP.NET MVC Model Binding 来实现目标。

 ASP.NET MVC Model Binding

 在ASP.NET MVC中,我们可以使用下面的方式来接收用户提交的数据:

 1       public   class  ArticlesController : Controller
 2      {
 3           public  ActionResult Query( string  title, DateTime ?  creationDate)
 4          {
 5               // ...
 6          }
 7          [HttpPost]
 8           public  ActionResult Create(Article article)
 9          {
10               // ...
11          }
12      }

  这得益于 ASP.NET MVC ModelBinding 特性。使用这个特性,我们无需再使用 Request.Form["title"] 从 HTTP 请求中获取数据,也不需要再使用 DateTime.Parse() 或 DateTime.TryParse() 方法进行类型转换,也不再需要对实例的每一个属性逐一赋值。ModelBinding 特性将我们从这些无聊的低级工作中解放了出来,让我们专心去做高级的工作。

如上面代码所示,ModelBinding 既可以绑定简单数据类型,也可以绑定自定义类型,还可以绑定数组、集合、字典、二进制数据(byte[]),甚至还能用来接收文件上传。

ModelBinding 在绑定自定义类型时,还能够有选择的绑定指定属性或排除指定属性: 

1       public  ActionResult Create([Bind(Include  =   " Name, Sex, Birthday " )] Person person) 
2      { 
3           //  ... 
4      } 
5       public  ActionResult Create([Bind(Exclude  =   " ID " )] Person person) 
6      { 
7           //  ... 
8      }

 ASP.NET MVC ModelBinding 的功能相当完善,实现也比较复杂,具体是由 DefaultModelBinder 类来完成的。对于一些特定功能(如本文中的敏感信息过滤)可通过扩展 DefaultModelBinder 类的功能来实现。

具有敏感信息过滤功能 ModelBinder

 在面向对象的世界中,扩展功能最简单的方式就是继承。我们新建一个类 SensitiveWordsFilterModelBinder,继承自 DefaultModelBinder,重写 SetProperty 和 BindModel 方法,如下:

 1       public   class  SensitiveWordsFilterModelBinder : DefaultModelBinder
 2      {
 3           protected   override   void  SetProperty(ControllerContext controllerContext, 
 4              ModelBindingContext bindingContext, PropertyDescriptor propertyDescriptor,  object  value)
 5          {
 6               if  (propertyDescriptor.PropertyType  ==   typeof ( string ) /*  && value is string */ )
 7                  value  =  SensitiveWordsFilter.Instance.Filter(value  as   string );
 8               base .SetProperty(controllerContext, bindingContext, propertyDescriptor, value);
 9          }
10           public   override   object  BindModel(ControllerContext controllerContext, ModelBindingContext bindingContext)
11          {
12              var value  =   base .BindModel(controllerContext, bindingContext);
13               if (bindingContext.ModelType  ==   typeof ( string ) /*  && value is string */ )
14                  value  =  SensitiveWordsFilter.Instance.Filter(value  as   string );
15               return  value;
16          }
17      }

 重写 SetProperty 方法(3~9行)用来对自定义类型进行敏感信息过滤:对自定义类型来说,数据绑定主要针对属性,所有属性绑定完成,自定义类型的实例也就绑定完成了。重写 SetProperty 就是在给 string 类型的属性赋值前,增加敏感信息过滤这一步操作。相对使用 SensitiveWordsFilterAttribute + 反射 的方式处理自定义类型,SensitiveWordsFilterModelBinder 更加合理,效率也要高些。重写 SetProperty 时,可以通过 bindingContext.ModelType 获取所在类的类型,也可以通过 propertyDescriptor 获取当前属性的信息,还可以通过controllerContext获取当前controller的信息,通过这三个属性,我们可以有选择的进行过滤操作,以提高效率。

重写 BindModel 方法(10~16行)用来对 string 类型进行敏感信息过滤,string 是简单数据库类型,对它进行绑定时不会调用 SetProperty 方法,因此要单独进行处理。

仅仅有这个 ModelBinder 是不够的,MVC不知道什么情况下使用它,因此我们还要进行配置,使 SensitiveWordsFilterModelBinder 生效。

配置使用 SensitiveWordsFilterModelBinder

方式一:直接在参数上使用

1        public  ActionResult Create([ModelBinder( typeof (SensitiveWordsFilterModelBinder))]Article article)
2       {
3            return   null ;
4       }
5        public  ActionResult Create([ModelBinder( typeof (SensitiveWordsFilterModelBinder))] string  title,
6                   [ModelBinder( typeof (SensitiveWordsFilterModelBinder))] string  content, DateTime ?  creationTime)
7       {
8            return   null ;
9       }

 使用这种方式比较“ugly”,尤其是 ModelBinder 名字比较长的时候。这种方式的最大缺点是需要对每个参数进行标记。

方式二:标记在 Model 上 

1      [ModelBinder( typeof (SensitiveWordsFilterModelBinder))]
2       public   class  Article: DomainModel
3      {
4           // ...
5      }

这种比较不错,只需在 Article 类上标记一次,所有 Controller 中的 Action 的 Article 类型的参数都将使用 SensitiveWordsFilterModelBinder。

但这种方式仅对自定义类型有效,对系统定义的类型,如:string,我们是很难给它加上 Attribute 的。

 方式三:在 Global.asax.cs 文件中处理:

1       protected   void  Application_Start()
2      {
3          ModelBinders.Binders.Add( typeof (Article),  new  SensitiveWordsFilterModelBinder());
4          ModelBinders.Binders.Add( typeof ( string ),  new  SensitiveWordsFilterModelBinder());
5      }

如上面代码,我们可为每一种类型,设置一个 ModerBinder。也可以像下面这样进行批量设置:

1        protected   void  Application_Start()
2       {
3           var sensitiveWordsFilterModelBinder  =   new  SensitiveWordsFilterModelBinder();
4           var types  =   typeof (Article).Assembly.GetTypes().Where(t => t.IsSubclassOf( typeof (DomainModel)));
5            foreach  (var type  in  types)
6               ModelBinders.Binders.Add(type, sensitiveWordsFilterModelBinder);
7       }

这个就不多解释了。

使用 SensitiveWordsFilterModelBinder 方式的缺点

 有些 MVC 的初学者之前大多是做 ASP.NET,可能有时会使用 Request.Form["..."] 这种方式来接收数据,使用这种方式的地方都会成为系统的漏洞。

总结

ASP.Net MVC 采用了非常先进的设计思想,具有良好的可扩展性,可以通过各种方式轻松解决我们遇到的各种问题。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值