西北狼

-- 学而时习之,不亦乐乎!
  博客园  :: 首页  :: 新随笔  :: 联系 :: 订阅 订阅  :: 管理

MVC学习之模型绑定的安全性

Posted on 2009-05-14 13:36  西北老狼  阅读(592)  评论(1编辑  收藏  举报
模型绑定的安全性
我们已经讨论了2中不同的方法使用ASP.NET MVC内置的模型绑定功能。第一个方法是使用UpdateModel() 方法更新一个已存在的模型对象的属性;第二个方法是传递模型对象,作为action方法的参数。这两项技术都非常强大和有用。
 
功能虽然强大,但也需要考虑用户输入的安全性,包括绑定对象到表单的输入。一定要通过HTML 编码所有用户输入值,避免HTML和Javascript注入攻击(备注:我们的范例应用程序使用LINQ to SQL,可以自动编码所有参数,避免这些类型的攻击)。不能仅仅依赖于客户端的验证,总是要采用服务端的验证,阻止攻击者试图传入无效的数据。
默认情况下,UpdateModel() 方法试图根据匹配的表单参数值,更新所有的属性。同样地,作为参数传递给action 方法的模型对象,基于表单参数设置全部模型对象的属性。
 
基于用途来锁定绑定
你可以基于用途,提供显式的可更新的属性列表,锁定绑定策略。这可以通过传递一个额外的字符串数组参数给UpdateModel() 方法:
            string[] allowedProperties = new[]{ "Title", "Description",
                "ContactPhone", "Address",
                "EventDate", "Latitude",
                "Longitude"};
            UpdateModel(dinner, allowedProperties);
 
作为传递给Action方法的参数 – 模型对象也支持[Bind]属性,允许指定include list或者允许的属性列表,如下所示:
// POST: /Dinners/Create
[AcceptVerbs(HttpVerbs.Post)]
public ActionResult Create( [Bind(Include="Title,Address")] Dinner dinner ) {
...
}
 
基于类型来锁定绑定
你可以基于类型来锁定绑定规则。这样你一旦制定绑定规则,就可以在所有Controllers 和 Action方法中应用了,包括UpdateModel方法和Action方法的参数。
通过添加[Bind]属性在类型上,或者在应用程序中的Global.asax文件中(如果类型不是我们自己定义的情况下,非常有用),来定制类型绑定规则。戒指使用Bind属性的Include和Exclude属性来控制类或接口中哪些属性是可绑定的。
 
我们的NerdDinner应用程序将使用这一技术,添加[Bind]属性,显示可绑定的属性:
[Bind(Include="Title,Description,EventDate,Address,Country,ContactPhone,Latitude,Longi
tude")]
public partial class Dinner {
}
 
注意到RSVPs集合、DinnerID和HostedBy 属性都不允许通过绑定来设置。基于安全的原因,这些属性只能在action方法中使用显式的代码进行设置。
 
CRUD封装
ASP.NET MVC包括一些内置的功能,帮助实现表单提交的场景。我们在DinnerRepository类中使用了大量这些功能提供CRUD的支持。
我们使用以Model模型为中心的方法来实现我们的范例程序。这意味着所有的验证和业务规则都在模型层(Model Layer)中定义 – 而不是在Controllers控制器和View视图中。Controller类和View视图模板都不必了解模型类实现的业务规则。
 
这样,可以保持我们的应用程序架构简洁和易于测试。在将来,我们可以添加额外的业务规则到模型层(Model Layer)中,而不必更改Controller类和View视图,就可以使用了。这一特性提供了我们很多的灵活性来改进我们的应用程序。
 
DinnersController控制器实现了Dinner列表和显示详细信息,还有创建、编辑和删除操作等等。该类的完整代码如下:
    public class DinnersController : Controller
    {
        DinnerRepository dinnerRepository = new DinnerRepository();
        //
        // GET: /Dinners/
        public ActionResult Index()
        {
            var dinners = dinnerRepository.FindUpcomingDinners().ToList();
 
            return View("Index", dinners);
        }
        //
        // GET: /Dinners/Details/2
        public ActionResult Details(int id)
        {
            Dinner dinner = dinnerRepository.GetDinner(id);
 
            if (dinner == null)
                return View("NotFound");
            else
                return View("Details", dinner);
        }
 
        //
        // GET: /Dinners/Edit/2
        public ActionResult Edit(int id)
        {
            Dinner dinner = dinnerRepository.GetDinner(id);
            return View(dinner);
        }
 
        //
        // POST: /Dinners/Edit/2
        [AcceptVerbs(HttpVerbs.Post)]
        public ActionResult Edit(int id, FormCollection formValues)
        {
            Dinner dinner = dinnerRepository.GetDinner(id);
 
            try
            {
                UpdateModel(dinner);
                dinnerRepository.Save();
                return RedirectToAction("Details", new { id = dinner.DinnerID });
            }
            catch
            {
                ModelState.AddRuleViolations(dinner.GetRuleViolations());
                return View(dinner);
            }
        }
 
        //
        // GET: /Dinners/Create
        public ActionResult Create()
        {
            Dinner dinner = new Dinner()
            {
                EventDate = DateTime.Now.AddDays(7)
            };
            return View(dinner);
        }
 
        //
        // POST: /Dinners/Create
        [AcceptVerbs(HttpVerbs.Post)]
        public ActionResult Create(Dinner dinner)
        {
            if (ModelState.IsValid)
            {
                try
                {
                    dinnerRepository.Add(dinner);
                    dinnerRepository.Save();
                    return RedirectToAction("Details", new { id = dinner.DinnerID });
                }
                catch
                {
                    ModelState.AddRuleViolations(dinner.GetRuleViolations());
                }
            }
            return View(dinner);
        }
 
       //
        // HTTP GET: /Dinners/Delete/1
        public ActionResult Delete(int id)
        {
            Dinner dinner = dinnerRepository.GetDinner(id);
            if (dinner == null)
                return View("NotFound");
            else
                return View(dinner);
        }
 
        //
        // HTTP POST: /Dinners/Delete/2
        [AcceptVerbs(HttpVerbs.Post)]
        public ActionResult Delete(int id, string confirmButton)
        {
            Dinner dinner = dinnerRepository.GetDinner(id);
            if (dinner == null)
                return View("NotFound");
            dinnerRepository.Delete(dinner);
            dinnerRepository.Save();
            return View("Deleted");
        }
    }