看到篇文章,里面说了个不知道的事儿,很粗糙生硬的翻了一下,记在这里,回头研究。
路过的朋友,如感兴趣,请直接看原文,以免耽误了您的时间。
原文ASP.NET MVC Tip #46 – Don’t use Delete Links because they create Security Holes
正文
我创建了一个 ASP.NET MVC 示例程序, 打算公布在 http://ww.ASP.net/mvc . 当 ASP.NET MVC 专题小组对该程序进行代码审查时 , 一个惊人的缺陷显现出来.
该程序极其简单。它包含了一个呈现数据库记录列表的视图。每条记录下面是一个Edit链接和一个Delete链接(见图1)。很标准的东西。或者,所以我想...
图1-- 一个数据库记录表格
这里就是缺陷。你不应该用链接来删除一条记录。使用Delete链接就打开了一个安全漏洞。
安全缺陷
某人可以发一封包含一个image的邮件给你。该image通过如下标签被嵌入到邮件中:
<img src=”http://www.theApp.com/Home/Delete/23” _fcksavedurl=””http://www.theApp.com/Home/Delete/23”” />
注意,src属性指向了Home控制器类的Delete() 方法。打开邮件(并且允许邮件客户端显示image)将在无警告的情况下删除23号记录。这是不好的。这是安全漏洞。
我曾经遇到过这种安全问题,但没有多想。REST纯粹主义者会捍卫Get请求不应该改变程序状态的想法。换句话说,执行Get请求应该是安全的,无副作用的。
比如,您不会希望搜索引擎在抓取您网站的时候删除您程序中的所有记录。执行Get请求不应该对您的程序有持久性影响。
删除一条记录的时候,适当的Http操作是 HTTP DELETE。HTTP 协议支持如下HTTP操作:
.OPITIONS - 返回可用的通信可选项的信息(幂等)。
.GET - 返回请求的任何信息(幂等)。
.HEAD - 与 GET 执行同样的操作,只是没有消息体(幂等)。
.POST - 发布新信息或更新已有信息(非幂等)。
.PUT - 发布新信息或更新已有信息(幂等)。
.DELETE - 删除信息(幂等)。
.TRACE - 执行一个消息循环 (幂等)。
.CONNECT - 用于SSL通道。
这些操作被定义为HTTP 1.1 标准的一部分,您可以在这里http://www.w3.org/Protocols/rfc2616/rfc2616-sec9.html看到.
注意,对 HTTP POST 和 HTTP PUT 的描述是相同的。为了解 POST 和 PUT 的区别,你需要明白幂等的含义。一个幂等操作无论执行多少次,都有相同的结果。比如,您执行一个POST操作来创建一条新的数据库记录,那么您每次都可以创建一条新的数据库记录。POST操作是非幂等的,因为每次操作的执行都会对您的程序有不同影响。
另一方面,如果您执行PUT操作,那么操作的执行都必须创建相同的数据库记录。PUT 操作是幂等的,应为执行PUT操作1000次与执行1次是一样的。
注意,HTTP DELETE 操作也是幂等的。多次执行HTTP DELETE 操作对您程序的影响应该是相同的。比如,请求 /Home/Delete/23 应该删除23号数据库记录,而不是其他记录,无论请求执行多少次。
HTML 只支持 GET和POST
所以,删除一条数据库记录时,恰当的做法是执行HTTP DELETE操作。执行一个HTTP DELETE操作不会打开安全漏洞,且不违背REST原则。
不幸的是,标准的HTML不支持GET、POST外的其他操作。链接总是执行GET操作,表单能执行GET或POST操作。HTML不支持其他的HTTP操作。
根据HTML 3.1 规范,HTML只支持GET和POST。参见http://www.w3.org/TR/REC-html32.html#form。此外,IE只支持GET和POST。参见http://msdn.microsoft.com/en-us/library/ms534167(VS.85).aspx。
执行 Ajax Deletes
如果您想超越标准HTML,您可以利用Ajax来执行HTTP DELETE操作。XmlHttpRequest对象支持任何HTTP操作。因此,如果您愿意您的程序依赖于Javascript,您可以以正确的方式做一切。
清单1中的Home 控制器包含Index()和Delete()方法。Index()方法从Movies数据库返回所有的movies,Delete()方法通过特定的Id删除特定的movie(此控制器使用Entity Framework)。
清单1 - ControllersHomeController.cs
using System;
using System.Collections.Generic;
using System.Linq;
using System.Web;
using System.Web.Mvc;
using Tip46.Models;
namespace Tip46.Controllers
{
[HandleError]
public class HomeController : Controller
{
private MoviesDBEntities _entities = new MoviesDBEntities();
public ActionResult Index()
{
ViewData.Model = _entities.MovieSet.ToList();
return View();
}
[AcceptVerbs(HttpVerbs.Delete)]
public ActionResult Delete(int id)
{
var movieToDelete = (from m in _entities.MovieSet
where m.Id == id
select m).FirstOrDefault();
_entities.DeleteObject(movieToDelete);
_entities.SaveChanges();
return RedirectToAction("Index");
}
}
}
注意,Delete()方法声明了AcceptVerbs特性。Delete()方法只能被HTTP DELETE操作调用。
清单2中的Index视图通过HTML table显示来自Movies数据库表中的数据。每个movie记录下呈现一个Delete链接。
清单2 - ViewsHomeIndex.aspx
<%@ Page Title="" Language="C#" MasterPageFile="~/Views/Shared/Site.Master" Inherits="System.Web.Mvc.ViewPage<IEnumerable<Tip46.Models.Movie>>" %>
<asp:Content ID="Content1" ContentPlaceHolderID="MainContent" runat="server">
<script src="../../Scripts/MicrosoftAjax.js" type="text/javascript"></script>
<script type="text/javascript">
function deleteRecord(recordId)
{
// Perform delete
var action = "/Home/Delete/" + recordId;
var request = new Sys.Net.WebRequest();
request.set_httpVerb("DELETE");
request.set_url(action);
request.add_completed(deleteCompleted);
request.invoke();
}
function deleteCompleted()
{
// Reload page
window.location.reload();
}
</script>
<h2>Index</h2>
<table>
<% foreach (var item in Model) { %>
<tr>
<td>
<%-- Ajax Delete --%>
<a οnclick="deleteRecord(<%= item.Id %>)" href="JavaScript:void(0)">Delete</a>
<%-- GET Delete: Security Hole
<%= Html.ActionLink("Delete", "Delete", new { id=item.Id })%>--%>
</td>
<td>
<%= Html.Encode(item.Id) %>
</td>
<td>
<%= Html.Encode(item.Title) %>
</td>
<td>
<%= Html.Encode(item.Director) %>
</td>
<td>
<%= Html.Encode(item.DateReleased) %>
</td>
</tr>
<% } %>
</table>
</asp:Content>
Delete通过Ajax调用执行。Delete链接调用Javascript deleteRecord()函数。该函数使用 Microsoft ASP.NET AJAX WebRequest 对象来执行Ajax调用。该WebRequest对象执行HTTP DELETE操作。
Delete操作执行完毕后,Javascript deleteCompleted()方法被调用。该方法重新加载了当前页面(这里将来会有一个更优雅的方式是使用下一个ASP.NET Ajax版本带来的ASP.NET Ajax模板功能,那样的话就只更新表格,而不用重新加载整个页面了)。
图2 - Index 视图
但是,我不想依赖于Javascript
很多开发人员不想自己的网站依赖于Javacript。回句话说,他们希望Javascript被关闭时,他们的网站一样可以工作。他们的需求是有些合理性的。不是所有移动设备都支持Javascript(尽管大多数做到了),并且Javascript还有可访问性问题(尽管Aria应该修正这些可访问性问题)。
如果您想自己的网站在Javascript被禁用时能工作,那么删除记录时,不能执行HTTP DELETE操作。而应该执行HTTP POST操作,HTTP POST不会像HTTP GET那样暴露出安全漏洞。
您可以使用AcceptVerbs特性来防止控制器动作被HTTP POST之外的操作调用。所以, Delete()动作看起来应该是这样的:
[AcceptVerbs(HttpVerbs.Post)]
public ActionResult Delete(int id)
{
var movieToDelete = (from m in _entities.MovieSet
where m.Id == id
select m).FirstOrDefault();
_entities.DeleteObject(movieToDelete);
_entities.SaveChanges();
return RedirectToAction("Index");
}
不幸的是,通过标准HTML来执行HTTP POST操作的唯一途径是使用<form>标签。并且,您必须还用一个<input type="submit">,<input type="image">,或者<input type="button">标签来为删除记录创建一个按钮。
这里最好的选择是<input type="image">。那样的话,您可以在展示数据库记录表格时,使得Edit和Delete链接看起来一样。因为我不想我的示例程序依赖于Javascript,所以我打算采用此方式。
清单3中是无Javascript依赖的Index视图。
清单3 - ViewsHomeIndex.aspx (无 JavaScript)
<%@ Page Title="" Language="C#" MasterPageFile="~/Views/Shared/Site.Master" Inherits="System.Web.Mvc.ViewPage<IEnumerable<Tip46.Models.Movie>>" %>
<asp:Content ID="Content1" ContentPlaceHolderID="MainContent" runat="server">
<h2>Index</h2>
<table>
<% foreach (var item in Model) { %>
<tr>
<td>
<a href='<%= Url.Action("Edit", "Home", new { id = item.Id })%>'><img src="Content/Edit.png" alt="edit" border="0" /></a>
</td>
<td>
<% using (Html.BeginForm("Delete", "Home", new { id = item.Id }))
{ %>
<input type="image" src="Content/Delete.png" />
<% } %>
</td>
<td>
<%= Html.Encode(item.Id) %>
</td>
<td>
<%= Html.Encode(item.Title) %>
</td>
<td>
<%= Html.Encode(item.Director) %>
</td>
<td>
<%= Html.Encode(item.DateReleased) %>
</td>
</tr>
<% } %>
</table>
<p>
<%= Html.ActionLink("Create New", "Create") %>
</p>
</asp:Content>
我从Visual Studio 图片库中获取了为Edit和Delete链接使用的图片(见图3)。您可以在您硬盘的这个位置拿到这些图片集:
C:Program FilesMicrosoft Visual Studio 9.0Common7VS2008ImageLibrary
图3 - 为Edit和Delete使用图片
为使图片正确对齐,我为表格单元格增加了垂直对齐的样式。我使用了下面的样式:
table
{
border-collapse:collapse;
}
td
{
vertical-align:top;
padding:10px;
border-bottom: solid 1px black;
}
结论
不要使用Delete链接来删除数据库记录。潜在的,可能有人在您未知情的情况下通过执行GET请求来删除。
最好的选择是使用Javascript来执行HTTP DELETE 操作。使用Javascript能让您避开安全漏洞。使用Javascript可以让您最终HTTP协议的语义。
如果您不想您的程序依赖于Javascript,那第二个最好的选择执行HTTP POST来替代HTTP DELETE。执行HTML POST 需要您使用HTML表单。这个是丑陋的,然而,您可以通过使用<input type="image">并添加样式表来改进外观。