如何检测表单中 GET 和 POST ViewModel 值之间的差异?
我有一个处理许多 VoyagesViewModel 类型模型的视图,在该视图中用户可以创建新航程或编辑所有活动航程,因此我在该视图中有相同对象类型的不同实例。 每个表单都包含类似的内容:
<% = Html.Hidden("Voyages["+ i +"].VoyageDetails[" + i2 + "].Id", location.Id)%>
<% = Html.TextBox("Voyages[" + i + "].VoyageDetails[" + i2 + "].ArrivalDate", location.ArrivalDate, new { @class = "dates" })%><% = Html.ValidationMessage("Voyages[" + i + "].VoyageDetails[" + i2 + "].ArrivalDate", "*")%>
<% = Html.TextBox("Voyages[" + i + "].VoyageDetails[" + i2 + "].DepartureDate", location.DepartureDate, new { @class = "dates" })%><% = Html.ValidationMessage("Voyages[" + i + "].VoyageDetails[" + i2 + "].DepartureDate", "*")%>
<% = Html.Hidden("Voyages[" + i + "].VoyageDetails[" + i2 + "].LocationID", location.LocationID)%>
<% = Html.Hidden("Voyages[" + i + "].VoyageDetails[" + i2 + "].LocationName", location.LocationName)%>
<% = Html.Hidden("Voyages[" + i + "].VoyageDetails[" + i2 + "].VesselName", location.VesselName)%>
<% = Html.Hidden("Voyages[" + i + "].VoyageDetails[" + i2 + "].VesselID", location.VesselID)%>
<% = Html.CheckBox("Voyages[" + i + "].VoyageDetails[" + i2 + "].remove", location.remove)%> (remove)
我在该视图中有 8 到 10 个这样的内容,当用户发布表单时(我在一个表单中拥有所有这些字段),我希望能够检测用户是否在航行中更改了某些内容保存它,这样如果没有进行任何更改,我就不必保存该特定航程。
为了检查这一点,我有一个隐藏字段,我这样做:
<% = Html.Hidden("Voyages[" + i + "].hash", TamperProofing.GetExpiringHMAC(new System.Web.Script.Serialization.JavaScriptSerializer().Serialize(Model), DateTime.Now.AddMinutes(15)))%>
我使用 HMAC 来获取洞对象的哈希版本,但首先我将对象序列化为字符串格式(JSON 格式):
{"Id":22,"VesselName":"CAPTAIN P (CPP)","VesselID":8,"VoyageDetails":[{"Id":58,"ArrivalDate":"\/Date(1259298000000)\/","DepartureDate":"\/Date(1259384400000)\/","LocationID":404,"LocationHash":null,"LocationName":"Balboa, Panama (PABLB)","VesselName":"CAPTAIN P (CPP)","VesselID":8,"Order":0,"Comment":null,"remove":false},{"Id":60,"ArrivalDate":"\/Date(1260248400000)\/","DepartureDate":"\/Date(1260334800000)\/","LocationID":406,"LocationHash":null,"LocationName":"Colon Free Zone, Panama (PACFZ)","VesselName":"CAPTAIN P (CPP)","VesselID":8,"Order":0,"Comment":null,"remove":false},{"Id":61,"ArrivalDate":"\/Date(1260421200000)\/","DepartureDate":"\/Date(1260507600000)\/","LocationID":407,"LocationHash":null,"LocationName":"Cristobal, Panama (PACTB)","VesselName":"CAPTAIN P (CPP)","VesselID":8,"Order":0,"Comment":null,"remove":false},{"Id":62,"ArrivalDate":null,"DepartureDate":null,"LocationID":408,"LocationHash":null,"LocationName":"Manzanillo, Panama (PAMAN)","VesselName":"CAPTAIN P (CPP)","VesselID":8,"Order":0,"Comment":null,"remove":false},{"Id":59,"ArrivalDate":null,"DepartureDate":null,"LocationID":405,"LocationHash":null,"LocationName":"Coco Solo, Panama (PACSO)","VesselName":"CAPTAIN P (CPP)","VesselID":8,"Order":0,"Comment":null,"remove":true}],"newVoyageDetail":null,"isComplete":false,"Code":"A Code","position":0,"hash":null}
因此,当用户发布表单时,我检查如果对每次航行进行这样的更改:
var obj = new System.Web.Script.Serialization.JavaScriptSerializer().Serialize(_voyage);
if (TamperProofing.VerifyChanges(obj, hash) == TamperProofing.HMACChanged.True)
{
//UPDATE
}else
{
//DO Nothing, is the exact same object
}
再次将其序列化并比较两个哈希值(隐藏的哈希值与最近计算的哈希值),如果匹配,则意味着没有任何更改。
一切都很好,我只是想知道是否还有其他选择可以做到这一点?
我的另一个担忧是序列化和所有由序列化生成的大字符串的 HMAC 所需的时间与将未更改的对象再次更新到数据库所需的时间。
编辑:我不需要知道哪个字段发生了变化,只要有什么变化就可以了。
I have a single view that handles a lot of Models of type VoyagesViewModel, in that view the user can create a new voyage or edit all the active voyages, so i have different instances of the same object type in that view.
each form contains something like:
<% = Html.Hidden("Voyages["+ i +"].VoyageDetails[" + i2 + "].Id", location.Id)%>
<% = Html.TextBox("Voyages[" + i + "].VoyageDetails[" + i2 + "].ArrivalDate", location.ArrivalDate, new { @class = "dates" })%><% = Html.ValidationMessage("Voyages[" + i + "].VoyageDetails[" + i2 + "].ArrivalDate", "*")%>
<% = Html.TextBox("Voyages[" + i + "].VoyageDetails[" + i2 + "].DepartureDate", location.DepartureDate, new { @class = "dates" })%><% = Html.ValidationMessage("Voyages[" + i + "].VoyageDetails[" + i2 + "].DepartureDate", "*")%>
<% = Html.Hidden("Voyages[" + i + "].VoyageDetails[" + i2 + "].LocationID", location.LocationID)%>
<% = Html.Hidden("Voyages[" + i + "].VoyageDetails[" + i2 + "].LocationName", location.LocationName)%>
<% = Html.Hidden("Voyages[" + i + "].VoyageDetails[" + i2 + "].VesselName", location.VesselName)%>
<% = Html.Hidden("Voyages[" + i + "].VoyageDetails[" + i2 + "].VesselID", location.VesselID)%>
<% = Html.CheckBox("Voyages[" + i + "].VoyageDetails[" + i2 + "].remove", location.remove)%> (remove)
I have 8 to 10 of this in that View, when the user POST the form(I have all this fields in a single form), i want to be able to detect if the user change something in a voyage to save it, so i don't have to save that specific voyage if no changes where made.
To check this i have a hidden field and i do this:
<% = Html.Hidden("Voyages[" + i + "].hash", TamperProofing.GetExpiringHMAC(new System.Web.Script.Serialization.JavaScriptSerializer().Serialize(Model), DateTime.Now.AddMinutes(15)))%>
Im using HMAC to get a hashed version of the hole object, but first i serialize the object into a string format (JSON Format):
{"Id":22,"VesselName":"CAPTAIN P (CPP)","VesselID":8,"VoyageDetails":[{"Id":58,"ArrivalDate":"\/Date(1259298000000)\/","DepartureDate":"\/Date(1259384400000)\/","LocationID":404,"LocationHash":null,"LocationName":"Balboa, Panama (PABLB)","VesselName":"CAPTAIN P (CPP)","VesselID":8,"Order":0,"Comment":null,"remove":false},{"Id":60,"ArrivalDate":"\/Date(1260248400000)\/","DepartureDate":"\/Date(1260334800000)\/","LocationID":406,"LocationHash":null,"LocationName":"Colon Free Zone, Panama (PACFZ)","VesselName":"CAPTAIN P (CPP)","VesselID":8,"Order":0,"Comment":null,"remove":false},{"Id":61,"ArrivalDate":"\/Date(1260421200000)\/","DepartureDate":"\/Date(1260507600000)\/","LocationID":407,"LocationHash":null,"LocationName":"Cristobal, Panama (PACTB)","VesselName":"CAPTAIN P (CPP)","VesselID":8,"Order":0,"Comment":null,"remove":false},{"Id":62,"ArrivalDate":null,"DepartureDate":null,"LocationID":408,"LocationHash":null,"LocationName":"Manzanillo, Panama (PAMAN)","VesselName":"CAPTAIN P (CPP)","VesselID":8,"Order":0,"Comment":null,"remove":false},{"Id":59,"ArrivalDate":null,"DepartureDate":null,"LocationID":405,"LocationHash":null,"LocationName":"Coco Solo, Panama (PACSO)","VesselName":"CAPTAIN P (CPP)","VesselID":8,"Order":0,"Comment":null,"remove":true}],"newVoyageDetail":null,"isComplete":false,"Code":"A Code","position":0,"hash":null}
So when the user POST the form i check if changes where made to each voyage like this:
var obj = new System.Web.Script.Serialization.JavaScriptSerializer().Serialize(_voyage);
if (TamperProofing.VerifyChanges(obj, hash) == TamperProofing.HMACChanged.True)
{
//UPDATE
}else
{
//DO Nothing, is the exact same object
}
Serialize it again and compare both hashes, the one on the hidden against the recently calculated one, if it match it means nothing changed.
All works pretty well, i was just wondering, if there are any other options to do this?
And my other concern is the time that can take the serialization and all the HMAC thing with this large string generated by the Serialization against the time it could take just to Update unchanged object again to the DB.
EDIT: I don't need to know which field changed, just if something changed.
如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。
绑定邮箱获取回复消息
由于您还没有绑定你的真实邮箱,如果其他用户或者作者回复了您的评论,将不能在第一时间通知您!
发布评论
评论(1)
如果服务器性能是一个问题,您可以考虑为每条记录创建一个“已更改”字段,并在用户可编辑字段值之一发生更改时使用 Javascript 来设置它。
为了确保下层浏览器或 NoScript 边缘情况的正确性,一种常见的模式是使用三态值,其中一个状态表示“未知”或“JS 禁用”。例如,您将服务器上的字段初始化为 0(或空),让 JS 代码在加载时将它们全部设置为 1,然后在修改字段时将它们设置为 2。如果您的服务器在 POST 上看到 0/null/empty,那么它会退回到服务器端方法,在本例中是您的哈希比较。如果它设置为 1,那么它会忽略该记录,如果它设置为 2,那么它会自动触发更新。
这样,您可以在 99% 的情况下避免计算哈希,并且仍然正确处理异常值。
这不一定能防止篡改,但您并没有具体说明要防止哪种篡改。最有效的防篡改方法实际上是首先将对象或信息保持在无法访问的位置,即处于会话状态或加密的 cookie 中。
这有帮助吗?
If server performance is a concern, you might consider creating a "changed" field for each record and using Javascript to set it when one of the user-editable field values changes.
To ensure correctness in downlevel browsers or NoScript fringe cases, a common pattern is to use a tri-state value, with one state meaning "unknown" or "JS disabled". For example, you initialize the fields on the server to 0 (or empty), have your JS code set them all to 1 on load, and then to 2 when the field is modified. If your server sees a 0/null/empty on POST, then it falls back on the server-side method, in this case your hash-compare. If it's set to 1, then it ignores that record, and if it's set to 2, then it automatically triggers an update.
This way, you could avoid computing hashes in 99% of cases, and still correctly handle the outliers.
This doesn't necessarily prevent tampering but you weren't too specific about what kind of tampering you're trying to prevent. The most effective means of tamper-proofing is really to keep the object or information out of reach in the first place, i.e. in the session state or an encrypted cookie.
Does that help at all?