维护旧系统,日志报告一个Unable to validate data错误:
------FatalError: [2010-07-23 11:36:01] Unable to validate data.
at System.Web.Configuration.MachineKeySection.GetDecodedData(Byte[] buf, Byte[] modifier, Int32 start, Int32 length, Int32& dataLength)
at System.Web.UI.ObjectStateFormatter.Deserialize(String inputString)
觉得挺奇怪,一个很简单的页面,也用了较长时间了,怎么会有这个错误,上网搜索了下,才知道这个错误的来由,然后去确认了,才知道是有人测试页面故意修改了ViewState所致。
原文链接:http://www.developmentnow.com/blog/InvalidViewstate+Or+Unable+To+Validate+Data+Error.aspx
转载如下:
On one of my projects, the client noted getting occasional errors when filling out forms. The errors were something like "Unable to validate data" or HttpException: Invalid_Viewstate. After some research, it turns out the error is due to the viewstate MAC (message authentication code) changing between postbacks, or more specifically, the viewstate MAC in the page is different than the MAC the server is expecting.
What is the ViewState MAC?
MAc stands for Message Authentication Code -- it's a way to ensure that messages haven't been tampered with.
As you know, the ViewState is ASP.NET's way of persisting form data across postbacks (John Peterson has a good overview if you want to know basic ViewState facts). This data appears in base-64 encoded form inside a hidden form value called __VIEWSTATE. The MAC is an additional value designed to prevent changing or tampering with viewstate data. ASP.NET looks at the data in the viewstate and, using a secret validation key, generates a hash from that data using SHA-1. That hash value is the MAC, and ASP.NET includes it in the form. When a postback occurs, your browser passes the form data, the viewstate, and the MAC value to the web server. ASP.NET looks at the submitted viewstate data and generates another MAC value on the fly. It then compares that MAC value to the MAC value contained in the POST information. If the two values match, then no one has tampered with the viewstate. If the newly-generated MAC doesn't match what comes across, however, then ASP.NET believes someone has monkeyed with the data in the viewstate and throws an Invalid_Viewstate error.
Why would the MAC be different from what the server expected?
Several things can cause the MAC to not match what ASP.NET is expecting:
- Some hacker (maybe even you?) actually tried to change the data in the viewstate
- The form data got cut off during submission to the server (due to timeout, crappy proxy server, etc)
- Using Server.Transfer can cause it to happen
- The validation key used to generate the previous MAC is different than the key being used to generate the new MAC
The last issue can happen more easily that you'd expect. One way is if you're running a web farm. By default, the validation key used to create the MAC is randomly generated by ASP.NET when the application pool starts up. This ensures that the validation key is unique and changes periodically. However, since the key is different from server to server, if you're viewing a page on Server A and post it to Server B, when Server B generates a MAC based on the viewstate data, that value won't match the MAC value when the page was initially served by Server A. Thus, you'll get an Invalid_Viewstate error.
Another reason the validation key (and thus the MAC) will be different is if you cross application pools. If you view a page running in pool A, and post to another page on pool B (e.g. through Server.Transfer), the key will be different & you'll get a mismatch.
Lastly, the validation key can change mid-session for users if the application pool restarts. Assume some user is viewing a page on your site and filling out a form. While they're doing that, some sysadmin restarts the pool, thus generating a new key. When the user posts the page they'll get an Invalid_Viewstate error. The application can also restart if it is set to shut down while idle (the default is to shut down pools that have been idle for 20 minutes). Imagine a user who views a page on your site, goes away for 10 minutes then maybe spends 20 minutes filling in the form on the page. Meanwhile, no one else is on your site, so the application pool times out & shuts down. When the user finally posts the form (30 minutes after viewing it), the application pool will start back up, create a new validation key, generate a new MAC, notice that the MAC values don't match, and reward you user's diligence with an Invalid_Viewstate error.
Avoiding MAC Mismatch
There are a few ways to avoid MAC mismatch mishaps:
- Don't use the ViewState if you don't need to. Not only will it avoid the whole MAC issue, but your pages will run faster to boot.
- Turn off MAC generation by setting enableViewStateMac=false in the page or web.config. This isn't recommended, since the MAC helps prevent people from tampering with your viewstate data. But if tampering with viewstate data isn't a concern (and it may not be for some applications where there's no risk of fraud or security breaches), you can turn it off.
- Prevent your application pool from restarting by disabling the auto recycle and idle timeout settings in the application pool. This isn't a 100% guarantee that the pool won't restart, but it does help.
- Hard-code the MAC validation key so that it's always the same. I recommend this approach for web farms and/or if your application pool keep restarting for whatever reason (overzealous admins, memory leaks, etc). The biggest risk is now your key is hard-coded in a file, so you need to make sure your server is secure so that people don't get that key (otherwise they could hack your viewstate). You can hardcode the key in the <machineKey> tag in the machine.config or web.config, like this:
<machineKey validationKey="21F090935F6E49C2C797F69BBAAD8402ABD2EE0B667A8B44EA7DD4374267A75D7" decryptionKey="ABAA84D7EC4BB56D75D217CECFFB9628809BDB8BF91CFCD64568A145BE59719F" validation="SHA1" />
If you're using ASP.NET 2.0, your machineKey tag should also have the decryption attribute, like this:
<machineKey validationKey="21F090935F6E49C2C797F69BBAAD8402ABD2EE0B667A8B44EA7DD4374267A75D7" decryptionKey="ABAA84D7EC4BB56D75D217CECFFB9628809BDB8BF91CFCD64568A145BE59719F" validation="SHA1" decryption="AES" />
Note, the above values are just examples! Generate your own values for validationKey and decryptionKey (and keep them secret) using the code from this MSDN page. A modified version is shown below:
using System; using System.Text; using System.Diagnostics; using System.Security; using System.Security.Cryptography; class App { static void Main(string[] args) { Debug.WriteLine("64-byte validationKey: " + getRandomKey(64)); Debug.WriteLine("24-byte decryptionKey: " + getRandomKey(24)); } public static string getRandomKey(int bytelength) { int len = bytelength * 2; byte[] buff = new byte[len/2]; RNGCryptoServiceProvider rng = new RNGCryptoServiceProvider(); rng.GetBytes(buff); StringBuilder sb = new StringBuilder(len); for (int i=0; i<buff.Length; i++) sb.Append(string.Format("{0:X2}", buff[i])); return sb.ToString(); } }
Hopefully the above will help you out if you encounter strange "invalid viewstate" errors, or if you plan to use the viewstate in a web farm, or if you just like knowing more about the viewstate.
P.S. While typing this entry, I accidentally hit the thumb button on my MX510, which by default hits the browser's "Back" button. Which sent me back to the previous page, which made me lose everything I had typed. Which was a super downer. So, disable that mouse button if you don't use it, or you'll be sorry! :)